docs(idlewatcher): update README to include loading page and SSE endpoint details

- Added information about the loading page (HTML + JS + CSS) and the SSE endpoint for wake events.
- Clarified the health monitor implementation and readiness tracking in the architecture overview.
- Correct state machine syntax.
This commit is contained in:
yusing
2026-01-08 20:31:44 +08:00
parent 86f35878fb
commit 13441286d1

View File

@@ -2,6 +2,8 @@
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. 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.
Idlewatcher also serves a small loading page (HTML + JS + CSS) and an SSE endpoint under [`internal/idlewatcher/types/paths.go`](internal/idlewatcher/types/paths.go:1) (prefixed with `/$godoxy/`) to provide wake events to browsers.
## Architecture Overview ## Architecture Overview
```mermaid ```mermaid
@@ -36,14 +38,13 @@ graph TB
``` ```
idlewatcher/ idlewatcher/
├── cmd # Command execution utilities
├── debug.go # Debug utilities for watcher inspection ├── debug.go # Debug utilities for watcher inspection
├── errors.go # Error types and conversion ├── errors.go # Error types and conversion
├── events.go # Wake event types and broadcasting ├── events.go # Wake event types and broadcasting
├── handle_http.go # HTTP request handling and loading page ├── handle_http.go # HTTP request handling and loading page
├── handle_http_debug.go # Debug HTTP handler (dev only) ├── handle_http_debug.go # Debug HTTP handler (!production builds)
├── handle_stream.go # Stream connection handling ├── handle_stream.go # Stream connection handling
├── health.go # Health monitoring interface ├── health.go # Health monitor implementation + readiness tracking
├── loading_page.go # Loading page HTML/CSS/JS templates ├── loading_page.go # Loading page HTML/CSS/JS templates
├── state.go # Container state management ├── state.go # Container state management
├── watcher.go # Core Watcher implementation ├── watcher.go # Core Watcher implementation
@@ -51,7 +52,10 @@ idlewatcher/
│ ├── docker.go # Docker container management │ ├── docker.go # Docker container management
│ └── proxmox.go # Proxmox LXC management │ └── proxmox.go # Proxmox LXC management
├── types/ ├── types/
── provider.go # Provider interface definition ── container_status.go # ContainerStatus enum
│ ├── paths.go # Loading page + SSE paths
│ ├── provider.go # Provider interface definition
│ └── waker.go # Waker interface (http + stream + health)
└── html/ └── html/
├── loading_page.html # Loading page template ├── loading_page.html # Loading page template
├── style.css # Loading page styles ├── style.css # Loading page styles
@@ -76,6 +80,9 @@ classDiagram
-healthTicker: *time.Ticker -healthTicker: *time.Ticker
-state: synk.Value~*containerState~ -state: synk.Value~*containerState~
-provider: synk.Value~Provider~ -provider: synk.Value~Provider~
-readyNotifyCh: chan struct{}
-eventChs: *xsync.Map~chan *WakeEvent, struct{}~
-eventHistory: []WakeEvent
-dependsOn: []*dependency -dependsOn: []*dependency
} }
@@ -96,6 +103,11 @@ classDiagram
Watcher --> dependency : depends on Watcher --> dependency : depends on
``` ```
Package-level helpers:
- `watcherMap` is a global registry of watchers keyed by [`types.IdlewatcherConfig.Key()`](internal/types/idlewatcher.go:60), guarded by `watcherMapMu`.
- `singleFlight` is a global `singleflight.Group` keyed by container name to prevent duplicate wake calls.
### Provider Interface ### Provider Interface
Abstraction for different container backends: Abstraction for different container backends:
@@ -135,16 +147,26 @@ classDiagram
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
[*] --> Napping: Container stopped/paused [*] --> Napping: status=stopped|paused
Napping --> Waking: Wake request
Waking --> Running: Container started Napping --> Starting: provider start/unpause event
Running --> Starting: Container is running but not healthy Starting --> Ready: health check passes
Starting --> Ready: Health check passes Starting --> Error: health check error / startup timeout
Ready --> Napping: Idle timeout
Ready --> Error check fails: Health Ready --> Napping: idle timeout (pause/stop/kill)
Error --> Waking: Retry wake Ready --> Error: health check error
Error --> Napping: provider stop/pause event
Error --> Starting: provider start/unpause event
``` ```
Implementation notes:
- `Starting` is represented by `containerState{status: running, ready: false, startedAt: non-zero}`.
- `Ready` is represented by `containerState{status: running, ready: true}`.
- `Error` is represented by `containerState{status: error, err: non-nil}`.
- State is updated primarily from provider events in [`(*Watcher).watchUntilDestroy()`](internal/idlewatcher/watcher.go:553) and health checks in [`(*Watcher).checkUpdateState()`](internal/idlewatcher/health.go:104).
## Lifecycle Flow ## Lifecycle Flow
### Wake Flow (HTTP) ### Wake Flow (HTTP)
@@ -154,34 +176,26 @@ sequenceDiagram
participant C as Client participant C as Client
participant W as Watcher participant W as Watcher
participant P as Provider participant P as Provider
participant H as HealthChecker participant SSE as SSE (/\$godoxy/wake-events)
participant SSE as SSE Events
C->>W: HTTP Request C->>W: HTTP Request
W->>W: resetIdleTimer() W->>W: resetIdleTimer()
Note over W: Handles /favicon.ico and /\$godoxy/* assets first
alt Container already ready alt Container already ready
W->>W: return true (proceed) W->>C: Reverse-proxy upstream (same request)
else else
alt No loading page configured W->>W: Wake() (singleflight + deps)
W->>P: ContainerStart()
W->>H: Wait for healthy alt Non-HTML request OR NoLoadingPage=true
H-->>W: Healthy W->>C: 100 Continue
W->>C: Continue request W->>W: waitForReady() (readyNotifyCh)
else Loading page enabled W->>C: Reverse-proxy upstream (same request)
W->>P: ContainerStart() else HTML + loading page
W->>SSE: Send WakeEventStarting W->>C: Serve loading page (HTML)
W->>C: Serve loading page C->>SSE: Connect (EventSource)
loop Health checks Note over SSE: Streams history + live wake events
H->>H: Check health C->>W: Retry original request when WakeEventReady
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
end end
``` ```
@@ -192,8 +206,6 @@ sequenceDiagram
sequenceDiagram sequenceDiagram
participant C as Client participant C as Client
participant W as Watcher participant W as Watcher
participant P as Provider
participant H as HealthChecker
C->>W: Connect to stream C->>W: Connect to stream
W->>W: preDial hook W->>W: preDial hook
@@ -201,10 +213,9 @@ sequenceDiagram
alt Container ready alt Container ready
W->>W: Pass through W->>W: Pass through
else else
W->>P: ContainerStart() W->>W: Wake() (singleflight + deps)
W->>W: waitStarted() W->>W: waitStarted() (wait for route to be started)
W->>H: Wait for healthy W->>W: waitForReady() (readyNotifyCh)
H-->>W: Healthy
W->>C: Stream connected W->>C: Stream connected
end end
``` ```
@@ -293,17 +304,31 @@ classDiagram
WakeEvent --> WakeEventType WakeEvent --> WakeEventType
``` ```
Notes:
- The SSE endpoint is [`idlewatcher.WakeEventsPath`](internal/idlewatcher/types/paths.go:3).
- Each SSE subscriber gets a dedicated buffered channel; the watcher also keeps an in-memory `eventHistory` that is sent to new subscribers first.
- `eventHistory` is cleared when the container transitions to napping (stop/pause).
## State Machine ## State Machine
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
Napping --> Starting: provider start/unpause event
Starting --> Ready: Health check passes
Starting --> Error: Health check fails / startup timeout
Error --> Napping: provider stop/pause event
Error --> Starting: provider start/unpause event
Ready --> Napping: Idle timeout
Ready --> Napping: Manual stop
note right of Napping note right of Napping
Container is stopped or paused Container is stopped or paused
Idle timer stopped Idle timer stopped
end note end note
note right of Waking note right of Starting
Container is starting Container is running but not ready
Health checking active Health checking active
Events broadcasted Events broadcasted
end note end note
@@ -312,13 +337,6 @@ stateDiagram-v2
Container healthy Container healthy
Idle timer running Idle timer running
end note 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 ## Key Files
@@ -332,11 +350,11 @@ stateDiagram-v2
| `provider/proxmox.go` | Proxmox LXC container operations | | `provider/proxmox.go` | Proxmox LXC container operations |
| `state.go` | Container state transitions | | `state.go` | Container state transitions |
| `events.go` | Event broadcasting via SSE | | `events.go` | Event broadcasting via SSE |
| `health.go` | Health monitor interface implementation | | `health.go` | Health monitor implementation + readiness tracking |
## Configuration ## Configuration
See `types.IdlewatcherConfig` for configuration options: See [`types.IdlewatcherConfig`](internal/types/idlewatcher.go:27) for configuration options:
- `IdleTimeout`: Duration before container is put to sleep - `IdleTimeout`: Duration before container is put to sleep
- `StopMethod`: pause, stop, or kill - `StopMethod`: pause, stop, or kill
@@ -344,12 +362,17 @@ See `types.IdlewatcherConfig` for configuration options:
- `StopTimeout`: Timeout for stop operation - `StopTimeout`: Timeout for stop operation
- `WakeTimeout`: Timeout for wake operation - `WakeTimeout`: Timeout for wake operation
- `DependsOn`: List of dependent containers - `DependsOn`: List of dependent containers
- `StartEndpoint`: Optional endpoint restriction for wake requests - `StartEndpoint`: Optional HTTP path restriction for wake requests
- `NoLoadingPage`: Skip loading page, wait directly - `NoLoadingPage`: Skip loading page, wait directly
Provider config (exactly one must be set):
- `Docker`: container id/name + docker connection info
- `Proxmox`: `node` + `vmid`
## Thread Safety ## Thread Safety
- Uses `synk.Value` for atomic state updates - Uses `synk.Value` for atomic state updates
- Uses `xsync.Map` for SSE subscriber management - Uses `xsync.Map` for SSE subscriber management
- Uses `sync.RWMutex` for watcher map access - Uses `sync.RWMutex` for watcher map (`watcherMapMu`) and SSE event history (`eventHistoryMu`)
- Uses `singleflight.Group` to prevent duplicate wake calls - Uses `singleflight.Group` to prevent duplicate wake calls