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() // graceful shutdown
d.Status() // check if running, get info
Index ¶
- func EnsureRunning(ctx context.Context, cfg Config, binary string, args []string) (int, error)
- func Proxy(ctx context.Context, port int, mcpPath string) error
- func Serve(ctx context.Context, cfg Config, handler http.Handler) error
- type Config
- type ConnTracker
- type Daemon
- func (d *Daemon) IsRunning() bool
- func (d *Daemon) Restart(ctx context.Context, binary string, args []string) error
- func (d *Daemon) SetHandler(h http.Handler)
- func (d *Daemon) Start(ctx context.Context, binary string, args []string) error
- func (d *Daemon) Status() (*Info, error)
- func (d *Daemon) Stop() error
- type Group
- type HealthChecker
- type Info
- type PIDInfo
- type PIDStore
- type ProcessManager
- type Status
Constants ¶
This section is empty.
Variables ¶
This section is empty.
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.
This is the main entry point for consumers like "gode mcp serve" -- it transparently handles "start if needed" logic.
Concurrency: an exclusive file lock serializes the check→spawn→ready sequence across processes. If two agents call EnsureRunning simultaneously, one acquires the lock and starts the daemon; the other blocks until the lock is released and then finds the daemon already running.
func Proxy ¶
Proxy bridges stdin/stdout to an HTTP endpoint on the daemon. Used by "gode mcp serve" to proxy MCP JSON-RPC through stdio to the shared daemon.
Protocol: reads complete JSON-RPC messages from stdin (newline-delimited JSON), POSTs each to http://127.0.0.1:{port}{mcpPath}, writes responses to stdout.
Connection tracking: on start, POSTs /daemon/connect to increment the daemon's active connection count. On exit (EOF or error), POSTs /daemon/disconnect to decrement it. This enables the daemon's idle auto-shutdown feature.
Exits cleanly on EOF (stdin closed = agent disconnected, daemon stays). Returns error on connection refused (daemon died).
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 file (with binary verification), and blocks until ctx is cancelled, SIGINT/SIGTERM is received, or idle timeout fires.
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) SetHandler ¶
SetHandler sets the HTTP handler that Serve() will expose.
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 ¶
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 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.
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. |