# Idlewatcher Idlewatcher manages container lifecycle based on idle timeout. When a container is idle for a configured duration, it can be automatically stopped, paused, or killed. When a request comes in, the container is woken up automatically. ## Architecture Overview ```mermaid graph TB subgraph Request Flow HTTP[HTTP Request] -->|Intercept| W[Watcher] Stream[Stream Request] -->|Intercept| W end subgraph Wake Process W -->|Wake| Wake[Wake Container] Wake -->|Check Status| State[Container State] Wake -->|Wait Ready| Health[Health Check] Wake -->|Events| SSE[SSE Events] end subgraph Idle Management Timer[Idle Timer] -->|Timeout| Stop[Stop Container] State -->|Running| Timer State -->|Stopped| Timer end subgraph Providers Docker[DockerProvider] --> DockerAPI[Docker API] Proxmox[ProxmoxProvider] --> ProxmoxAPI[Proxmox API] end W -->|Uses| Providers ``` ## Directory Structure ``` idlewatcher/ ├── cmd # Command execution utilities ├── debug.go # Debug utilities for watcher inspection ├── errors.go # Error types and conversion ├── events.go # Wake event types and broadcasting ├── handle_http.go # HTTP request handling and loading page ├── handle_http_debug.go # Debug HTTP handler (dev only) ├── handle_stream.go # Stream connection handling ├── health.go # Health monitoring interface ├── loading_page.go # Loading page HTML/CSS/JS templates ├── state.go # Container state management ├── watcher.go # Core Watcher implementation ├── provider/ # Container provider implementations │ ├── docker.go # Docker container management │ └── proxmox.go # Proxmox LXC management ├── types/ │ └── provider.go # Provider interface definition └── html/ ├── loading_page.html # Loading page template ├── style.css # Loading page styles └── loading.js # Loading page JavaScript ``` ## Core Components ### Watcher The main component that manages a single container's lifecycle: ```mermaid classDiagram class Watcher { +string Key() string +Wake(ctx context.Context) error +Start(parent task.Parent) gperr.Error +ServeHTTP(rw ResponseWriter, r *Request) +ListenAndServe(ctx context.Context, predial, onRead HookFunc) -idleTicker: *time.Ticker -healthTicker: *time.Ticker -state: synk.Value~*containerState~ -provider: synk.Value~Provider~ -dependsOn: []*dependency } class containerState { +status: ContainerStatus +ready: bool +err: error +startedAt: time.Time +healthTries: int } class dependency { +*Watcher +waitHealthy: bool } Watcher --> containerState : manages Watcher --> dependency : depends on ``` ### Provider Interface Abstraction for different container backends: ```mermaid classDiagram class Provider { <> +ContainerPause(ctx) error +ContainerUnpause(ctx) error +ContainerStart(ctx) error +ContainerStop(ctx, signal, timeout) error +ContainerKill(ctx, signal) error +ContainerStatus(ctx) (ContainerStatus, error) +Watch(ctx) (eventCh, errCh) +Close() } class DockerProvider { +client: *docker.SharedClient +watcher: watcher.DockerWatcher +containerID: string } class ProxmoxProvider { +*proxmox.Node +vmid: int +lxcName: string +running: bool } Provider <|-- DockerProvider Provider <|-- ProxmoxProvider ``` ### Container Status ```mermaid stateDiagram-v2 [*] --> Napping: Container stopped/paused Napping --> Waking: Wake request Waking --> Running: Container started Running --> Starting: Container is running but not healthy Starting --> Ready: Health check passes Ready --> Napping: Idle timeout Ready --> Error check fails: Health Error --> Waking: Retry wake ``` ## Lifecycle Flow ### Wake Flow (HTTP) ```mermaid sequenceDiagram participant C as Client participant W as Watcher participant P as Provider participant H as HealthChecker participant SSE as SSE Events C->>W: HTTP Request W->>W: resetIdleTimer() alt Container already ready W->>W: return true (proceed) else alt No loading page configured W->>P: ContainerStart() W->>H: Wait for healthy H-->>W: Healthy W->>C: Continue request else Loading page enabled W->>P: ContainerStart() W->>SSE: Send WakeEventStarting W->>C: Serve loading page loop Health checks H->>H: Check health H-->>W: Not healthy yet W->>SSE: Send progress end H-->>W: Healthy W->>SSE: Send WakeEventReady C->>W: SSE connection W->>SSE: Events streamed C->>W: Poll/retry request W->>W: return true (proceed) end end ``` ### Stream Wake Flow ```mermaid sequenceDiagram participant C as Client participant W as Watcher participant P as Provider participant H as HealthChecker C->>W: Connect to stream W->>W: preDial hook W->>W: wakeFromStream() alt Container ready W->>W: Pass through else W->>P: ContainerStart() W->>W: waitStarted() W->>H: Wait for healthy H-->>W: Healthy W->>C: Stream connected end ``` ### Idle Timeout Flow ```mermaid sequenceDiagram participant Client as Client participant T as Idle Timer participant W as Watcher participant P as Provider participant D as Dependencies loop Every request Client->>W: HTTP/Stream W->>W: resetIdleTimer() end T->>W: Timeout W->>W: stopByMethod() alt stop method = pause W->>P: ContainerPause() else stop method = stop W->>P: ContainerStop(signal, timeout) else kill method = kill W->>P: ContainerKill(signal) end P-->>W: Result W->>D: Stop dependencies D-->>W: Done ``` ## Dependency Management Watchers can depend on other containers being started first: ```mermaid graph LR A[App] -->|depends on| B[Database] A -->|depends on| C[Redis] B -->|depends on| D[Cache] ``` ```mermaid sequenceDiagram participant A as App Watcher participant B as DB Watcher participant P as Provider A->>B: Wake() Note over B: SingleFlight prevents
duplicate wake B->>P: ContainerStart() P-->>B: Started B->>B: Wait healthy B-->>A: Ready A->>P: ContainerStart() P-->>A: Started ``` ## Event System Wake events are broadcast via Server-Sent Events (SSE): ```mermaid classDiagram class WakeEvent { +Type: WakeEventType +Message: string +Timestamp: time.Time +Error: string +WriteSSE(w io.Writer) error } class WakeEventType { <> WakeEventStarting WakeEventWakingDep WakeEventDepReady WakeEventContainerWoke WakeEventWaitingReady WakeEventReady WakeEventError } WakeEvent --> WakeEventType ``` ## State Machine ```mermaid stateDiagram-v2 note right of Napping Container is stopped or paused Idle timer stopped end note note right of Waking Container is starting Health checking active Events broadcasted end note note right of Ready Container healthy Idle timer running end note Napping --> Waking: Wake() Waking --> Ready: Health check passes Waking --> Error: Health check fails Error --> Waking: Retry Ready --> Napping: Idle timeout Ready --> Napping: Manual stop ``` ## Key Files | File | Purpose | | --------------------- | ----------------------------------------------------- | | `watcher.go` | Core Watcher implementation with lifecycle management | | `handle_http.go` | HTTP interception and loading page serving | | `handle_stream.go` | Stream connection wake handling | | `provider/docker.go` | Docker container operations | | `provider/proxmox.go` | Proxmox LXC container operations | | `state.go` | Container state transitions | | `events.go` | Event broadcasting via SSE | | `health.go` | Health monitor interface implementation | ## Configuration See `types.IdlewatcherConfig` for configuration options: - `IdleTimeout`: Duration before container is put to sleep - `StopMethod`: pause, stop, or kill - `StopSignal`: Signal to send when stopping - `StopTimeout`: Timeout for stop operation - `WakeTimeout`: Timeout for wake operation - `DependsOn`: List of dependent containers - `StartEndpoint`: Optional endpoint restriction for wake requests - `NoLoadingPage`: Skip loading page, wait directly ## Thread Safety - Uses `synk.Value` for atomic state updates - Uses `xsync.Map` for SSE subscriber management - Uses `sync.RWMutex` for watcher map access - Uses `singleflight.Group` to prevent duplicate wake calls