daemon

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package daemon provides background-process management for `otelop start`: a state directory under $XDG_STATE_HOME, a metadata file that doubles as the PID anchor, and the Caddy-style re-exec primitive used to spawn a detached child while still reporting bind errors back to the parent via a synchronisation pipe.

Index

Constants

View Source
const (
	// EnvDaemonized marks the child that has been re-exec'd as the
	// detached daemon. Presence of this variable is how the child
	// distinguishes itself from the foreground invocation that spawned it.
	EnvDaemonized = "OTELOP_DAEMONIZED"

	// EnvStateDirOverride lets tests and advanced users point the state
	// directory somewhere other than $XDG_STATE_HOME/otelop.
	EnvStateDirOverride = "OTELOP_STATE_DIR"
)

Variables

This section is empty.

Functions

func EnsureStateDir

func EnsureStateDir() (string, error)

func IsDaemonChild

func IsDaemonChild() bool

IsDaemonChild reports whether the current process was launched as the detached daemon child of an earlier `otelop start`.

func LockMetadata

func LockMetadata() (*os.File, error)

LockMetadata opens the metadata file and acquires an exclusive advisory flock on it. The returned file MUST be held open for the lifetime of the daemon — closing it releases the lock and makes the daemon look dead to concurrent `otelop status` / `otelop stop` callers. The kernel releases the lock automatically when the holding process exits, which is what makes the check immune to PID recycling.

func LogFile

func LogFile() (string, error)

func MetadataFile

func MetadataFile() (string, error)

func ProcessAlive

func ProcessAlive(pid int) bool

ProcessAlive reports whether the given PID refers to a live process. It uses signal 0 — the kernel returns ESRCH for dead processes and EPERM if the process exists but is owned by another user (which we still count as alive for our purposes).

func ReadyPipe

func ReadyPipe() *os.File

ReadyPipe returns the inherited ready pipe when the current process was launched via Spawn. Returns nil otherwise — callers can pass the result to SignalReady/SignalError unconditionally.

func RemoveState

func RemoveState() error

RemoveState deletes the metadata file. Missing files are not an error.

func SignalError

func SignalError(f *os.File, err error)

func SignalReady

func SignalReady(f *os.File)

func Spawn

func Spawn(ctx context.Context, logPath string) error

Spawn re-execs the current binary as a detached child and blocks until the child reports ready (or an error) via an inherited pipe. stdout/stderr of the child are redirected to logPath so panics after ready are still captured.

Follows the Caddy pattern: one fork, setsid to detach from the terminal, and an OS pipe for ready/error synchronisation. The second fork of the classical daemon dance is skipped — otelop never opens /dev/tty, so the "don't acquire a controlling terminal" guarantee is not needed.

func StateDir

func StateDir() (string, error)

StateDir returns the directory used for daemon logs and metadata. It honours OTELOP_STATE_DIR and XDG_STATE_HOME, falling back to ~/.local/state/otelop on both macOS and Linux for consistency with other dev tooling.

func StopAndWait

func StopAndWait(pid int, timeout time.Duration) error

StopAndWait sends SIGTERM to the process, then polls until it exits or timeout elapses. Returns nil if the process was already gone.

func WriteMetadata

func WriteMetadata(meta Metadata) error

WriteMetadata atomically writes the metadata file via temp + rename so concurrent readers never observe a partial write.

Types

type Metadata

type Metadata struct {
	PID           int       `json:"pid"`
	StartedAt     time.Time `json:"startedAt"`
	HTTPAddr      string    `json:"httpAddr"`
	OTLPGRPCAddr  string    `json:"otlpGrpcAddr"`
	OTLPHTTPAddr  string    `json:"otlpHttpAddr"`
	ProxyURL      string    `json:"proxyUrl"`
	ProxyProtocol string    `json:"proxyProtocol"`
	Version       string    `json:"version"`
}

Metadata captures the tiny bit of state the parent and sibling invocations need without querying the running process. Live counters and uptime are intentionally kept out of this file — they come from the GraphQL status query so we have a single source of truth.

func ReadMetadata

func ReadMetadata() (*Metadata, error)

ReadMetadata loads the metadata file. It returns (nil, nil) when the file does not exist — callers should treat "no metadata" as "not running" rather than an error.

func Running

func Running() (*Metadata, bool, error)

Running reports whether an otelop daemon is currently active in this state directory. It combines ReadMetadata with an advisory flock probe on the metadata file, which is immune to PID recycling: the kernel releases the lock only when the holding process exits, so `locked == true` always means the real daemon is still alive.

Return shapes:

  • (nil, false, nil): no metadata file — daemon is not running.
  • (meta, false, nil): metadata exists but no lock holder — the daemon died uncleanly. Callers usually want to clean up the stale file.
  • (meta, true, nil): daemon is alive and holds the lock. meta.PID is safe to signal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL