# Idlewatcher Manages container lifecycle based on idle timeout, automatically stopping/pausing containers and waking them on request. ## Overview The `internal/idlewatcher` package implements idle-based container lifecycle management for GoDoxy. When a container is idle for a configured duration, it can be automatically stopped, paused, or killed. When a request arrives, the container is woken up automatically. ### Primary Consumers - **Route layer**: Routes with idlewatcher config integrate with this package to manage container lifecycle - **HTTP handlers**: Serve loading pages and SSE events during wake-up - **Stream handlers**: Handle stream connections with idle detection ### Non-goals - Does not implement container runtime operations directly (delegates to providers) - Does not manage container dependencies beyond wake ordering - Does not provide health checking (delegates to `internal/health/monitor`) ### Stability Internal package with stable public API. Changes to exported types require backward compatibility. ## Public API ### Exported Types ```go // Watcher manages lifecycle of a single container type Watcher struct { // Embedded route helper for proxy/stream/health routeHelper cfg *types.IdlewatcherConfig // Thread-safe state containers provider synk.Value[idlewatcher.Provider] state synk.Value[*containerState] lastReset synk.Value[time.Time] // Timers and channels idleTicker *time.Ticker healthTicker *time.Ticker readyNotifyCh chan struct{} // SSE event broadcasting (HTTP routes only) eventChs *xsync.Map[chan *WakeEvent, struct{}] eventHistory []WakeEvent } ``` ```go // WakeEvent is broadcast via SSE during wake-up type WakeEvent struct { Type WakeEventType Message string Timestamp time.Time Error string } ``` ### Exported Functions/Methods ```go // NewWatcher creates or reuses a watcher for the given route and config func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig) (*Watcher, error) // Wake wakes the container, blocking until ready func (w *Watcher) Wake(ctx context.Context) error // Start begins the idle watcher loop func (w *Watcher) Start(parent task.Parent) gperr.Error // ServeHTTP serves the loading page and SSE events func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) // ListenAndServe handles stream connections with idle detection func (w *Watcher) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) // Key returns the unique key for this watcher func (w *Watcher) Key() string ``` ### Package-level Variables ```go var ( // watcherMap is a global registry keyed by config.Key() watcherMap map[string]*Watcher watcherMapMu sync.RWMutex // singleFlight prevents duplicate wake calls for the same container singleFlight singleflight.Group ) ``` ## Architecture ### Core Components ```mermaid classDiagram class Watcher { +Wake(ctx) error +Start(parent) gperr.Error +ServeHTTP(ResponseWriter, *Request) +ListenAndServe(ctx, preDial, onRead) +Key() string } class containerState { status ContainerStatus ready bool err error startedAt time.Time healthTries int } class idlewatcher.Provider { <> +ContainerPause(ctx) error +ContainerStart(ctx) error +ContainerStop(ctx, signal, timeout) error +ContainerStatus(ctx) (ContainerStatus, error) +Watch(ctx) (eventCh, errCh) } Watcher --> containerState : manages Watcher --> idlewatcher.Provider : uses ``` ### Component Interactions ```mermaid flowchart TD A[HTTP Request] --> B{Container Ready?} B -->|Yes| C[Proxy Request] B -->|No| D[Wake Container] D --> E[SingleFlight Check] E --> F[Wake Dependencies] F --> G[Start Container] G --> H[Health Check] H -->|Pass| I[Notify Ready] I --> J[SSE Event] J --> K[Loading Page] K --> L[Retry Request] ``` ### State Machine ```mermaid stateDiagram-v2 [*] --> Napping: Container stopped/paused Napping --> Starting: Wake() called Starting --> Ready: Health check passes Starting --> Error: Health check fails / timeout Ready --> Napping: Idle timeout Ready --> Napping: Manual stop Error --> Starting: Retry wake Error --> Napping: Container stopped externally ``` ## Configuration Surface Configuration is defined in `types.IdlewatcherConfig`: ```go type IdlewatcherConfig struct { IdlewatcherConfigBase Docker *types.DockerProviderConfig // Exactly one required Proxmox *types.ProxmoxProviderConfig // Exactly one required } type IdlewatcherConfigBase struct { IdleTimeout time.Duration // Duration before container is stopped StopMethod types.ContainerMethod // pause, stop, or kill StopSignal types.ContainerSignal // Signal to send StopTimeout int // Timeout in seconds WakeTimeout time.Duration // Max time to wait for wake DependsOn []string // Container dependencies StartEndpoint string // Optional path restriction NoLoadingPage bool // Skip loading page } ``` ### Docker Labels ```yaml labels: proxy.idle_timeout: 5m proxy.idle_stop_method: stop proxy.idle_depends_on: database:redis ``` ### Path Constants ```go const ( LoadingPagePath = "/$godoxy/loading" WakeEventsPath = "/$godoxy/wake-events" ) ``` ## Dependency and Integration Map | Dependency | Purpose | | -------------------------------- | --------------------------- | | `internal/health/monitor` | Health checking during wake | | `internal/route/routes` | Route registry lookup | | `internal/docker` | Docker client connection | | `internal/proxmox` | Proxmox LXC management | | `internal/watcher/events` | Container event watching | | `pkg/gperr` | Error handling | | `xsync/v4` | Concurrent maps | | `golang.org/x/sync/singleflight` | Duplicate wake suppression | ## Observability ### Logs - **INFO**: Wake start, container started, ready notification - **DEBUG**: State transitions, health check details - **ERROR**: Wake failures, health check errors Log context includes: `alias`, `key`, `provider`, `method` ### Metrics No metrics exposed directly; health check metrics available via `internal/health/monitor`. ## Security Considerations - Loading page and SSE endpoints are mounted under `/$godoxy/` path - No authentication on loading page; assumes internal network trust - SSE event history may contain container names (visible to connected clients) ## Failure Modes and Recovery | Failure | Behavior | Recovery | | ----------------------------- | -------------------------------------------------- | ------------------------------ | | Wake timeout | Returns error, container remains in current state | Retry wake with longer timeout | | Health check fails repeatedly | Container marked as error, retries on next request | External fix required | | Provider connection lost | SSE disconnects, next request retries wake | Reconnect on next request | | Dependencies fail to start | Wake fails with dependency error | Fix dependency container | ## Usage Examples ### Basic HTTP Route with Idlewatcher ```go route := &route.Route{ Alias: "myapp", Idlewatcher: &types.IdlewatcherConfig{ IdlewatcherConfigBase: types.IdlewatcherConfigBase{ IdleTimeout: 5 * time.Minute, StopMethod: types.ContainerMethodStop, StopTimeout: 30, }, Docker: &types.DockerProviderConfig{ ContainerID: "abc123", }, }, } w, err := idlewatcher.NewWatcher(parent, route, route.Idlewatcher) if err != nil { return err } return w.Start(parent) ``` ### Watching Wake Events ```go // Events are automatically served at /$godoxy/wake-events // Client connects via EventSource: const eventSource = new EventSource("/$godoxy/wake-events"); eventSource.onmessage = (e) => { const event = JSON.parse(e.data); console.log(`Wake event: ${event.type}`, event.message); }; ``` ## Testing Notes - Unit tests cover state machine transitions - Integration tests with Docker daemon for provider operations - Mock provider for testing wake flow without real containers