pkg/gateway/daemon/daemon.go
Source
- Package:
daemon - File:
pkg/gateway/daemon/daemon.go - GitHub: https://github.com/theroutercompany/api_router/blob/main/pkg/gateway/daemon/daemon.go
Overview
What:
Implements the managed "daemon" lifecycle used by the cmd/apigw daemon subcommands.
This package provides:
- helpers to write/read a PID file for the running process
- optional redirection of logs to a file via
APIGW_LOG_PATH Run/Status/Stopentrypoints that the CLI can call
Why:
The gateway supports both foreground execution and a "managed" mode that behaves like a lightweight daemon. Operators often want:
- a deterministic PID file location for stop/status scripts
- logs written to a file for background runs
- basic safety checks (avoid overwriting an existing PID file)
Keeping this logic in a dedicated package makes the CLI implementation thin and avoids duplicating OS/process logic.
How:
Run():
- optionally writes the PID file
- optionally sets up log output by setting
APIGW_LOG_PATH - loads config, builds runtime, starts it, then blocks in
Wait()
Status():
- reads the PID file (if present)
- probes the process via
signal 0to determine if it is running
Stop():
- looks up the PID, sends a signal (default SIGTERM)
- waits briefly for exit, then escalates to SIGKILL if needed
- removes the PID file when the process is gone
Notes: PID files are best-effort. If the process is killed abruptly, the PID file may remain and Status()/Stop() handle that case.
Contents
Imports
import block 1
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
gatewayconfig "github.com/theroutercompany/api_router/pkg/gateway/config"
gatewayruntime "github.com/theroutercompany/api_router/pkg/gateway/runtime"
)
Types
type block 1
type Options struct {
ConfigPath string
PIDFile string
LogFile string
}
Options
What: Configuration knobs for daemon lifecycle behaviour.
Why: Makes daemon paths explicit and keeps Run/Stop free of global configuration.
How: Passed from the CLI layer into Run (and PID/log paths into Status/Stop).
Notes: Empty PID/log paths disable PID/log file behaviour.
type block 2
type ProcessStatus struct {
PID int
Running bool
}
ProcessStatus
What: Result type representing the daemon PID and whether it is running.
Why: Consolidates status checks into a single return value for the CLI and callers.
How: The Status and Stop functions populate this struct based on PID file contents and process probing.
Notes: PID can be non-zero even when Running is false if the PID file exists but the process is gone.
Functions and Methods
Run
What: Boots the gateway runtime in managed "daemon" mode.
Why: Provides a CLI-friendly entrypoint that handles PID/log file setup and then runs the normal runtime.
How: Writes the PID file, sets up the log file (if configured), loads config from opts.ConfigPath, constructs the runtime, starts it, and blocks on rt.Wait().
Notes: The returned cleanup closures run only on graceful returns; hard kills can leave PID files behind.
func Run(ctx context.Context, opts Options) error {
cleanupPID, err := writePIDFile(opts.PIDFile)
if err != nil {
return err
}
defer cleanupPID()
logCloser, err := setupLogFile(opts.LogFile)
if err != nil {
return err
}
defer logCloser()
loadOpts := []gatewayconfig.Option{}
if strings.TrimSpace(opts.ConfigPath) != "" {
loadOpts = append(loadOpts, gatewayconfig.WithPath(opts.ConfigPath))
}
cfg, err := gatewayconfig.Load(loadOpts...)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
rt, err := gatewayruntime.New(cfg)
if err != nil {
return fmt.Errorf("build runtime: %w", err)
}
if err := rt.Start(ctx); err != nil {
return err
}
return rt.Wait()
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L34:
cleanupPID, err := writePIDFile(opts.PIDFile)- What: Defines cleanupPID, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L35:
if err != nil { return err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L36:
return err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L36:
- L38:
defer cleanupPID()- What: Defers a call for cleanup.
- Why: Ensures the deferred action runs even on early returns.
- How: Schedules the call to run when the surrounding function returns.
- L40:
logCloser, err := setupLogFile(opts.LogFile)- What: Defines logCloser, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L41:
if err != nil { return err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L42:
return err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L42:
- L44:
defer logCloser()- What: Defers a call for cleanup.
- Why: Ensures the deferred action runs even on early returns.
- How: Schedules the call to run when the surrounding function returns.
- L46:
loadOpts := []gatewayconfig.Option{}- What: Defines loadOpts.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L47:
if strings.TrimSpace(opts.ConfigPath) != "" { loadOpts = append(loadOpts, gatewayconfig.WithPath(opts.ConfigPath)) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L48:
loadOpts = append(loadOpts, gatewayconfig.WithPath(opts.ConfigPath))- What: Assigns loadOpts.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L48:
- L51:
cfg, err := gatewayconfig.Load(loadOpts...)- What: Defines cfg, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L52:
if err != nil { return fmt.Errorf("load config: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L53:
return fmt.Errorf("load config: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L53:
- L56:
rt, err := gatewayruntime.New(cfg)- What: Defines rt, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L57:
if err != nil { return fmt.Errorf("build runtime: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L58:
return fmt.Errorf("build runtime: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L58:
- L61:
if err := rt.Start(ctx); err != nil { return err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L61:
err := rt.Start(ctx)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L62:
return err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L61:
- L64:
return rt.Wait()- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
Status
What: Returns the current daemon PID and whether the process appears to be running.
Why: Enables apigw daemon status and shell scripts to determine if the gateway is alive.
How: Reads the PID file and sends signal 0 to the process to test for existence; missing PID file returns an empty status and nil error.
Notes: A stale PID file (PID reused by another process) can produce false positives; this is a common PID-file limitation.
func Status(pidPath string) (ProcessStatus, error) {
pid, err := readPIDFile(pidPath)
if errors.Is(err, os.ErrNotExist) {
return ProcessStatus{}, nil
}
if err != nil {
return ProcessStatus{}, err
}
status := ProcessStatus{PID: pid}
proc, err := os.FindProcess(pid)
if err != nil {
return status, fmt.Errorf("find process: %w", err)
}
if err := proc.Signal(syscall.Signal(0)); err == nil {
status.Running = true
} else {
status.Running = false
}
return status, nil
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L69:
pid, err := readPIDFile(pidPath)- What: Defines pid, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L70:
if errors.Is(err, os.ErrNotExist) { return ProcessStatus{}, nil }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L71:
return ProcessStatus{}, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L71:
- L73:
if err != nil { return ProcessStatus{}, err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L74:
return ProcessStatus{}, err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L74:
- L77:
status := ProcessStatus{PID: pid}- What: Defines status.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L78:
proc, err := os.FindProcess(pid)- What: Defines proc, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L79:
if err != nil { return status, fmt.Errorf("find process: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L80:
return status, fmt.Errorf("find process: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L80:
- L82:
if err := proc.Signal(syscall.Signal(0)); err == nil { status.Running = true } else { status.Running = false }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L82:
err := proc.Signal(syscall.Signal(0))- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L83:
status.Running = true- What: Assigns status.Running.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L85:
status.Running = false- What: Assigns status.Running.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L82:
- L87:
return status, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
Stop
What: Stops the daemon process referenced by the PID file.
Why: Enables apigw daemon stop and ensures PID cleanup occurs when the process is gone.
How: Resolves status, sends the configured signal (default SIGTERM), polls for up to ~5s, escalates to SIGKILL if still running, and removes the PID file.
Notes: If the process is already dead, Stop removes the PID file and returns without error.
func Stop(pidPath string, sig syscall.Signal) (ProcessStatus, error) {
if sig == 0 {
sig = syscall.SIGTERM
}
status, err := Status(pidPath)
if err != nil {
return status, err
}
if status.PID == 0 {
return status, os.ErrNotExist
}
if !status.Running {
_ = os.Remove(pidPath)
return status, nil
}
proc, err := os.FindProcess(status.PID)
if err != nil {
return status, fmt.Errorf("find process: %w", err)
}
if err := proc.Signal(sig); err != nil {
return status, fmt.Errorf("signal process: %w", err)
}
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
time.Sleep(200 * time.Millisecond)
st, err := Status(pidPath)
if errors.Is(err, os.ErrNotExist) || (err == nil && !st.Running) {
_ = os.Remove(pidPath)
return st, nil
}
if err != nil {
return status, err
}
}
if sig != syscall.SIGKILL {
if err := proc.Signal(syscall.SIGKILL); err != nil {
return status, fmt.Errorf("force kill process: %w", err)
}
time.Sleep(200 * time.Millisecond)
}
_ = os.Remove(pidPath)
return status, nil
}
Walkthrough
Expand walkthrough (32 steps)
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L92:
if sig == 0 { sig = syscall.SIGTERM }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L93:
sig = syscall.SIGTERM- What: Assigns sig.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L93:
- L96:
status, err := Status(pidPath)- What: Defines status, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L97:
if err != nil { return status, err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L98:
return status, err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L98:
- L100:
if status.PID == 0 { return status, os.ErrNotExist }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L101:
return status, os.ErrNotExist- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L101:
- L103:
if !status.Running { _ = os.Remove(pidPath) return status, nil }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L104:
_ = os.Remove(pidPath)- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L105:
return status, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L104:
- L108:
proc, err := os.FindProcess(status.PID)- What: Defines proc, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L109:
if err != nil { return status, fmt.Errorf("find process: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L110:
return status, fmt.Errorf("find process: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L110:
- L112:
if err := proc.Signal(sig); err != nil { return status, fmt.Errorf("signal process: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L112:
err := proc.Signal(sig)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L113:
return status, fmt.Errorf("signal process: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L112:
- L116:
deadline := time.Now().Add(5 * time.Second)- What: Defines deadline.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L117:
for time.Now().Before(deadline) { time.Sleep(200 * time.Millisecond) st, err := Status(pidPath) if errors.Is(err, os.ErrNotExist) || (err =…- What: Runs a loop.
- Why: Repeats logic until a condition is met or the loop terminates.
- How: Executes a
forloop statement. - Nested steps:
- L118:
time.Sleep(200 * time.Millisecond)- What: Calls time.Sleep.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L119:
st, err := Status(pidPath)- What: Defines st, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L120:
if errors.Is(err, os.ErrNotExist) || (err == nil && !st.Running) { _ = os.Remove(pidPath) return st, nil }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L121:
_ = os.Remove(pidPath)- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L122:
return st, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L121:
- L124:
if err != nil { return status, err }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L125:
return status, err- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L125:
- L118:
- L129:
if sig != syscall.SIGKILL { if err := proc.Signal(syscall.SIGKILL); err != nil { return status, fmt.Errorf("force kill process: %w", err) }…- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L130:
if err := proc.Signal(syscall.SIGKILL); err != nil { return status, fmt.Errorf("force kill process: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L130:
err := proc.Signal(syscall.SIGKILL)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L131:
return status, fmt.Errorf("force kill process: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L130:
- L133:
time.Sleep(200 * time.Millisecond)- What: Calls time.Sleep.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L130:
- L135:
_ = os.Remove(pidPath)- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L136:
return status, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
writePIDFile
What: Writes the current process PID to a file and returns a cleanup function.
Why: PID files are the simplest interoperability mechanism for stop/status scripts and CLI subcommands.
How: Ensures the directory exists, errors if the PID file already exists, writes the current PID, and returns a cleanup function that removes the file.
Notes: The existence check is a safety guard but cannot prevent all races across processes.
func writePIDFile(path string) (func(), error) {
path = strings.TrimSpace(path)
if path == "" {
return func() {}, nil
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return func() {}, fmt.Errorf("ensure pid directory: %w", err)
}
if _, err := readPIDFile(path); err == nil {
return func() {}, fmt.Errorf("pid file %s already exists", path)
}
tmp := []byte(fmt.Sprintf("%d\n", os.Getpid()))
if err := os.WriteFile(path, tmp, 0o644); err != nil {
return func() {}, fmt.Errorf("write pid file: %w", err)
}
return func() {
_ = os.Remove(path)
}, nil
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L140:
path = strings.TrimSpace(path)- What: Assigns path.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L141:
if path == "" { return func() {}, nil }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L142:
return func() {}, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L142:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L142:
- L142:
- L145:
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return func() {}, fmt.Errorf("ensure pid directory: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L145:
err := os.MkdirAll(filepath.Dir(path), 0o755)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L146:
return func() {}, fmt.Errorf("ensure pid directory: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L146:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L146:
- L145:
- L149:
if _, err := readPIDFile(path); err == nil { return func() {}, fmt.Errorf("pid file %s already exists", path) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L149:
_, err := readPIDFile(path)- What: Defines _, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L150:
return func() {}, fmt.Errorf("pid file %s already exists", path)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L150:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L150:
- L149:
- L153:
tmp := []byte(fmt.Sprintf("%d\n", os.Getpid()))- What: Defines tmp.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L154:
if err := os.WriteFile(path, tmp, 0o644); err != nil { return func() {}, fmt.Errorf("write pid file: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L154:
err := os.WriteFile(path, tmp, 0o644)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L155:
return func() {}, fmt.Errorf("write pid file: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L155:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L155:
- L154:
- L158:
return func() { _ = os.Remove(path) }, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L158:
func() { _ = os.Remove(path) }- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback). - Nested steps:
- L159:
_ = os.Remove(path)- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L159:
- L158:
readPIDFile
What: Reads and parses a PID file into an integer PID.
Why: Centralizes PID parsing and validation so Status and Stop behave consistently.
How: Reads the file, trims whitespace, parses an integer, and validates the PID is positive.
Notes: Returns os.ErrNotExist when the file is missing so callers can treat "not running" as non-fatal.
func readPIDFile(path string) (int, error) {
path = strings.TrimSpace(path)
if path == "" {
return 0, fmt.Errorf("pid file path is required")
}
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return 0, os.ErrNotExist
}
return 0, fmt.Errorf("read pid file: %w", err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
return 0, fmt.Errorf("parse pid: %w", err)
}
if pid <= 0 {
return 0, fmt.Errorf("invalid pid value %d", pid)
}
return pid, nil
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L164:
path = strings.TrimSpace(path)- What: Assigns path.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L165:
if path == "" { return 0, fmt.Errorf("pid file path is required") }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L166:
return 0, fmt.Errorf("pid file path is required")- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L166:
- L169:
data, err := os.ReadFile(path)- What: Defines data, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L170:
if err != nil { if errors.Is(err, os.ErrNotExist) { return 0, os.ErrNotExist } return 0, fmt.Errorf("read pid file: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L171:
if errors.Is(err, os.ErrNotExist) { return 0, os.ErrNotExist }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L172:
return 0, os.ErrNotExist- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L172:
- L174:
return 0, fmt.Errorf("read pid file: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L171:
- L177:
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))- What: Defines pid, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L178:
if err != nil { return 0, fmt.Errorf("parse pid: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L179:
return 0, fmt.Errorf("parse pid: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L179:
- L181:
if pid <= 0 { return 0, fmt.Errorf("invalid pid value %d", pid) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L182:
return 0, fmt.Errorf("invalid pid value %d", pid)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L182:
- L184:
return pid, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
setupLogFile
What: Prepares a log file path for the process and sets the APIGW_LOG_PATH environment variable.
Why: The default logger reads APIGW_LOG_PATH to decide where to write logs for daemon runs.
How: Ensures the directory exists, opens the file for append/create, sets APIGW_LOG_PATH, and returns a cleanup function that closes the file.
Notes: Setting the env var must happen before the logger is initialised to take effect.
func setupLogFile(path string) (func(), error) {
path = strings.TrimSpace(path)
if path == "" {
return func() {}, nil
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return func() {}, fmt.Errorf("ensure log directory: %w", err)
}
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return func() {}, fmt.Errorf("open log file: %w", err)
}
if err := os.Setenv("APIGW_LOG_PATH", path); err != nil {
_ = file.Close()
return func() {}, fmt.Errorf("set log env: %w", err)
}
return func() {
_ = file.Close()
}, nil
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L188:
path = strings.TrimSpace(path)- What: Assigns path.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L189:
if path == "" { return func() {}, nil }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L190:
return func() {}, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L190:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L190:
- L190:
- L193:
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return func() {}, fmt.Errorf("ensure log directory: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L193:
err := os.MkdirAll(filepath.Dir(path), 0o755)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L194:
return func() {}, fmt.Errorf("ensure log directory: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L194:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L194:
- L193:
- L197:
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)- What: Defines file, err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L198:
if err != nil { return func() {}, fmt.Errorf("open log file: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L199:
return func() {}, fmt.Errorf("open log file: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L199:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L199:
- L199:
- L202:
if err := os.Setenv("APIGW_LOG_PATH", path); err != nil { _ = file.Close() return func() {}, fmt.Errorf("set log env: %w", err) }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L202:
err := os.Setenv("APIGW_LOG_PATH", path)- What: Defines err.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L203:
_ = file.Close()- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L204:
return func() {}, fmt.Errorf("set log env: %w", err)- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L204:
func() {}- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback).
- L204:
- L202:
- L207:
return func() { _ = file.Close() }, nil- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values). - Nested steps:
- L207:
func() { _ = file.Close() }- What: Defines an inline function (closure).
- Why: Encapsulates callback logic and may capture variables from the surrounding scope.
- How: Declares a
funcliteral and uses it as a value (for example, as an HTTP handler or callback). - Nested steps:
- L208:
_ = file.Close()- What: Assigns _.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L208:
- L207:
WaitSignal
What: Blocks until a termination signal is received or the context is done.
Why: Allows callers to coordinate graceful shutdown on SIGTERM/SIGINT while still supporting context cancellation.
How: Uses signal.Notify for SIGTERM and SIGINT and waits for either the signal channel or ctx.Done().
Notes: The process typically exits shortly after this returns by cancelling the runtime context.
func WaitSignal(ctx context.Context) {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
select {
case <-ctx.Done():
case <-sigCh:
}
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L214:
sigCh := make(chan os.Signal, 1)- What: Defines sigCh.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L215:
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)- What: Calls signal.Notify.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L216:
select { case <-ctx.Done(): case <-sigCh: }- What: Selects among concurrent operations.
- Why: Coordinates channel operations without blocking incorrectly.
- How: Executes a
selectstatement and runs one ready case. - Nested steps:
- L217:
case <-ctx.Done():- What: Selects a select-case branch.
- Why: Coordinates concurrent operations without blocking incorrectly.
- How: Runs this case body when its channel operation is ready (or runs default immediately).
- L218:
case <-sigCh:- What: Selects a select-case branch.
- Why: Coordinates concurrent operations without blocking incorrectly.
- How: Runs this case body when its channel operation is ready (or runs default immediately).
- L217: