Skip to main content

pkg/gateway/daemon/daemon.go

Source

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/Stop entrypoints 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 0 to 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

pkg/gateway/daemon/daemon.go#L3
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

pkg/gateway/daemon/daemon.go#L20
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

pkg/gateway/daemon/daemon.go#L27
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.

pkg/gateway/daemon/daemon.go#L33
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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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.
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (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.

pkg/gateway/daemon/daemon.go#L68
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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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.
  • 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 return statement (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.

pkg/gateway/daemon/daemon.go#L91
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.
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 for loop 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 return statement (possibly returning values).
      • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
      • 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.
  • 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 return statement (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.

pkg/gateway/daemon/daemon.go#L139
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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal 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.

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.

pkg/gateway/daemon/daemon.go#L163
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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
      • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (possibly returning values).
  • 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 return statement (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.

pkg/gateway/daemon/daemon.go#L187
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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal and uses it as a value (for example, as an HTTP handler or callback).
  • 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 return statement (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 func literal 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.

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.

pkg/gateway/daemon/daemon.go#L213
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 select statement 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).

Start Here

Guides

Reference

Neighboring source