Help us shape the future of Go! Take the 2026 Go Developer Survey

display

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package display coordinates the single interactive VM display across API replicas. Media never enters this package.

Index

Constants

View Source
const (
	// SessionProtocol is the display session API/protocol revision advertised
	// to clients.
	SessionProtocol = 1

	// MaxParticipants caps simultaneous members (controller + observers) per
	// workspace display session. Chosen to keep capture framebuffer fan-out and
	// per-observer outbound queues bounded while allowing practical collabo-desk
	// sessions.
	MaxParticipants = 8

	// MaxWidth/MaxHeight bound the advertised guest framebuffer used for
	// validation and memory bounding.
	MaxWidth  = 4096
	MaxHeight = 2160

	// HandshakeTimeout bounds each observer's display stream negotiation.
	HandshakeTimeout = 10 * time.Second

	// IdleMembershipTTL is how long a participant may remain inactive before the
	// registry drops it. Observer streams keep this refreshed via heartbeat
	// touch; a joined-but-never-attached participant also expires.
	IdleMembershipTTL = 15 * time.Minute
)
View Source
const (
	// RoleObserver is a view-only participant.
	RoleObserver = "observer"
	// RoleController can inject input and is the single writer.
	RoleController = "controller"
)
View Source
const (
	// ClientTTL is how long a claim stays valid without a renewal, measured on
	// each participant's own monotonic clock from when it sent the request.
	ClientTTL = 4 * time.Second
	// FenceInterval is how long a contender must observe an unchanged
	// resourceVersion before it may take over a held lease. It exceeds the
	// worst case in which a partitioned owner could still be writing input:
	// its renewals fail, and its local input deadline passes, within ClientTTL
	// of losing API contact — long before FenceInterval elapses.
	FenceInterval = 12 * time.Second
)

Variables

View Source
var (
	// ErrCapacity means the workspace display session reached MaxParticipants.
	ErrCapacity = errors.New("display session participant limit reached")
	// ErrParticipantNotFound means the workspace or participant id is unknown.
	ErrParticipantNotFound = errors.New("display participant not found")
	// ErrControllerPresent means a controller already holds the display.
	ErrControllerPresent = errors.New("display already controlled")
	// ErrNotController means the acting participant does not hold control.
	ErrNotController = errors.New("participant is not the current controller")
	// ErrAlreadyController means the acting participant already holds control.
	ErrAlreadyController = errors.New("participant is already the controller")
	// ErrBadTarget is returned when a transfer target is missing or not an observer.
	ErrBadTarget = errors.New("transfer target must be an observer participant")
	// ErrInvalidRole is returned for an unknown join role.
	ErrInvalidRole = errors.New("invalid display participant role")
)
View Source
var ErrBusy = errors.New("interactive display in use")
View Source
var ErrInputFenced = errors.New("controller input fencing lost")

ErrInputFenced means the controller's input lease is no longer renewable, so its writes must stop within the fencing bound (ClientTTL).

View Source
var ErrRevoked = errors.New("interactive display ownership revoked")

Functions

This section is empty.

Types

type Capability added in v0.6.0

type Capability struct {
	Protocol          int
	Transports        []string
	MaxParticipants   int
	MaxWidth          int
	MaxHeight         int
	HandshakeTimeout  time.Duration
	IdleMembershipTTL time.Duration
	Participants      int
	ControllerPresent bool
}

Capability describes the session limits plus a live count snapshot.

type Fence added in v0.6.0

type Fence struct {
	// contains filtered or unexported fields
}

Fence is the input boundary for one controller participant. Admitting a controller to the registry is not enough to write: the control lease must be renewable and the local Registry must still list the participant as controller. A Fence therefore gates input dispatch, dropping writes at the fencing bound when either leg is lost, mirroring the legacy Guard semantics.

func AcquireControl added in v0.6.0

func AcquireControl(ctx context.Context, store *Store, sessions *Sessions, ns, name, participant string, force bool) (*Fence, error)

AcquireControl grants participant the input fence on ns/name. With force, an existing control holder is revoked first (its renewal dies at once, its input no later than ClientTTL) and this call claims the slot as soon as it is free; without force the local Registry must already list participant as controller. The returned Fence renews the lease in the background until Close.

func (*Fence) Close added in v0.6.0

func (f *Fence) Close()

Close stops renewing and releases the control lease. Like Guard.Close it must be called after the associated input path has been closed; a stale token cannot release a successor.

func (*Fence) Deadline added in v0.6.0

func (f *Fence) Deadline() (time.Time, bool)

func (*Fence) DispatchInput added in v0.6.0

func (f *Fence) DispatchInput(fn func()) error

DispatchInput runs fn only while participant still holds the registry controller role and the control lease has not passed its fencing bound; otherwise the write is dropped with the appropriate error.

func (*Fence) Done added in v0.6.0

func (f *Fence) Done() <-chan struct{}

type Guard

type Guard struct {
	// contains filtered or unexported fields
}

Guard bounds validity using request-start times on this process's monotonic clock. A delayed renewal must not resurrect an expired owner.

One guard owns the claims behind one VNC-console session: the capture lease always (the console is single-connection) and, when the caller also owns the interactive display, the seat lease. An observer-only broker generation holds the capture alone while a Tier 1 session holds the seat; it upgrades with EnsureSeat once the seat frees. Losing any held claim stops the guard, because a session that lost the seat was taken over and a session that lost the capture must not keep a console someone else is about to dial.

func Acquire

func Acquire(ctx context.Context, store *Store, ns, name string) (*Guard, error)

Acquire claims the interactive seat (tier "vnc") and the VNC-console capture for ns/name, so exactly one session can drive the console and the console is reserved before it is dialled. A capture failure releases the freshly claimed seat: the pair is atomic from the caller's point of view.

func AcquireCapture added in v0.6.0

func AcquireCapture(ctx context.Context, store *Store, ns, name string) (*Guard, error)

AcquireCapture claims only the VNC-console capture. It is the observer-only mode of a shared display: the desktop is driven elsewhere (a Tier 1 session holds the seat), but observers still need the one console connection coordinated so no second dialer races them to it.

func (*Guard) Close

func (g *Guard) Close()

Close must be invoked after the caller has closed its media sockets. It releases every claim the guard held; stale ids can never release a successor.

func (*Guard) Deadline

func (g *Guard) Deadline() (time.Time, bool)

func (*Guard) Done

func (g *Guard) Done() <-chan struct{}

func (*Guard) EnsureSeat added in v0.6.0

func (g *Guard) EnsureSeat(ctx context.Context) error

EnsureSeat claims the interactive seat (tier "vnc") for a guard that started capture-only, upgrading an observer-only generation to a full shared display once the previous seat holder (a Tier 1 session) let go. It is idempotent and fails with the claim's busy/ownership error when another session won the seat first.

func (*Guard) HasSeat added in v0.6.0

func (g *Guard) HasSeat() bool

HasSeat reports whether the guard currently holds the interactive seat — the condition under which shared-display participants may drive, not just watch.

type Membership added in v0.6.0

type Membership struct {
	// contains filtered or unexported fields
}

Membership coordinates the cross-replica routing marker for the in-memory session registry. The registry lives per replica, but clients reach any replica: before a broker generation exists (and its seat/capture leases record the owner), the first REST join must record WHICH replica holds the workspace's membership so sibling replicas can forward stream attaches and membership/control calls there.

The marker is the kw-display-membership-* coordination lease, claimed on first join, renewed while local members exist, and released when the last member leaves. It gates nothing media-side; the seat/capture/control leases keep their own invariants.

func NewMembership added in v0.6.0

func NewMembership(store *Store, sessions *Sessions) *Membership

NewMembership wires the registry to the lease store. The returned coordinator must be installed on the Sessions (SetMembershipClaimer) and driven with Run.

func (*Membership) ClaimForJoin added in v0.6.0

func (m *Membership) ClaimForJoin(ns, name string) error

ClaimForJoin implements Sessions.MembershipClaimer: it is called when a join creates a fresh local session for a workspace and records this replica as the membership owner. A claim by another live replica fails with an *OwnershipError naming it; the caller unwinds the join so the client's next attempt is forwarded to the owner.

func (*Membership) Owner added in v0.6.0

func (m *Membership) Owner(ctx context.Context, ns, name string) (string, error)

Owner returns the recorded membership owner for ns/name, or "".

func (*Membership) Run added in v0.6.0

func (m *Membership) Run(ctx context.Context)

Run renews live claims while their local session exists and releases claims whose session emptied (last leave or idle TTL). It exits with ctx.

type MembershipClaimer added in v0.6.0

type MembershipClaimer interface {
	// ClaimForJoin claims the membership-owner lease for ns/name. An
	// *OwnershipError means another replica holds it; the join is unwound
	// and the client's next attempt is forwarded to the owner.
	ClaimForJoin(ns, name string) error
}

MembershipClaimer records which replica owns a workspace's membership when the first join creates a local session. The registry is in-memory per replica, so without this marker a stream attach or REST call landing on a sibling replica would answer 404 from an empty registry (see Membership).

type OwnershipError added in v0.6.0

type OwnershipError struct {
	Owner string
	Kind  string
}

OwnershipError reports that another API replica owns a workspace display or its control slot. Owner is the replica identity recorded on the lease at claim time (see ownerKey); Kind is which lease held it ("display-ownership" or "display-control"). It unwraps to ErrBusy so callers keep working with a plain busy check while learning where to route.

func (*OwnershipError) Error added in v0.6.0

func (e *OwnershipError) Error() string

func (*OwnershipError) Unwrap added in v0.6.0

func (e *OwnershipError) Unwrap() error

type Participant added in v0.6.0

type Participant struct {
	ID        string
	Role      string
	Connected bool
	JoinedAt  time.Time
}

Participant is a snapshot of a workspace display session member.

type SeatInfo added in v0.10.0

type SeatInfo struct {
	InUse      bool   `json:"inUse"`
	Transport  string `json:"transport"`
	ProxyOwner string `json:"proxyOwner"`
}

SeatInfo is one coherent ownership snapshot for transport discovery.

type Sessions added in v0.6.0

type Sessions struct {
	// contains filtered or unexported fields
}

Sessions is the in-memory membership registry for shared display sessions. It enforces the "one controller plus view-only observers" invariant and keeps participants bounded. B later binds control back to the coordination Lease store when proving multi-replica safety; this registry alone is local only.

func NewSessions added in v0.6.0

func NewSessions() *Sessions

NewSessions returns an empty registry.

func (*Sessions) Acquire added in v0.6.0

func (s *Sessions) Acquire(ns, name, id string, force bool) (*Participant, bool, error)

Acquire makes id the controller. Without force an occupied display yields ErrControllerPresent; with force the current controller is demoted to observer. Returns the effective controller and whether control was held before the transition.

func (*Sessions) Capability added in v0.6.0

func (s *Sessions) Capability(ns, name string) Capability

Capability returns the static limits and a live snapshot. The returned Capability is safe for concurrent use.

func (*Sessions) Join added in v0.6.0

func (s *Sessions) Join(ns, name, role string) (*Participant, error)

Join adds a participant. role is RoleObserver or RoleController. Requesting controller on an occupied display yields ErrControllerPresent. When the join creates a fresh session and a MembershipClaimer is installed, the claim runs before the join returns: a claim owned by another replica unwinds the member and returns the *OwnershipError so the caller can route.

func (*Sessions) Keys added in v0.6.0

func (s *Sessions) Keys() []string

Keys returns the keys (ns/name) of sessions with at least one live member, purging idle members first. The membership coordinator uses it to renew live claims and release emptied ones.

func (*Sessions) Leave added in v0.6.0

func (s *Sessions) Leave(ns, name, id string) error

Leave removes a participant, clearing control first when the departing member held it. Observers are unaffected.

func (*Sessions) Lookup added in v0.6.0

func (s *Sessions) Lookup(ns, name, id string) (*Participant, error)

Lookup returns a live snapshot of an existing participant, updating its idle deadline. It is used by the stream route to bind a WebSocket connection to a participant registered through the REST membership API.

func (*Sessions) Release added in v0.6.0

func (s *Sessions) Release(ns, name, id string) (*Participant, bool, error)

Release clears control when id is the current controller, demoting it to observer. Non-controller participants acting on release get ErrNotController.

func (*Sessions) SetConnected added in v0.6.0

func (s *Sessions) SetConnected(ns, name, id string, connected bool) error

SetConnected marks participant attachment state.

func (*Sessions) SetControlLocked added in v0.6.0

func (s *Sessions) SetControlLocked(ns, name string, locked bool)

SetControlLocked marks whether controller joins/acquires are refused for the workspace. The shared-display broker sets it while it serves an observer-only generation (a Tier 1 session holds the interactive seat) and clears it when the seat is claimed or the generation ends. It is a UX-honest gate on top of the stream route's own controller check.

func (*Sessions) SetMembershipClaimer added in v0.6.0

func (s *Sessions) SetMembershipClaimer(c MembershipClaimer)

SetMembershipClaimer installs the cross-replica ownership hook consulted by Join when it creates a fresh session.

func (*Sessions) Status added in v0.6.0

func (s *Sessions) Status(ns, name string) (Status, bool)

Status returns the live membership snapshot, or the zero Status plus nil when no session exists.

func (*Sessions) Touch added in v0.6.0

func (s *Sessions) Touch(ns, name, id string) error

Touch refreshes a participant's idle deadline.

func (*Sessions) Transfer added in v0.6.0

func (s *Sessions) Transfer(ns, name, id, to string) (*Participant, bool, error)

Transfer moves control from the current controller id to observer to. Returns the new controller and whether control was held before.

type Status added in v0.6.0

type Status struct {
	Protocol     int
	Epoch        string
	Controller   *Participant
	Observers    []*Participant
	Participants int
}

Status is a point-in-time membership snapshot for one workspace.

type Store

type Store struct {
	// contains filtered or unexported fields
}

func NewStore

func NewStore(client kubernetes.Interface) *Store

func NewStoreWithOwner added in v0.6.0

func NewStoreWithOwner(client kubernetes.Interface, owner string) *Store

NewStoreWithOwner attaches this API replica's identity to every lease it claims. Other replicas read it to route clients to the pod that actually generates a workspace's display (see Owner, SeatOwner, ControlOwner).

func (*Store) CaptureOwner added in v0.6.0

func (s *Store) CaptureOwner(ctx context.Context, ns, name string) (string, error)

CaptureOwner reports the replica that currently holds the capture lease for ns/name, or "" when it is free or absent. It is the cross-replica routing key for shared-display traffic: a replica that cannot claim the capture must send observers to the owner instead of dialing a second console.

func (*Store) Claim

func (s *Store) Claim(ctx context.Context, ns, name, tier string) (string, error)

func (*Store) ClaimAgent added in v0.10.0

func (s *Store) ClaimAgent(ctx context.Context, ns, name, owner string) (string, error)

ClaimAgent distinguishes encoded premium fan-out from Selkies' Tier 1 seat.

func (*Store) ClaimCapture added in v0.6.0

func (s *Store) ClaimCapture(ctx context.Context, ns, name string) (string, error)

ClaimCapture takes the VNC-console capture lease for a workspace. See captureLeaseName for who claims it and why.

func (*Store) ClaimControl added in v0.6.0

func (s *Store) ClaimControl(ctx context.Context, ns, name, participant string) (string, error)

ClaimControl takes the control-role lease for a workspace, associated with a session participant id. It lives in a separate coordination object from the capture/seat lease so input ownership can be split from framebuffer capture (and revoked/transferred independently).

func (*Store) ClaimMembership added in v0.6.0

func (s *Store) ClaimMembership(ctx context.Context, ns, name string) (string, error)

ClaimMembership takes the membership-owner lease for a workspace. Unlike the media leases, a claim by the SAME replica that already holds it adopts the slot with a fresh id: after a process restart the registry is empty while the old claim may still be fresh, and the replica must be able to re-attach its own routing marker without waiting out the fencing interval. Another replica's live claim still yields an OwnershipError naming it.

func (*Store) ControlHolder added in v0.6.0

func (s *Store) ControlHolder(ctx context.Context, ns, name string) (string, bool, error)

ControlHolder reports the control lease's recorded participant, whether the lease is held, and the holder token. The participant id is advisory to the registry and is not an authorization boundary on its own.

func (*Store) ControlInUse added in v0.6.0

func (s *Store) ControlInUse(ctx context.Context, ns, name string) (bool, error)

ControlInUse reports whether the control lease is currently held (not fenced).

func (*Store) ControlOwner added in v0.6.0

func (s *Store) ControlOwner(ctx context.Context, ns, name string) (string, error)

ControlOwner reports the replica that currently holds the control-role lease for ns/name, or "" when it is free or absent. The control claim is written by the seat holder, so this normally equals SeatOwner.

func (*Store) InUse

func (s *Store) InUse(ctx context.Context, ns, name string) (bool, error)

func (*Store) LiveCaptureOwner added in v0.6.0

func (s *Store) LiveCaptureOwner(ctx context.Context, ns, name string) (string, error)

LiveCaptureOwner is CaptureOwner with the same liveness check as LiveSeatOwner.

func (*Store) LiveMembershipOwner added in v0.6.0

func (s *Store) LiveMembershipOwner(ctx context.Context, ns, name string) (string, error)

LiveMembershipOwner is MembershipOwner with the same liveness check as LiveSeatOwner.

func (*Store) LiveSeatOwner added in v0.6.0

func (s *Store) LiveSeatOwner(ctx context.Context, ns, name string) (string, error)

LiveSeatOwner is SeatOwner with a liveness check: it returns "" once the recorded holder has gone quiet past this replica's fencing observation, so routing toward a dead owner's pod stops and a local takeover can proceed. The repeated reads that drive routing traffic ARE the fencing observation.

func (*Store) MembershipOwner added in v0.6.0

func (s *Store) MembershipOwner(ctx context.Context, ns, name string) (string, error)

MembershipOwner reports the replica that currently holds the membership lease for ns/name, or "" when it is free or absent. It is the cross-replica routing key for the in-memory session registry: a replica that cannot find a participant locally forwards to the membership owner.

func (*Store) Owner added in v0.6.0

func (s *Store) Owner() string

Owner reports the replica identity this store writes onto claims.

func (*Store) Release

func (s *Store) Release(ctx context.Context, ns, name, id string) error

Release is called only after media sockets/workers have stopped. A stale token can never release a successor. Tokens must not be logged or sent to guests.

func (*Store) ReleaseCapture added in v0.6.0

func (s *Store) ReleaseCapture(ctx context.Context, ns, name, id string) error

ReleaseCapture frees the capture lease held by id, called only after the console connection it guarded is closed. A stale id can never release a successor.

func (*Store) ReleaseControl added in v0.6.0

func (s *Store) ReleaseControl(ctx context.Context, ns, name, token string) error

ReleaseControl frees the control lease held by token. A stale token can never release a successor.

func (*Store) ReleaseMembership added in v0.6.0

func (s *Store) ReleaseMembership(ctx context.Context, ns, name, id string) error

ReleaseMembership frees the membership lease held by id. A stale id can never release a successor.

func (*Store) Renew

func (s *Store) Renew(ctx context.Context, ns, name, id string) error

func (*Store) RenewCapture added in v0.6.0

func (s *Store) RenewCapture(ctx context.Context, ns, name, id string) error

RenewCapture keeps the capture lease alive, with the same fencing semantics as Renew.

func (*Store) RenewControl added in v0.6.0

func (s *Store) RenewControl(ctx context.Context, ns, name, token string) error

RenewControl keeps the control lease alive. Once revoked it only fails; a stripped controller loses the lease at the developer-controlled fence bound.

func (*Store) RenewMembership added in v0.6.0

func (s *Store) RenewMembership(ctx context.Context, ns, name, id string) error

RenewMembership keeps the membership lease alive, with the same fencing semantics as Renew.

func (*Store) Revoke

func (s *Store) Revoke(ctx context.Context, ns, name string) (bool, error)

Revoke marks the current owner revoked without freeing its slot. The old proxy/API must stop input before Release, or the full fence interval elapses.

func (*Store) RevokeControl added in v0.6.0

func (s *Store) RevokeControl(ctx context.Context, ns, name string) (bool, error)

RevokeControl strips the current control holder without freeing the slot, mirroring Revoke for the capture seat. Renewal fails immediately; input stops within ClientTTL of the holder observing the revoke.

func (*Store) SeatInfo added in v0.10.0

func (s *Store) SeatInfo(ctx context.Context, ns, name string) (SeatInfo, error)

func (*Store) SeatOwner added in v0.6.0

func (s *Store) SeatOwner(ctx context.Context, ns, name string) (string, error)

SeatOwner reports the replica that currently holds the capture/seat lease for ns/name, or "" when the lease is free or absent.

func (*Store) SeatTier added in v0.6.0

func (s *Store) SeatTier(ctx context.Context, ns, name string) (string, bool, error)

SeatTier reports the transport tier recorded on a held seat lease: "vnc", "tier1", or "" when the seat is free (or held by a build that records no tier). A shared-display broker uses it to tell "a Tier 1 controller owns this desktop — observers may watch but nobody here may drive" from every other hold state.

func (*Store) ServeHTTP

func (s *Store) ServeHTTP(w http.ResponseWriter, r *http.Request, ns, name, action string)

ServeHTTP requires the enclosing route to authorize editor/namespace access and resolve the VM. Claim IDs are workspace-scoped, high-entropy capabilities in a header, never a URL/query string or a credential sent to the guest.

func (*Store) SetNow added in v0.6.0

func (s *Store) SetNow(now func() time.Time)

SetNow overrides the store's clock. It exists for tests that drive fencing observations through time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL