Documentation
¶
Overview ¶
Package daemon provides a reusable, cross-platform daemon lifecycle library.
Two modes of operation:
- Client mode: Start/Stop/Restart/Status -- manage a background daemon process.
- Server mode: Serve() -- run as the foreground daemon process with HTTP, PID management, signal handling.
Consumer wires CLI commands; this library provides the operations.
import "github.com/grpmsoft/daemon"
d := daemon.New(daemon.Config{Name: "myapp", DataDir: ".myapp"})
d.Start(ctx, binary, args) // spawn detached background process
d.Stop(ctx) // graceful shutdown
d.Status() // check if running, get info
Index ¶
- Variables
- func EnsureRunning(ctx context.Context, cfg Config, binary string, args []string) (int, error)
- func Proxy(ctx context.Context, cfg Config, opts ProxyOptions) error
- func Serve(ctx context.Context, cfg Config, handler http.Handler) error
- type Config
- type ConnTracker
- type Daemon
- type Group
- type HealthChecker
- type Info
- type PIDInfo
- type PIDStore
- type ProcessManager
- type ProxyOptions
- type Status
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotRunning indicates the daemon is not currently running. ErrNotRunning = errors.New("daemon: not running") // ErrAlreadyRunning indicates a daemon instance is already running. ErrAlreadyRunning = errors.New("daemon: already running") // ErrStartTimeout indicates the daemon did not become ready within the configured timeout. ErrStartTimeout = errors.New("daemon: start timeout") // ErrUnhealthy indicates the health check failed after the daemon started. ErrUnhealthy = errors.New("daemon: health check failed") // ErrStalePIDFile indicates the PID file exists but the daemon is not running. ErrStalePIDFile = errors.New("daemon: stale pid file") // ErrInvalidConfig indicates the configuration is invalid. ErrInvalidConfig = errors.New("daemon: invalid config") )
Functions ¶
func EnsureRunning ¶
EnsureRunning checks if a daemon is already running for this config. If running, it returns the existing port. If not, it starts a new daemon using the provided binary and args, then waits for health check to pass.
Protocol: two locks, two purposes, one ordering.
- Startup lock (<name>.lock): serializes starters. Held during spawn→ready. Kernel releases on holder death → next waiter becomes starter automatically (N12).
- PID file lock (<name>.pid): liveness signal. Held by running daemon for lifetime. Under startup lock, "PID held" means exactly "daemon alive" (no race with spawning).
func Proxy ¶
func Proxy(ctx context.Context, cfg Config, opts ProxyOptions) error
Proxy bridges an input stream to the daemon's HTTP endpoint. Reads newline-delimited JSON-RPC from opts.Stdin, POSTs each to the daemon, writes responses to opts.Stdout.
Connection tracking: signals /daemon/connect on start and /daemon/disconnect on exit (only if connect succeeded — prevents stray decrements).
Respects ctx for cancellation. Uses context-based timeouts instead of hardcoded http.Client.Timeout.
func Serve ¶
Serve runs the daemon in the foreground. Called by the spawned process. It picks a free port, registers /health + connection tracking endpoints, writes PID data to the locked PID file, and blocks until ctx is cancelled, SIGINT/SIGTERM is received, or idle timeout fires.
Lock inheritance: if DAEMON_PIDFD=3 is set (spawned by Start/EnsureRunning), Serve inherits the locked fd from the parent via ExtraFiles. If not set (manual foreground start), Serve acquires the lock itself.
Idle auto-shutdown: if Config.IdleTimeout > 0 and all connections disconnect, Serve waits IdleTimeout before initiating graceful shutdown. A new connection arriving during the wait resets the timer.
Types ¶
type Config ¶
type Config struct {
// Name identifies the application (e.g. "gode", "goda").
// Used for PID file naming and health response.
Name string
// DataDir is the directory for runtime files (PID, logs), relative to
// the workspace root. Example: ".gode".
DataDir string
// Timeout is how long Start() waits for the health check to pass.
// Zero means 30 seconds.
Timeout time.Duration
// HealthPath is the HTTP path for the health endpoint.
// Zero value means "/health".
HealthPath string
// IdleTimeout is how long the daemon waits with zero active connections
// before initiating a graceful shutdown. Zero means never auto-shutdown.
IdleTimeout time.Duration
}
Config controls daemon lifecycle behavior. Consumers provide this when creating a Daemon instance.
type ConnTracker ¶
type ConnTracker struct {
// contains filtered or unexported fields
}
ConnTracker tracks active client connections to the daemon. When IdleTimeout is configured, dropping to zero connections starts an idle timer that triggers graceful shutdown.
func NewConnTracker ¶
func NewConnTracker() *ConnTracker
NewConnTracker creates a connection tracker.
func (*ConnTracker) Active ¶
func (ct *ConnTracker) Active() int64
Active returns the current number of active connections.
func (*ConnTracker) Connect ¶
func (ct *ConnTracker) Connect()
Connect increments the connection count and signals that idle timer should be reset.
func (*ConnTracker) Disconnect ¶
func (ct *ConnTracker) Disconnect()
Disconnect decrements the connection count. If it drops to zero, signals on idleCh so the idle timer can start. The count is clamped to zero — an unmatched Disconnect (stray request, crashed client) cannot drive it negative and cause premature shutdown.
type Daemon ¶
type Daemon struct {
// contains filtered or unexported fields
}
Daemon manages a background daemon process. It depends on PIDStore, ProcessManager, and HealthChecker interfaces — not on concrete implementations. New() wires the defaults.
func NewWithDeps ¶
func NewWithDeps(cfg Config, pids PIDStore, procs ProcessManager, health HealthChecker) *Daemon
NewWithDeps creates a Daemon with explicit interface implementations. Intended for testing — callers supply mock PIDStore, ProcessManager, and HealthChecker.
func (*Daemon) IsRunning ¶
IsRunning returns true if the daemon process is alive according to the PID file.
func (*Daemon) Start ¶
Start spawns a detached background process running the given binary with args. It waits for the health check to pass within Config.Timeout. The spawned process must eventually call Serve().
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group manages multiple concurrent actors. When any actor returns, all others are interrupted. Modeled after oklog/run.Group but implemented from scratch with zero external dependencies.
type HealthChecker ¶
type HealthChecker interface {
Check(port int, healthPath string) error
WaitUntilReady(port int, healthPath string, timeout time.Duration) error
}
HealthChecker abstracts daemon readiness verification.
type Info ¶
type Info struct {
Status Status `json:"status"`
PID int `json:"pid"`
Port int `json:"port"`
Name string `json:"name"`
StartTime time.Time `json:"startTime"`
Uptime time.Duration `json:"uptime"`
}
Info is the status snapshot returned by Daemon.Status().
type PIDInfo ¶
type PIDInfo struct {
PID int `json:"pid"`
Port int `json:"port"`
Name string `json:"name"`
Binary string `json:"binary"`
StartTime time.Time `json:"startTime"`
}
PIDInfo holds the persisted state of a running daemon, as returned by PIDStore.Load().
type PIDStore ¶
type PIDStore interface {
Save(pid, port int, name, binary string, startTime time.Time) error
Load() (PIDInfo, error)
Clear() error
IsAlive() bool
Path() string
}
PIDStore abstracts PID file persistence. The Daemon model depends on this interface, not on a concrete file-based implementation. PIDInfo is returned by Load() with the stored daemon state.
type ProcessManager ¶
type ProcessManager interface {
StartDetached(binary string, args []string, logFile string, env []string) (pid int, err error)
KillProcess(pid int) error
IsProcessAlive(pid int) bool
}
ProcessManager abstracts platform-specific process lifecycle operations.
type ProxyOptions ¶ added in v0.2.0
type ProxyOptions struct {
// MCPPath is the HTTP path on the daemon to POST JSON-RPC messages to.
// Default: "/mcp".
MCPPath string
// Stdin is the input reader for JSON-RPC messages. Default: os.Stdin.
Stdin io.Reader
// Stdout is the output writer for JSON-RPC responses. Default: os.Stdout.
Stdout io.Writer
// LogPayloads enables logging of request/response bodies (first 500 bytes).
// Default: false (only method/id/size/latency logged for security — S3).
LogPayloads bool
}
ProxyOptions configures the Proxy behavior.
type Status ¶
type Status int
Status represents the lifecycle state of a daemon process.
const ( StatusStopped Status = iota // not running, no PID file StatusStarting // process spawned, health check pending StatusRunning // healthy and responding StatusStopping // shutdown in progress StatusError // PID file exists but process is dead or unhealthy )
Status constants representing the lifecycle state of a daemon process.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package internal provides platform-specific implementations for the daemon library: PID file management, process lifecycle operations, and health checking.
|
Package internal provides platform-specific implementations for the daemon library: PID file management, process lifecycle operations, and health checking. |
|
pidlock
Package pidlock provides an exclusive advisory lock backed by the PID file itself.
|
Package pidlock provides an exclusive advisory lock backed by the PID file itself. |