proxy

package
v0.10.17 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

View Source
const DeployingPageSentinel = "Deploying app"

DeployingPageSentinel is the deployingPage counterpart of LoadingPageSentinel: always present in the deploying wait page and in neither of the other miss pages.

View Source
const LoadingPageSentinel = "Starting app"

LoadingPageSentinel is a string that is always present in the loadingPage HTML body. Exported so tests can assert that a response is the loading page without comparing the full HTML; if the copy changes and the sentinel is removed, the build test fails rather than vacuously passing.

View Source
const MsgPoolSaturated = "Service temporarily at capacity, please retry."

MsgPoolSaturated is the plain-text body returned with a 503 when every replica is at its session cap and the request carries no sticky cookie. Exported so docs/scaling.md can be guarded against drift from this string.

View Source
const PoolSyncInterval = 1500 * time.Millisecond

PoolSyncInterval is the default tick rate at which the pool syncer reconciles the proxy's backend pools against the DB replica table.

View Source
const ReplicaSessionStaleCutoff = 3 * ReporterInterval

ReplicaSessionStaleCutoff is the age at which a row in replica_sessions is considered stale (from a crashed or permanently-disconnected instance). Callers pass `now - ReplicaSessionStaleCutoff` as the cutoffEpoch to AppFleetLoad and ReapStaleReplicaSessions.

The window is intentionally conservative: a stale row can only delay a scale-down or hibernation decision (false "still active"), never wrongly trigger one. Three ReporterIntervals ensures a live instance that misses a single tick (slow DB write, GC pause) is not evicted.

View Source
const ReporterInterval = 5 * time.Second

ReporterInterval is how often the SessionReporter pushes replica session counts and last-activity to the replica_sessions table. Every instance refreshes its rows on each tick so their updated_at stays above the stale cutoff as long as the instance is alive.

Three ticks without a refresh = stale (ReplicaSessionStaleCutoff below). Keeping the interval short (a few seconds) means the fleet view is near-real-time while the stale window remains a comfortable multiple of the interval so a transient DB hiccup does not immediately evict a live instance.

Variables

This section is empty.

Functions

func BuildAppLimiter added in v0.10.16

func BuildAppLimiter(renderSeconds, cores, headroom, principalBurst float64, divisor, lruCapacity int) *admission.AppLimiter

BuildAppLimiter constructs the per-app AppLimiter from an app's render cost and the host sizing knobs, or returns nil when render_seconds is not positive, which means pacing is disabled for the app and no limiter should exist. The shared bucket is sized R = (cores * headroom) / render_seconds with burst = round(cores); per-principal buckets get R/divisor with principalBurst.

Exported (capital B) because cmd/shinyhub/main.go, in package main, calls it at startup; an unexported helper is not reachable across packages.

Types

type AccessLogEntry added in v0.2.3

type AccessLogEntry struct {
	Slug         string
	Method       string
	Path         string
	Status       int
	Bytes        int64
	Duration     time.Duration
	ClientIP     string // trusted-proxy-aware client IP (see SetClientIPResolver)
	Peer         string // raw r.RemoteAddr (direct TCP peer)
	ReplicaIndex int    // -1 when no replica served the request
	Sticky       bool
	// Reject is the platform rejection reason for this request, set only for
	// rejections emitted on the main ServeHTTP path (pool-saturated,
	// pool-degraded, unknown-slug). Empty for routed requests and for
	// readiness-probe rejections (which bypass this access-log path).
	Reject RejectReason
}

AccessLogEntry describes a single proxied request. It is passed to the callback registered via SetAccessLogger so callers can emit structured logs, metrics, or audit records without the proxy package depending on a particular logging library.

For routed requests (a replica handled the request) ReplicaIndex is the zero-based pool index and Sticky reports whether the sticky-session cookie selected the replica. For loading-page responses (no live replica) ReplicaIndex is -1.

type ElasticPoolSnapshot added in v0.10.6

type ElasticPoolSnapshot struct {
	Mode              string // "grouped" or "per_session"
	SessionsPerWorker int    // per-worker admission cap (grouped_size; always 1 for per_session)
	MaxWorkers        int
	Workers           []ElasticWorkerStatus // sorted by SlotID
}

ElasticPoolSnapshot is a point-in-time capacity view of an elastic pool: the admission ceiling is MaxWorkers x SessionsPerWorker.

type ElasticWorkerStatus added in v0.10.6

type ElasticWorkerStatus struct {
	SlotID       int    `json:"slot_id"`
	Status       string `json:"status"` // "booting", "running", or "draining"
	Sessions     int    `json:"sessions"`
	ActiveConns  int64  `json:"active_conns"`
	DeploymentID int64  `json:"deployment_id,omitempty"`
}

ElasticWorkerStatus is one worker's live routing state in an elastic pool, as maintained by the admission path. Sessions counts bound clients (assignedClients), which is what the grouped_size cap admits against; ActiveConns counts connections open right now.

type FleetSignal added in v0.8.1

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

FleetSignal is an autoscale.Signal adapter that overrides ReplicaSessionCounts to return the fleet-wide sum of active sessions from the replica_sessions table. All other Signal methods are delegated to the underlying *Proxy unchanged.

FleetSignal is only wired in clustered deployments (Postgres DSN). Single-node deployments pass the *Proxy directly to the autoscaler so the local in-memory count is used exactly as before.

func NewFleetSignal added in v0.8.1

func NewFleetSignal(prx *Proxy, store fleetStore, log *slog.Logger) *FleetSignal

NewFleetSignal constructs a FleetSignal backed by prx for slug->appID resolution and store for fleet-load aggregation. log may be nil, in which case the default logger is used.

func (*FleetSignal) RejectsByReason added in v0.8.1

func (f *FleetSignal) RejectsByReason(slug string, d time.Duration) map[RejectReason]uint64

RejectsByReason delegates to the underlying proxy unchanged. The rejection rollup is per-instance and does not need fleet aggregation for autoscale decisions: a single instance seeing pool-saturated rejects is sufficient evidence to scale up.

func (*FleetSignal) ReplicaSessionCounts added in v0.8.1

func (f *FleetSignal) ReplicaSessionCounts(slug string) []int64

ReplicaSessionCounts satisfies the autoscale.Signal interface by returning the fleet-wide count. The autoscaler always calls this method; the name matches the interface so FleetSignal can be used wherever *Proxy is used today as a Signal.

Callers that need the exact local in-memory count (UI/app-detail API, admission, scale-drain) use *Proxy.ReplicaSessionCounts directly; the FleetSignal adapter is only passed to the autoscaler.

type PoolSessionStat added in v0.9.1

type PoolSessionStat struct {
	Sessions int
	Cap      int
	// Replicas counts slots that admit new sessions: non-nil and not draining.
	// A draining replica (scale-down in progress) still holds its existing
	// sessions - which are counted in Sessions - but the picker routes no new
	// session to it, so it must not inflate the admission ceiling.
	Replicas int
}

PoolSessionStat is a per-pool session snapshot for the metrics collector: total active sessions across all live replicas, the per-replica admission cap (0 = unlimited), and the number of replicas available for NEW admission. The current admission ceiling is Cap*Replicas when Cap > 0.

type PoolSyncer added in v0.8.1

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

PoolSyncer reconciles the proxy's backend pools against the authoritative DB replica table. It runs on every control-plane instance in a clustered deployment so standbys can serve off-host apps without relying on a local placement registry.

Sync is diff-based: a slot whose endpoint_url and deployment_id have not changed since the last sync is left completely untouched, which preserves the wsReady cache and avoids flapping readiness on every tick.

func NewPoolSyncer added in v0.8.1

func NewPoolSyncer(prx *Proxy, store RoutableSource, transport TransportBuilder, log *slog.Logger, identityGlobal bool) *PoolSyncer

NewPoolSyncer constructs a syncer. interval is the reconcile tick period; pass 0 to use PoolSyncInterval. identityGlobal is the resolved global auth.identity_headers flag for this instance.

func (*PoolSyncer) Run added in v0.8.1

func (s *PoolSyncer) Run(ctx context.Context)

Run starts the reconcile loop and blocks until ctx is cancelled. Intended to be called in a dedicated goroutine.

func (*PoolSyncer) RunOnce added in v0.8.1

func (s *PoolSyncer) RunOnce(ctx context.Context)

RunOnce performs exactly one sync pass. Useful in tests and the initial sync at startup.

func (*PoolSyncer) SyncSlug added in v0.8.1

func (s *PoolSyncer) SyncSlug(_ context.Context, slug string)

SyncSlug performs a targeted, synchronous sync for a single slug. It calls ListRoutableReplicas (all replicas) and reconciles only the pool for the given slug. Used by the on-miss path so a freshly-started app is served before the next background tick.

This issues a full-table query and filters client-side, which avoids adding a second query variant. The miss path is low-frequency (first request after a cold-start or scale-up), so the extra scan cost is negligible. A per-slug query is a future optimisation at large fleet scale.

type Proxy

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

Proxy routes /app/:slug/* to the registered backend pool for that slug.

func New

func New() *Proxy

func (*Proxy) ActiveUpgradedConns added in v0.7.4

func (p *Proxy) ActiveUpgradedConns() int

ActiveUpgradedConns is the number of live hijacked (WebSocket) connections.

func (*Proxy) ApplyRenderPacing added in v0.10.16

func (p *Proxy) ApplyRenderPacing(slug string, renderSeconds float64)

ApplyRenderPacing installs, updates, or clears the render-admission limiter for slug to match renderSeconds. It is idempotent and safe from any goroutine: a repeated call with an unchanged value is a no-op that never resets the live token bucket. renderSeconds <= 0 clears pacing. With no factory set it is a no-op (pacing stays off). The entire compare-build-install runs in one appLimitersMu critical section, via setAppLimiterLocked rather than the re-locking public SetAppLimiter, so concurrent callers are serialized and the last writer's value is the one left installed (no stale limiter can be written after a newer one). BuildAppLimiter touches no proxy state and only allocates, so holding the lock across it is cheap.

func (*Proxy) BeginHibernate added in v0.2.2

func (p *Proxy) BeginHibernate(slug string, since time.Time) bool

BeginHibernate atomically removes slug from the routing table iff no activity has been recorded since `since` and no in-flight request is currently being proxied. On success it returns true and the caller is responsible for stopping the underlying processes. On failure (a request raced in or one is mid-flight) it returns false and the routing table is untouched, so the caller MUST NOT stop anything.

The two-signal check (lastSeen and per-replica activeConns) is what makes this safe to call from the hibernation watchdog. lastSeen catches any request that has finished its routing decision and reached RecordActivity while activeConns catches a request that has already picked a replica but has not yet completed.

func (*Proxy) ConnectivityHealth added in v0.9.5

func (p *Proxy) ConnectivityHealth(slug string) (everConnected, servingWithoutWS bool)

ConnectivityHealth reports the realtime-connection health for slug since its pool was last (re)registered:

  • everConnected is true once at least one WebSocket handshake has completed.
  • servingWithoutWS is true when the app has served real traffic for longer than wsWarnGrace but no WebSocket has ever connected - the signature of a reverse proxy blocking the WebSocket upgrade, which leaves the page rendering while every interaction fails.

The two are mutually exclusive; a never-served or still-within-grace app reports (false, false). The app-detail envelope combines this with the app's running state to surface an operator warning.

func (*Proxy) Deregister

func (p *Proxy) Deregister(slug string)

Deregister removes the entire pool for slug from the routing table. For elastic pools it dispatches the terminate callback (if set) for every worker in the pool and stops pending client release timers for the slug before dropping the map, so native or Docker processes are not orphaned on redeploy. Multiplex pools retain the previous behaviour (no callbacks).

func (*Proxy) DeregisterElasticWorker added in v0.9.2

func (p *Proxy) DeregisterElasticWorker(slug string, slotID int)

DeregisterElasticWorker removes a running elastic worker from the pool and cancels any client slots bound to it. It is the clean-up path for a successfully-started worker that is being intentionally terminated (see ElasticSpawner.Terminate). Idempotent: no-ops on unknown slugs or slotIDs. Caller must NOT hold p.mu.

func (*Proxy) DeregisterReplicaIfTarget added in v0.6.2

func (p *Proxy) DeregisterReplicaIfTarget(slug string, index int, expectURL string) bool

DeregisterReplicaIfTarget removes the replica at index only while its current target still equals expectURL, returning whether it removed the slot. A worker-loss pass uses it so it cannot pull a route that a concurrent redeploy already re-pointed at a healthy backend: the deploy path registers the new route before it persists the new replica row, so a loss pass reading the stale row must confirm the live route still belongs to the lost replica before deregistering it. Unknown pools, out-of-range indices, and empty slots are no-ops.

func (*Proxy) DrainReplica added in v0.7.0

func (p *Proxy) DrainReplica(slug string, index int) bool

DrainReplica marks the slot at index in slug's pool as draining and reports whether a live backend was marked. A draining slot is skipped by the least- connections picker (no new cookie-less sessions) while the sticky-cookie path still forwards, so established sessions finish before the slot is stopped. Returns false if the pool is absent, the index is out of range, or the slot holds no backend - the caller can then treat the scale-down as a no-op.

func (*Proxy) DrainUpgraded added in v0.7.4

func (p *Proxy) DrainUpgraded(timeout time.Duration) (forced int)

DrainUpgraded waits for all tracked upgraded connections to close on their own, up to timeout, then force-closes any that remain. It returns the number force-closed (0 means everything drained cleanly). It returns immediately when no upgraded connections are open. Callers are expected to have called SetDraining(true) and stopped accepting new connections before calling DrainUpgraded, so the tracked set only shrinks.

func (*Proxy) Draining added in v0.7.4

func (p *Proxy) Draining() bool

Draining reports whether this instance is draining for shutdown.

func (*Proxy) ElasticWorkerCount added in v0.9.2

func (p *Proxy) ElasticWorkerCount(slug string) int

ElasticWorkerCount returns the number of workers (in any state) currently tracked in the elastic pool for slug. Returns 0 for unknown slugs or non-elastic pools. Intended for tests to assert that termination/deregistration correctly empties the workers map (PoolHasAny always returns true for elastic pools because they route on demand; it cannot be used for this assertion).

func (*Proxy) ElasticWorkersSnapshot added in v0.10.6

func (p *Proxy) ElasticWorkersSnapshot(slug string) (ElasticPoolSnapshot, bool)

ElasticWorkersSnapshot returns the live capacity view of slug's elastic pool for status surfaces (API, CLI, UI). ok is false for multiplex or unknown pools, so callers can distinguish "no capacity view exists" from an elastic pool that currently has zero workers. Callers must NOT hold p.mu.

func (*Proxy) EnableImmediateFlush added in v0.8.1

func (p *Proxy) EnableImmediateFlush(ch chan string)

EnableImmediateFlush wires the channel that the session reporter reads to detect 0->active transitions. The channel must be buffered (capacity >= 1). Call once at startup before serving; never call it again. Passing a nil channel is a no-op (disables the feature, used by single-node paths).

func (*Proxy) ForgetRejects added in v0.6.1

func (p *Proxy) ForgetRejects(slug string)

ForgetRejects drops all rejection history for slug. Call only when the app is logically gone (post-delete), never on Deregister, which fires on every redeploy/restart/stop.

func (*Proxy) HasLiveReplica added in v0.2.1

func (p *Proxy) HasLiveReplica(slug string) bool

HasLiveReplica reports whether slug has at least one non-nil replica.

func (*Proxy) IsDraining added in v0.7.0

func (p *Proxy) IsDraining(slug string, index int) bool

IsDraining reports whether the slot at index in slug's pool is marked draining. Returns false for an absent pool, out-of-range index, or nil slot.

func (*Proxy) IsWSReady added in v0.3.3

func (p *Proxy) IsWSReady(slug string) bool

IsWSReady reports whether slug has observed a 101 Switching Protocols response since the last deregister/hibernate.

func (*Proxy) LastSeen

func (p *Proxy) LastSeen(slug string) time.Time

LastSeen returns the last time a request was successfully proxied for slug. Returns zero time if slug has never been proxied.

func (*Proxy) MarkSynced added in v0.8.1

func (p *Proxy) MarkSynced()

MarkSynced marks the proxy as having completed at least one pool synchronisation from the authoritative DB. On single-node deployments this is called at startup so /readyz is unchanged. On clustered deployments the pool syncer calls this after its first successful pass.

func (*Proxy) MarkWSReady added in v0.3.3

func (p *Proxy) MarkWSReady(slug string)

MarkWSReady records that slug has completed at least one WebSocket handshake since the last lifecycle reset. Idempotent. Normally called by the statusRecorder's onUpgrade hook when the reverse proxy emits 101 Switching Protocols, but exported so adapters that route WS traffic outside the standard reverse-proxy path can still feed the probe.

func (*Proxy) PoolCap added in v0.2.5

func (p *Proxy) PoolCap(slug string) int

PoolCap returns the per-replica session cap for slug, or 0 if the pool is not registered or the cap is disabled.

func (*Proxy) PoolHasAny added in v0.9.2

func (p *Proxy) PoolHasAny(slug string) bool

PoolHasAny reports whether slug currently has at least one routable backend (a registered replica for multiplex pools, or a running/booting elastic worker for elastic pools). It is the exported counterpart of poolRoutable used by lifecycle tests and external health checks.

func (*Proxy) PoolIdentityHeaders added in v0.8.6

func (p *Proxy) PoolIdentityHeaders(slug string) bool

PoolIdentityHeaders reports the current identityHeaders flag for slug. Returns false when the pool does not exist. Used in tests.

func (*Proxy) PoolSessionSnapshot added in v0.9.1

func (p *Proxy) PoolSessionSnapshot() map[string]PoolSessionStat

PoolSessionSnapshot returns a best-effort snapshot of session usage for every registered pool, keyed by slug. Taken under the read lock, but per-replica counts are loaded independently (not one global instant), matching ReplicaSessionCounts. Intended for the Prometheus session gauges.

func (*Proxy) RecordActivity

func (p *Proxy) RecordActivity(slug string)

RecordActivity marks slug as seen at the current time. It also records the first time real traffic was served (firstServedAt) and, once an app has been serving longer than wsWarnGrace without any WebSocket connecting, logs a one-shot ERROR: the realtime channel is not getting through, which usually means a reverse proxy is not forwarding the WebSocket upgrade.

func (*Proxy) Register

func (p *Proxy) Register(slug, targetURL string) error

Register is kept for single-replica callers. It is equivalent to SetPoolSize(slug, 1) + RegisterReplica(slug, 0, targetURL, nil, 0).

func (*Proxy) RegisterElasticWorker added in v0.9.2

func (p *Proxy) RegisterElasticWorker(slug string, slotID int, targetURL string, base http.RoundTripper, deploymentID int64) error

RegisterElasticWorker installs a ready backend into an elastic pool's worker map at slotID. It is called by the spawn callback (Task 12/13) once the native or Docker process is listening. The pool must already be elastic (SetPoolMode with grouped or per_session) and slotID must have been allocated by a prior reserveWorker call. The Director and transport setup mirrors RegisterReplica exactly so the forwarding behaviour is identical.

If a booting placeholder already exists for slotID (inserted by reserveWorker), it is updated in-place to preserve assignedClients; a brand-new entry is created only when the slot is absent (e.g. called out of order in tests).

func (*Proxy) RegisterReplica added in v0.2.1

func (p *Proxy) RegisterReplica(slug string, index int, targetURL string, base http.RoundTripper, deploymentID int64) error

RegisterReplica registers a backend URL at the given index within slug's pool. base is the HTTP transport used for outbound requests; nil uses the tuned defaultBackendTransport. Remote tunnel URLs may carry a path prefix (e.g. /v1/data/<token>) that is prepended to every forwarded app-relative path. deploymentID is stamped into the sticky cookie so a stale cookie from a previous deployment causes a re-pick rather than pinning the client to a potentially wrong replica. Returns an error if the pool size has not been set or the index is out of range.

func (*Proxy) RegisteredSlugs added in v0.8.1

func (p *Proxy) RegisteredSlugs() map[string]struct{}

RegisteredSlugs returns the set of slugs that currently have a pool entry. Used by the pool syncer to deregister slugs that have no routable replicas in the DB on a given sync pass.

func (*Proxy) RejectsByReason added in v0.6.1

func (p *Proxy) RejectsByReason(slug string, d time.Duration) map[RejectReason]uint64

RejectsByReason returns the per-reason rejection counts recorded for slug over roughly the last d. Reasons with no rejections in the window are omitted; a slug with none returns nil.

func (*Proxy) ReleaseReservation added in v0.9.2

func (p *Proxy) ReleaseReservation(slug string, slotID int)

ReleaseReservation removes a booting slot that failed to spawn. It also cancels and removes any client slots already bound to this slotID (a client that pre-bound during the boot window must not be left dangling). Called by the spawn callback (Task 12) when a worker fails to start or pass health checks. Caller must NOT hold p.mu.

func (*Proxy) ReplicaDeploymentID added in v0.8.1

func (p *Proxy) ReplicaDeploymentID(slug string, index int) int64

ReplicaDeploymentID returns the deployment ID stamped into the replica at index for slug, or 0 if the slot is unset or the pool does not exist. Used by the pool syncer to diff the current state against the DB row so an unchanged slot is not re-registered (which would clear wsReady).

func (*Proxy) ReplicaSessionCounts added in v0.2.5

func (p *Proxy) ReplicaSessionCounts(slug string) []int64

ReplicaSessionCounts returns a snapshot of the active connection count for each replica slot in slug's pool, indexed by replica index. Slots with a nil backend return -1. The returned slice length equals the current pool size; returns nil if the pool is not registered.

Intended for the metrics endpoint; callers should treat the result as a best-effort sample, not a synchronised read (each entry is loaded independently).

func (*Proxy) ReplicaTargetURL added in v0.6.1

func (p *Proxy) ReplicaTargetURL(slug string, index int) string

ReplicaTargetURL returns the target URL registered for slug at index, or an empty string if the slot is unset or the pool does not exist. Useful for observability and test assertions.

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles /app/:slug/* requests. When the slug has no live replica, the loading page is served and the wake trigger is invoked in a goroutine. Routing uses a sticky session cookie (shinyhub_rep_<slug>) pinned to a specific replica index. On a cache miss or stale cookie, least-connections with round-robin tie-breaking selects the replica and a new cookie is set.

func (*Proxy) SetAccessLogger added in v0.2.3

func (p *Proxy) SetAccessLogger(fn func(AccessLogEntry))

SetAccessLogger registers a callback invoked once per proxied request (including responses served from the loading page). Pass nil to disable. Safe to call concurrently with ServeHTTP; subsequent requests observe the new value atomically.

func (*Proxy) SetAppAccessLookup added in v0.10.16

func (p *Proxy) SetAppAccessLookup(fn func(slug string) string)

SetAppAccessLookup wires (or clears, with nil) the app-access-mode lookup used by per-principal fairness to choose a principal key. Atomic.

func (*Proxy) SetAppLimiter added in v0.10.16

func (p *Proxy) SetAppLimiter(slug string, l *admission.AppLimiter)

SetAppLimiter installs (or, with nil, clears) the render-admission limiter for slug. A nil limiter means pacing is disabled for the app. Safe to call concurrently with ServeHTTP.

func (*Proxy) SetAppReadyFunc added in v0.8.1

func (p *Proxy) SetAppReadyFunc(fn func(slug string) bool)

SetAppReadyFunc wires an injected predicate that serveReadyProbe uses instead of IsWSReady when determining whether a slug is ready. When fn is nil (the default), serveReadyProbe reverts to IsWSReady. Called at most once at startup; reads on the hot path are lock-free via atomic.Pointer.

func (*Proxy) SetAppStatusLookup added in v0.8.20

func (p *Proxy) SetAppStatusLookup(fn func(slug string) (status, reason string))

SetAppStatusLookup registers a callback that reports an app's lifecycle status and (for a crashed app) its failure reason. It lets a no-backend miss for a crashed/stopped app render a clear status page instead of the loading spinner. Called once at startup; leaving it unset preserves the loading-page behaviour.

func (*Proxy) SetCPUWatermark added in v0.10.16

func (p *Proxy) SetCPUWatermark(w *admission.Watermark)

SetCPUWatermark wires (or clears, with nil) the host CPU watermark used by the render-aware admission path. Atomic, safe to call concurrently with ServeHTTP.

func (*Proxy) SetClientIPResolver added in v0.2.3

func (p *Proxy) SetClientIPResolver(fn func(*http.Request) string)

func (*Proxy) SetDraining added in v0.7.4

func (p *Proxy) SetDraining(v bool)

SetDraining marks (or unmarks) this instance as draining for shutdown. While draining, /readyz reports unready so a load balancer stops routing new requests; existing hijacked connections keep flowing.

func (*Proxy) SetIdentityProvider added in v0.8.6

func (p *Proxy) SetIdentityProvider(fn identityProviderFn)

SetIdentityProvider wires the identity payload assembler. Call once at startup before serving; a nil provider disables injection (headers are still stripped).

func (*Proxy) SetMemoryGuard added in v0.10.1

func (p *Proxy) SetMemoryGuard(minAvailableMB int, probe func() (int, bool))

SetMemoryGuard arms (or, with a non-positive floor or nil probe, disarms) the host-memory admission floor for elastic pools. While probe reports less than minAvailableMB of available host memory, requests that would allocate a NEW worker are shed with 503; sessions already bound to a worker are unaffected. Shedding one incoming session is deliberate: the alternative is the kernel OOM-killing a live worker together with every session on it.

func (*Proxy) SetOnMissSync added in v0.8.1

func (p *Proxy) SetOnMissSync(fn func(slug string))

SetOnMissSync registers a function called synchronously when a request arrives for a slug with no live pool, before the loading page is served. In clustered mode the pool syncer wires this to SyncSlug so a freshly- active app becomes routable on the very first request rather than after the next background tick. When fn is nil (the default, and always on single-node) the miss path is byte-for-byte unchanged. Called at most once at startup.

func (*Proxy) SetPoolAppID added in v0.8.1

func (p *Proxy) SetPoolAppID(slug string, appID int64)

SetPoolAppID records the numeric database primary key for the app that owns slug's pool. The session reporter uses this to write replica_sessions rows keyed by app_id without a DB lookup at snapshot time. Call this alongside SetPoolSize whenever the app's ID is known. Creates the pool (size 1) if it does not yet exist. A zero appID is ignored so callers that do not know the ID (e.g. single-node paths that never use the reporter) are safe to omit it.

func (*Proxy) SetPoolCap added in v0.2.5

func (p *Proxy) SetPoolCap(slug string, max int)

SetPoolCap sets the per-replica active-session cap for slug. Once every non-nil replica reaches this count, new requests without a valid sticky cookie are shed with 503 Retry-After. A value of 0 means unlimited. Creates the pool (size 1) if it does not yet exist so callers can configure the cap before spawning replicas.

func (*Proxy) SetPoolIdentityHeaders added in v0.8.6

func (p *Proxy) SetPoolIdentityHeaders(slug string, enabled bool)

SetPoolIdentityHeaders sets the per-pool identity-forwarding flag (the effective value, post global-config resolution). Creates the pool (size 1) if absent so callers can configure it before spawning replicas, matching SetPoolCap.

func (*Proxy) SetPoolMode added in v0.9.2

func (p *Proxy) SetPoolMode(slug string, mode config.WorkerIsolationMode, groupedSize, maxWorkers int)

SetPoolMode sets the worker-isolation mode and elastic sizing parameters for slug's pool. When mode is grouped or per_session the pool switches to demand-driven routing via the workers map; when mode is multiplex (or the empty string) the pool reverts to the dense replicas slice. Mirrors SetPoolCap's locking pattern. Creates the pool (size 1) if absent.

func (*Proxy) SetPoolSize added in v0.2.1

func (p *Proxy) SetPoolSize(slug string, size int)

SetPoolSize initialises or resizes the replica pool for slug. It is idempotent: growing preserves existing replicas; shrinking drops trailing slots. Callers must invoke this before RegisterReplica. The per-replica session cap is preserved across resizes; set it separately via SetPoolCap.

func (*Proxy) SetRejectRecorder added in v0.6.1

func (p *Proxy) SetRejectRecorder(rec RejectRecorder)

SetRejectRecorder wires (or clears, with nil) the admission-reject sink. Instance-level and atomic, matching SetAccessLogger/SetClientIPResolver. Safe to call concurrently with ServeHTTP.

func (*Proxy) SetRenderLimiterFactory added in v0.10.16

func (p *Proxy) SetRenderLimiterFactory(f func(renderSeconds float64) *admission.AppLimiter)

SetRenderLimiterFactory installs the factory ApplyRenderPacing uses to build a per-app limiter from a render_seconds value, capturing the host sizing (cores, headroom, divisor, LRU) once at startup so Detect is never called on a request or reconcile path. Call once before serving.

func (*Proxy) SetRenderParkBudget added in v0.10.16

func (p *Proxy) SetRenderParkBudget(perApp, total int)

SetRenderParkBudget installs the per-app and host-wide ceilings on parked render-paced upgrades. A non-positive ceiling means that dimension is unlimited. Safe to call at startup before serving.

func (*Proxy) SetRenderParkTTL added in v0.10.16

func (p *Proxy) SetRenderParkTTL(d time.Duration)

SetRenderParkTTL sets how long a render-paced upgrade parks before it is shed. Safe to call at startup before serving.

func (*Proxy) SetSlugExists added in v0.3.0

func (p *Proxy) SetSlugExists(fn func(string) (bool, error))

SetSlugExists registers a synchronous predicate that the proxy uses to distinguish a known-but-not-running slug (serve loading page) from a completely unknown slug (return 404). The predicate returns (exists, lookupErr); a non-nil err signals the lookup itself failed (DB unavailable, context cancelled, etc.) — the proxy must not interpret this as "slug missing" and 404 the user. When unset, the proxy falls back to always serving the loading page on miss — matching the legacy behaviour from before the predicate was wired up.

func (*Proxy) SetSpawnFunc added in v0.9.2

func (p *Proxy) SetSpawnFunc(fn func(slug string, slotID int))

SetSpawnFunc registers the callback invoked (via a goroutine) when the elastic routing decides to allocate a new worker slot (decisionAllocate). The callback is responsible for starting the worker process and subsequently calling RegisterElasticWorker once the backend is ready to serve. Tasks 12/13 wire the real implementation; leaving it unset disables demand-spawn (useful in tests that only exercise accounting).

func (*Proxy) SetStickySecret added in v0.7.2

func (p *Proxy) SetStickySecret(key []byte)

SetStickySecret enables HMAC signing of the sticky-routing cookie with the given key (derive it from the server auth secret). Wire it at startup; when unset the cookie carries a bare index and deployment ID without a signature.

func (*Proxy) SetTerminateFunc added in v0.9.2

func (p *Proxy) SetTerminateFunc(fn func(slug string, slotID int))

SetTerminateFunc registers the callback invoked (via a goroutine, never inline under p.mu) when an elastic worker's assignedClients count drops to zero after the grace window expires. Typical use: Task 12's boot-timeout handler and the idle-worker reaper.

func (*Proxy) SetTracing added in v0.4.1

func (p *Proxy) SetTracing(cfg config.TracingConfig, buf *tracing.Buffer)

SetTracing wires the tracing configuration and shared ring buffer into the proxy. Must be called once at startup before traffic arrives. Passing a nil buffer disables span recording but still propagates traceparent when cfg.Enabled — useful for testing or for deployments that want apps to trace without surfacing anything in the UI.

func (*Proxy) SetTrustedProxies added in v0.7.2

func (p *Proxy) SetTrustedProxies(nets []*net.IPNet)

SetTrustedProxies configures the upstream-proxy CIDRs whose forwarding headers are trusted (see Proxy.trustedProxies). Wire it from cfg.TrustedProxyNets at startup, before serving. Safe to leave unset (trust no peer) for a directly-exposed deployment.

func (*Proxy) SetWakeHoldTimeout added in v0.8.22

func (p *Proxy) SetWakeHoldTimeout(d time.Duration)

SetWakeHoldTimeout sets how long a request is held during a wake before the loading page is served. 0 disables the hold (the loading page is served immediately, the pre-hold behaviour). Called once at startup.

func (*Proxy) SetWakeTrigger added in v0.8.1

func (p *Proxy) SetWakeTrigger(fn func(string))

SetWakeTrigger registers a callback invoked (in a goroutine) when a request arrives for a slug with no registered backend, or when a forward error occurs in clustered mode. The callback issues the BeginWake CAS and, if this instance is the active owner, drives the wake inline. Called at startup on EVERY instance (not owner-gated) so a standby can arm the DB waking transition even though only the active executes the deploy.

func (*Proxy) SyncedOnce added in v0.8.1

func (p *Proxy) SyncedOnce() bool

SyncedOnce reports whether MarkSynced has been called at least once.

func (*Proxy) UndrainReplica added in v0.7.0

func (p *Proxy) UndrainReplica(slug string, index int) bool

UndrainReplica clears the drain flag on the slot at index in slug's pool, returning it to the least-connections rotation, and reports whether a live backend was unmarked. It is the rollback for an aborted scale-down: when the stop fails, the still-running replica must resume serving new cookie-less sessions instead of being left permanently half-drained. Returns false for an absent pool, out-of-range index, or nil slot.

type RejectReason added in v0.6.1

type RejectReason string

RejectReason is the closed vocabulary stamped onto platform-emitted data-plane rejections. It is the single source of truth shared by the X-Shinyhub-Reject response header, the in-memory rolling rollup (rejectCounter), and the Prometheus admission-rejects counter.

const (
	// ReasonUnknownSlug: no app with this slug is registered on this server (404).
	ReasonUnknownSlug RejectReason = "unknown-slug"
	// ReasonPoolSaturated: all configured replicas are live and at their
	// per-replica session cap (503). Remedy: raise --max-sessions-per-replica
	// and/or --replicas.
	ReasonPoolSaturated RejectReason = "pool-saturated"
	// ReasonPoolDegraded: fewer replicas are registered than configured and the
	// survivors are at cap (503). Remedy: check replica health before adding
	// capacity.
	ReasonPoolDegraded RejectReason = "pool-degraded"
	// ReasonAppNotReady: a known (or not-confidently-unknown) app has no replica
	// that has completed a WebSocket handshake yet (503, readiness probe).
	ReasonAppNotReady RejectReason = "app-not-ready"
	// ReasonMemoryPressure: the host is below the configured available-memory
	// floor (server.min_available_memory_mb), so no new elastic worker may be
	// allocated (503). Existing sessions keep routing. Deliberately distinct
	// from pool-saturated so capacity automation does not scale up in response.
	// Remedy: free host memory, lower per-app ceilings, or add hardware.
	ReasonMemoryPressure RejectReason = "memory-pressure"
	// ReasonRenderPaced: the app's render-admission token bucket is empty, so a
	// new session is briefly deferred. Transient, self-clearing in under a
	// second. Deliberately distinct from pool-saturated so capacity automation
	// does not read it as a scale-up signal: adding a worker does not add cores.
	ReasonRenderPaced RejectReason = "render-paced"
	// ReasonCPUSaturation: the host CPU watermark is breached, so a new session
	// is shed to protect the sessions already connected. Also distinct from
	// pool-saturated for the same reason.
	ReasonCPUSaturation RejectReason = "cpu-saturation"
)

type RejectRecorder added in v0.6.1

type RejectRecorder interface {
	RecordReject(slug, reason string)
}

RejectRecorder is an optional sink for admission-reject events, satisfied by the metrics registry. Defined here (not imported) so the proxy keeps zero Prometheus dependencies and stays unit-testable.

type RoutableSource added in v0.8.1

type RoutableSource interface {
	ListRoutableReplicas() ([]db.RoutableReplica, error)
}

RoutableSource is the minimal DB interface the pool syncer requires to list all routable replicas across all apps. *db.Store satisfies it.

type SessionReporter added in v0.8.1

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

SessionReporter periodically snapshots the local proxy's per-(slug, replica) active connection counts and last-activity timestamps and upserts them into the replica_sessions table so every other instance can include this instance's load in fleet-wide aggregation queries.

In addition to the periodic tick it listens on the proxy's immediateFlush channel: when any app's local active count rises from 0 to >0 (a session just admitted on a previously-idle app), the reporter flushes that app's row immediately so the fleet view is updated within milliseconds, not up to one full reporterInterval later.

SessionReporter must only be started in clustered deployments (Postgres DSN). Single-node deployments must never start it; they write no replica_sessions rows at all.

func NewSessionReporter added in v0.8.1

func NewSessionReporter(prx *Proxy, store sessionStore, instanceID string, flushCh chan string) *SessionReporter

NewSessionReporter creates a SessionReporter. flushCh is the channel that the proxy signals on 0->active transitions; it must be the same channel passed to prx.EnableImmediateFlush. The channel must be buffered.

func (*SessionReporter) Run added in v0.8.1

func (r *SessionReporter) Run(ctx context.Context)

Run starts the reporter loop. It blocks until ctx is cancelled and returns when cleanup is done. Callers should run it in a goroutine tracked by a WaitGroup so shutdown can wait for the final flush.

type TransportBuilder added in v0.8.1

type TransportBuilder interface {
	TransportForReplica(r *db.Replica) (http.RoundTripper, error)
}

TransportBuilder derives the per-replica HTTP transport from a DB row. *worker.ReplicaTransportBuilder satisfies it.

Jump to

Keyboard shortcuts

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