docs: add per package README for implementation details (AI generated with human review)

This commit is contained in:
yusing
2026-01-08 23:39:19 +08:00
parent 2f2828ec48
commit 86b655be3c
54 changed files with 13825 additions and 124 deletions

View File

@@ -0,0 +1,293 @@
# 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 {
<<interface>>
+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

View File

@@ -0,0 +1,219 @@
# Idlewatcher Provider
Implements container runtime abstractions for Docker and Proxmox LXC backends.
## Overview
The `internal/idlewatcher/provider` package implements the `idlewatcher.Provider` interface for different container runtimes. It enables the idlewatcher to manage containers regardless of the underlying runtime (Docker or Proxmox LXC).
### Primary Consumers
- **idlewatcher.Watcher**: Uses providers to perform container lifecycle operations
- **Package tests**: Verify provider contract compliance
### Non-goals
- Does not implement idle detection logic
- Does not manage route configuration
- Does not handle health checking
### Stability
Internal package implementing stable `idlewatcher.Provider` interface.
## Public API
### Provider Interface
```go
type Provider interface {
// Lifecycle operations
ContainerPause(ctx context.Context) error
ContainerUnpause(ctx context.Context) error
ContainerStart(ctx context.Context) error
ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error
ContainerKill(ctx context.Context, signal types.ContainerSignal) error
// Status and monitoring
ContainerStatus(ctx context.Context) (ContainerStatus, error)
Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan gperr.Error)
// Cleanup
Close()
}
```
### Container Status
```go
type ContainerStatus string
const (
ContainerStatusRunning ContainerStatus = "running"
ContainerStatusStopped ContainerStatus = "stopped"
ContainerStatusPaused ContainerStatus = "paused"
ContainerStatusError ContainerStatus = "error"
)
```
### Exported Functions
```go
// NewDockerProvider creates a provider for Docker containers
func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string) (idlewatcher.Provider, error)
// NewProxmoxProvider creates a provider for Proxmox LXC containers
func NewProxmoxProvider(ctx context.Context, nodeName string, vmid int) (idlewatcher.Provider, error)
```
## Architecture
### Core Components
```mermaid
classDiagram
class Provider {
<<interface>>
+ContainerPause(ctx) error
+ContainerStart(ctx) error
+ContainerStop(ctx, signal, timeout) error
+ContainerStatus(ctx) (ContainerStatus, error)
+Watch(ctx) (eventCh, errCh)
+Close()
}
class DockerProvider {
+client *docker.SharedClient
+watcher watcher.DockerWatcher
+containerID string
+ContainerPause(ctx) error
+ContainerStart(ctx) error
+ContainerStatus(ctx) (ContainerStatus, error)
}
class ProxmoxProvider {
+*proxmox.Node
+vmid int
+lxcName string
+running bool
+ContainerStart(ctx) error
+ContainerStop(ctx, signal, timeout) error
}
Provider <|-- DockerProvider
Provider <|-- ProxmoxProvider
```
### Component Interactions
```mermaid
flowchart TD
A[Watcher] --> B{Provider Type}
B -->|Docker| C[DockerProvider]
B -->|Proxmox| D[ProxmoxProvider]
C --> E[Docker API]
D --> F[Proxmox API]
E --> G[Container Events]
F --> H[LXC Events]
G --> A
H --> A
```
## Configuration Surface
### Docker Provider Config
```go
type DockerProviderConfig struct {
URL string // Docker socket URL (unix:///var/run/docker.sock)
SocketPath string // Alternative socket path
}
```
### Proxmox Provider Config
Provided via `NewProxmoxProvider` parameters:
- `nodeName`: Proxmox node name
- `vmid`: LXC container ID
## Dependency and Integration Map
| Dependency | Purpose |
| ------------------------- | -------------------------------------- |
| `internal/docker` | Docker client and container operations |
| `internal/proxmox` | Proxmox API client |
| `internal/watcher` | Event watching for container changes |
| `internal/watcher/events` | Event types |
| `pkg/gperr` | Error handling |
## Observability
### Logs
- **DEBUG**: API calls and responses
- **ERROR**: Operation failures with context
Log context includes: `container`, `vmid`, `action`
## Security Considerations
- Docker provider requires access to Docker socket
- Proxmox provider requires API credentials
- Both handle sensitive container operations
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| ------------------------- | ------------------------ | --------------------------- |
| Docker socket unavailable | Returns connection error | Fix socket permissions/path |
| Container not found | Returns not found error | Verify container ID |
| Proxmox node unavailable | Returns API error | Check network/node |
| Operation timeout | Returns timeout error | Increase timeout or retry |
## Usage Examples
### Creating a Docker Provider
```go
provider, err := provider.NewDockerProvider(types.DockerProviderConfig{
SocketPath: "/var/run/docker.sock",
}, "abc123def456")
if err != nil {
return err
}
defer provider.Close()
// Check container status
status, err := provider.ContainerStatus(ctx)
if err != nil {
return err
}
// Start container if stopped
if status == idlewatcher.ContainerStatusStopped {
if err := provider.ContainerStart(ctx); err != nil {
return err
}
}
```
### Watching for Container Events
```go
eventCh, errCh := provider.Watch(ctx)
for {
select {
case <-ctx.Done():
return
case event := <-eventCh:
log.Printf("Container %s: %s", event.ActorName, event.Action)
case err := <-errCh:
log.Printf("Watch error: %v", err)
}
}
```