Documentation
¶
Overview ¶
Package display coordinates the single interactive VM display across API replicas. Media never enters this package.
Index ¶
- Constants
- Variables
- type Capability
- type Fence
- type Guard
- type Membership
- type MembershipClaimer
- type OwnershipError
- type Participant
- type SeatInfo
- type Sessions
- func (s *Sessions) Acquire(ns, name, id string, force bool) (*Participant, bool, error)
- func (s *Sessions) Capability(ns, name string) Capability
- func (s *Sessions) Join(ns, name, role string) (*Participant, error)
- func (s *Sessions) Keys() []string
- func (s *Sessions) Leave(ns, name, id string) error
- func (s *Sessions) Lookup(ns, name, id string) (*Participant, error)
- func (s *Sessions) Release(ns, name, id string) (*Participant, bool, error)
- func (s *Sessions) SetConnected(ns, name, id string, connected bool) error
- func (s *Sessions) SetControlLocked(ns, name string, locked bool)
- func (s *Sessions) SetMembershipClaimer(c MembershipClaimer)
- func (s *Sessions) Status(ns, name string) (Status, bool)
- func (s *Sessions) Touch(ns, name, id string) error
- func (s *Sessions) Transfer(ns, name, id, to string) (*Participant, bool, error)
- type Status
- type Store
- func (s *Store) CaptureOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) Claim(ctx context.Context, ns, name, tier string) (string, error)
- func (s *Store) ClaimAgent(ctx context.Context, ns, name, owner string) (string, error)
- func (s *Store) ClaimCapture(ctx context.Context, ns, name string) (string, error)
- func (s *Store) ClaimControl(ctx context.Context, ns, name, participant string) (string, error)
- func (s *Store) ClaimMembership(ctx context.Context, ns, name string) (string, error)
- func (s *Store) ControlHolder(ctx context.Context, ns, name string) (string, bool, error)
- func (s *Store) ControlInUse(ctx context.Context, ns, name string) (bool, error)
- func (s *Store) ControlOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) InUse(ctx context.Context, ns, name string) (bool, error)
- func (s *Store) LiveCaptureOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) LiveMembershipOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) LiveSeatOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) MembershipOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) Owner() string
- func (s *Store) Release(ctx context.Context, ns, name, id string) error
- func (s *Store) ReleaseCapture(ctx context.Context, ns, name, id string) error
- func (s *Store) ReleaseControl(ctx context.Context, ns, name, token string) error
- func (s *Store) ReleaseMembership(ctx context.Context, ns, name, id string) error
- func (s *Store) Renew(ctx context.Context, ns, name, id string) error
- func (s *Store) RenewCapture(ctx context.Context, ns, name, id string) error
- func (s *Store) RenewControl(ctx context.Context, ns, name, token string) error
- func (s *Store) RenewMembership(ctx context.Context, ns, name, id string) error
- func (s *Store) Revoke(ctx context.Context, ns, name string) (bool, error)
- func (s *Store) RevokeControl(ctx context.Context, ns, name string) (bool, error)
- func (s *Store) SeatInfo(ctx context.Context, ns, name string) (SeatInfo, error)
- func (s *Store) SeatOwner(ctx context.Context, ns, name string) (string, error)
- func (s *Store) SeatTier(ctx context.Context, ns, name string) (string, bool, error)
- func (s *Store) ServeHTTP(w http.ResponseWriter, r *http.Request, ns, name, action string)
- func (s *Store) SetNow(now func() time.Time)
Constants ¶
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 )
const ( // RoleObserver is a view-only participant. RoleObserver = "observer" // RoleController can inject input and is the single writer. RoleController = "controller" )
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 ¶
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") )
var ErrBusy = errors.New("interactive display in use")
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).
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) DispatchInput ¶ added in v0.6.0
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.
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 ¶
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
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) EnsureSeat ¶ added in v0.6.0
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.
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
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
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
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
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
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
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
SetConnected marks participant attachment state.
func (*Sessions) SetControlLocked ¶ added in v0.6.0
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
Status returns the live membership snapshot, or the zero Status plus nil when no session exists.
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
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) ClaimAgent ¶ added in v0.10.0
ClaimAgent distinguishes encoded premium fan-out from Selkies' Tier 1 seat.
func (*Store) ClaimCapture ¶ added in v0.6.0
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
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
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
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
ControlInUse reports whether the control lease is currently held (not fenced).
func (*Store) ControlOwner ¶ added in v0.6.0
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) LiveCaptureOwner ¶ added in v0.6.0
LiveCaptureOwner is CaptureOwner with the same liveness check as LiveSeatOwner.
func (*Store) LiveMembershipOwner ¶ added in v0.6.0
LiveMembershipOwner is MembershipOwner with the same liveness check as LiveSeatOwner.
func (*Store) LiveSeatOwner ¶ added in v0.6.0
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
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
Owner reports the replica identity this store writes onto claims.
func (*Store) Release ¶
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
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
ReleaseControl frees the control lease held by token. A stale token can never release a successor.
func (*Store) ReleaseMembership ¶ added in v0.6.0
ReleaseMembership frees the membership lease held by id. A stale id can never release a successor.
func (*Store) RenewCapture ¶ added in v0.6.0
RenewCapture keeps the capture lease alive, with the same fencing semantics as Renew.
func (*Store) RenewControl ¶ added in v0.6.0
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
RenewMembership keeps the membership lease alive, with the same fencing semantics as Renew.
func (*Store) Revoke ¶
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
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) SeatOwner ¶ added in v0.6.0
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
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.