process

package
v0.19.7 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 6 Imported by: 0

README

process

import "github.com/kitsunium/sdk/pkg/v1/proc/process"

Package process is the public facade for the SDK's keystone process-spawn primitive: it starts a child process under explicit credentials, in its own process group and session, and returns a handle that can wait for exit, signal the leader or its whole group, and stop the group gracefully with a SIGTERM→SIGKILL escalation.

It is a thin facade over internal/service/proc/exec. The public types are aliases of the internal/core/proc port, so a value built here is the same type the service layer consumes — no conversion, no parallel hierarchy.

Spec → systemd directive mapping
Spec.Path / Args / Dir / Env   ExecStart= / WorkingDirectory= / Environment=
Spec.User / Group / Groups     User= / Group= / SupplementaryGroups=
Spec.Setpgid                   KillMode=control-group (group-killable tree)
Spec.Setsid                    a new session, detached from the parent tty
Spec.Nice                      Nice=
Spec.OOMScoreAdj               OOMScoreAdjust=
Spec.Umask                     UMask=        (applied via trampoline)
Spec.Rlimits                   Limit*=       (applied via trampoline)
Environment, and finding the executable

A nil Spec.Env yields an empty environment — the child never silently inherits the supervisor's, so a spawned service starts from a known state. Pass an explicit slice (including os.Environ()) to inherit deliberately.

Spec.Path may be a bare name. "go" is searched in the PATH the CHILD will see — the PATH entry of Spec.Env whenever it names one, even empty, the parent's only when it names none — with os/exec's rules: the first executable in PATH order wins, and a match found only through a relative entry ("." or an empty one) is refused with exec.ErrDot, which the returned SpawnFailed wraps (errors.Is answers). A path with a separator is taken as written.

Standard streams

Spec.Stdio selects how the child's stdin/stdout/stderr are wired: StdioInherit (the default) shares the parent's streams; StdioNull discards the child's output and gives it an immediate-EOF stdin; StdioCapture connects Spec.Stdout and Spec.Stderr (any io.Writer) and Spec.Stdin (any io.Reader), with a nil stream falling back to the null device for that one stream. In capture mode every byte the child writes reaches the writers before Wait returns, and the copier goroutines terminate at EOF (no leak). It composes with Setpgid/Setsid and credential drop. Stdin should be EOF-terminating.

Stopping a process group

Stop sends the chosen signal to the entire process group, waits up to grace for exit, then escalates to SIGKILL on the group. Because the child leads its own group (Spec.Setpgid), forked grandchildren are reached too — no survivor is left behind.

Example
spec := process.Spec{
	Path:    "/bin/sh",
	Args:    []string{"sh", "-c", "sleep 1000 & wait"},
	Setpgid: true,
}
p, err := process.Start(context.Background(), spec)
if err != nil {
	// err carries a central proc sentinel code (errs.HasCode).
	return err
}
// Graceful stop: SIGTERM to the group, escalate to SIGKILL after 5s.
if err := p.Stop(context.Background(), 5*time.Second, process.SIGTERM); err != nil {
	return err
}
exit, err := p.Wait()
_ = exit // exit.Code / exit.Signal / exit.UserTime / exit.MaxRSS
Resource limits and umask

The Go runtime exposes no hook to run setrlimit(2) or umask(2) in the child between fork and exec, so Start honours Spec.Rlimits and Spec.Umask via a re-exec trampoline: when either is set, Start spawns this binary as a tiny self-trampoline that applies the limits in the fresh child, then execs the real target — so the target starts already under its limits, with no cgo and no dependency. An Rlimit naming a resource with no RLIMIT_* mapping on the platform (NPROC, MEMLOCK — absent from stdlib syscall) is still rejected up front with UnknownResource; a limit the kernel refuses to set (e.g. raising a hard cap unprivileged) fails the spawn rather than running the child unconfined. Nice and OOMScoreAdj are applied post-start on the live pid and surface a typed error if the host refuses.

Platforms

Unix gets everything above. Windows spawns too, through CreateProcess: stdio wiring, Setpgid (a new console process group), Spec.Rlimits (enforced by a Job Object) and the PATH search (with PATHEXT) all work, while the fields with no Windows meaning at this layer — User/Group/Groups, Umask, Nice, OOMScoreAdj, ExtraFiles, CgroupPath — are refused with UnsupportedPlatform rather than dropped. There, SignalGroup reaches the leader only and Stop's escalation is TerminateProcess. Every other platform returns UnsupportedPlatform from Start; the package compiles everywhere.

The process itself

Everything above acts on a child. Self and Build read the process they run in, on every platform, and never fail:

stats := process.Self()
stats.Goroutines, stats.HeapBytes, stats.GCPauses.Quantile(0.99), stats.CPUTime

build, ok := process.Build()
sdk, found := build.Module("github.com/kitsunium/sdk")
// sdk.Version is a release ("v0.4.6"), or "" with sdk.Revision/sdk.Time
// for a pseudo-version, or "" with sdk.Local for a directory.

CPUTime is the kernel's count (getrusage) where the platform has one and the Go runtime's estimate elsewhere — refreshed only at a garbage collection; Stats.CPUEstimated says which. The distributions and every counter are cumulative since the process started — subtract two snapshots for a window.

A module's recorded version conflates three things, and Module keeps them apart: a RELEASE in Version, a COMMIT in Revision and Time (a pseudo-version names one, and so does the main module's version-control stamp), and a DIRECTORY in Local and Dir (a replace, a workspace module). What is in a local directory NOW is a question for the framework's git.Head (ADR 0158).

Package process — the running process itself: what it was built from and what it is doing.

Package process — ergonomic re-exports: the handful of signal constants and resource sentinels callers need to drive Stop/SignalGroup and read typed errors without importing internal/core/proc directly.

Package process — StdioMode re-exports for wiring a child's standard streams.

Index

Constants

const ResourceAS Resource = 4

ResourceAS limits the process virtual address-space size (RLIMIT_AS).

Initialised with github.com/kitsunium/sdk/internal/core/proc.ResourceAS.

const ResourceCPU Resource = 5

ResourceCPU limits CPU time in seconds (RLIMIT_CPU).

Initialised with github.com/kitsunium/sdk/internal/core/proc.ResourceCPU.

const ResourceCore Resource = 3

ResourceCore limits the size of a core dump in bytes (RLIMIT_CORE).

Initialised with github.com/kitsunium/sdk/internal/core/proc.ResourceCore.

const ResourceNoFile Resource = 1

ResourceNoFile limits the highest open file descriptor (RLIMIT_NOFILE).

Initialised with github.com/kitsunium/sdk/internal/core/proc.ResourceNoFile.

const SIGHUP Signal = 1

SIGHUP is the hang-up / reload signal many daemons treat as "reconfigure".

const SIGINT Signal = 2

SIGINT is the interrupt signal (Ctrl-C analogue).

const SIGKILL Signal = 9

SIGKILL is the unignorable kill Stop escalates to after the grace window.

const SIGQUIT Signal = 3

SIGQUIT is the quit-with-core signal.

const SIGTERM Signal = 15

SIGTERM is the polite termination request — the default Stop signal.

const StdioCapture StdioMode = 2

StdioCapture connects the child to Spec.Stdout/Stderr (io.Writer) and Spec.Stdin (io.Reader); a nil stream falls back to the null device for that one stream. The systemd analogue is StandardOutput=/StandardError=/StandardInput=.

Initialised with github.com/kitsunium/sdk/internal/core/proc.StdioCapture.

const StdioInherit StdioMode = 0

StdioInherit shares the parent's stdin/stdout/stderr with the child — the default and the only behaviour before per-process wiring existed.

Initialised with github.com/kitsunium/sdk/internal/core/proc.StdioInherit.

const StdioNull StdioMode = 1

StdioNull connects every stream to the null device: output is discarded and the child's stdin returns EOF immediately.

Initialised with github.com/kitsunium/sdk/internal/core/proc.StdioNull.

func Build

func Build() (info BuildInfo, ok bool)

Build describes what the running binary was built from, and reports false when the binary carries no build information.

func MustStart

func MustStart(ctx context.Context, spec Spec) Process

MustStart is like Start but panics with the typed error when the spawn fails — UnsupportedPlatform off Unix/Windows, InvalidSpec, RlimitFailed, … It is the idiomatic Go MustX opt-in (like regexp.MustCompile) for a consumer that chooses crash-on-failure at its own startup; the SDK itself never panics, and Start is the non-panicking form for normal use. The panic value is the typed error, so a top-level recover() can classify it via errs.CodeOf / HasCode.

func ParseBuild

func ParseBuild(info *debug.BuildInfo) BuildInfo

ParseBuild describes a BuildInfo from elsewhere — one a test builds by hand, or debug.ParseBuildInfo's reading of another binary — exactly as Build describes the running one.

func Self

func Self() Stats

Self takes a snapshot of the running process. It never fails: a figure the platform cannot give is zero, and Stats.CPUEstimated says when CPUTime is the runtime's estimate rather than the kernel's count.

func Start

func Start(ctx context.Context, spec Spec) (proc Process, err error)

Start spawns the process described by spec and returns a live Process handle. It delegates to internal/service/proc/exec; ctx is honoured up to the fork/exec boundary. A bare Spec.Path is searched in the child's PATH (see the package documentation). On Windows the Unix-only Spec fields are refused with UnsupportedPlatform; on platforms that are neither Unix nor Windows, Start itself returns UnsupportedPlatform.

type BuildInfo

BuildInfo is what the running binary was built from: the toolchain, the main package, the main module with its version-control stamp, and every dependency followed through its replacement. It is an alias of the service value.

type BuildInfo = svcself.BuildValue

BuildInfo is an alias of github.com/kitsunium/sdk/internal/service/proc/self.BuildValue, declared as:

type BuildInfo struct {
	// GoVersion is the toolchain that built the binary, e.g. "go1.27.1".
	GoVersion string
	// Path is the import path of the main package, e.g.
	// "github.com/acme/app/cmd/server".
	Path      string
	// Main is the main module.
	Main      ModuleValue
	// Deps are the dependencies linked into the binary, in the order the
	// toolchain recorded them.
	Deps      []ModuleValue
}

func (BuildInfo) Module
func (*BuildInfo) Module(path string) (found ModuleValue, ok bool)

Module returns the module whose path is path — the main module or one of the dependencies — and whether the build contains it.

type Distribution

Distribution is one of the Go runtime's cumulative histograms, read as durations: Count, and Quantile(q) as the upper bound of the bucket that reaches q. It is an alias of the service value.

type Distribution = svcself.DistributionValue

Distribution is an alias of github.com/kitsunium/sdk/internal/service/proc/self.DistributionValue, declared as:

type Distribution struct {
	// Counts[i] is the number of observations in [Buckets[i], Buckets[i+1]).
	Counts  []uint64
	// Buckets are the boundaries, in seconds, one more than Counts. The
	// first may be -Inf and the last +Inf.
	Buckets []float64
}

func (Distribution) Count
func (Distribution) Count() uint64

Count returns how many observations the distribution holds.

func (Distribution) Quantile
func (Distribution) Quantile(q float64) time.Duration

Quantile returns the q-th quantile of the distribution, q in [0, 1]: the UPPER bound of the bucket where the cumulative count reaches q of the total — an upper estimate, which is the honest reading of a bucketed histogram. q outside [0, 1] is clamped, NaN is read as 0, and an empty distribution is zero. A bucket whose upper bound is infinite answers with its lower bound.

type ExitResult

ExitResult is the outcome of a finished process — exit code, terminating signal, and resource usage. It is an alias of the core port's ExitValue.

type ExitResult = coreproc.ExitValue

ExitResult is an alias of github.com/kitsunium/sdk/internal/core/proc.ExitValue, declared as:

type ExitResult struct {
	// Code is the exit status (0–255) when the process exited normally; it is -1
	// when the process was terminated by a signal.
	Code       int
	// Signal is the signal that terminated the process, or the zero Signal when
	// it exited normally.
	Signal     Signal
	// Signaled reports whether the process was terminated by a signal rather
	// than exiting on its own.
	Signaled   bool
	// UserTime is the CPU time spent in user mode.
	UserTime   time.Duration
	// SystemTime is the CPU time spent in kernel mode on the process's behalf.
	SystemTime time.Duration
	// MaxRSS is the peak resident set size in kilobytes, as reported by wait4's
	// rusage; it is 0 when the platform does not supply it.
	MaxRSS     int64
}

func (ExitResult) Success
func (ExitResult) Success() bool

Success reports whether the process exited normally with status zero.

type Limit

Limit is a soft/hard resource-limit pair for setrlimit(2). It is an alias of the core port's LimitValue.

type Limit = coreproc.LimitValue

Limit is an alias of github.com/kitsunium/sdk/internal/core/proc.LimitValue, declared as:

type Limit struct {
	// Soft is the enforced limit; it must not exceed Hard.
	Soft uint64
	// Hard is the ceiling for Soft; raising it typically requires privilege.
	Hard uint64
}

type Module

Module is one module of the running binary, with its release, its commit and its local directory told apart. It is an alias of the service value.

type Module = svcself.ModuleValue

Module is an alias of github.com/kitsunium/sdk/internal/service/proc/self.ModuleValue, declared as:

type Module struct {
	// Time is the commit's time, when known: the main module's vcs.time
	// stamp, or the timestamp a pseudo-version carries. Zero otherwise.
	Time        time.Time
	// Path is the module path the build required, e.g.
	// "github.com/kitsunium/sdk".
	Path        string
	// Version is the released version the code came from, e.g. "v0.4.6", or
	// "" when there is none to tell: a pseudo-version (a commit, in Revision),
	// a module built from a local directory, or a main module built without
	// one. A "+dirty" suffix is not kept here; it is what Modified says.
	Version     string
	// Revision is the version-control commit the code came from, when known:
	// the main module's vcs.revision stamp (a full object name), or the
	// commit a pseudo-version names (its twelve-character prefix).
	Revision    string
	// Dir is the directory a replace directive pointed the module at, as the
	// toolchain recorded it — relative to the main module, or to the
	// workspace root in workspace mode — or "" when it was not replaced by a
	// directory. A workspace module is Local with no Dir: the toolchain does
	// not record where it was.
	Dir         string
	// Replacement is the module path a replace directive substituted, when it
	// substituted another module rather than a directory; Version is then
	// that module's.
	Replacement string
	// Local reports that the code came from a directory on the build machine
	// rather than from a published version: a directory replacement, a
	// workspace module, or a main module built inside its own source tree.
	Local       bool
	// Modified reports that the build included uncommitted changes: the main
	// module's vcs.modified stamp, or the "+dirty" its version carried. The
	// toolchain records nothing of the kind for a dependency.
	Modified    bool
}

type Process

Process is the handle to a spawned process: PID, Wait, Signal, SignalGroup, and a group-aware Stop. It is an alias of the core port interface.

type Process = coreproc.Process

Process is an alias of github.com/kitsunium/sdk/internal/core/proc.Process, declared as:

type Process interface {
	// PID reports the process identifier of the leader.
	PID() int
	// Signal delivers sig to the leader process only.
	Signal(sig Signal) error
	// SignalGroup delivers sig to the leader's entire process group (kill(-pgid,
	// sig)), reaching forked grandchildren — the KillMode=control-group analogue.
	SignalGroup(sig Signal) error
	// Stop terminates the process group gracefully: it sends sig, waits up to
	// grace for exit, then escalates to SIGKILL. It returns when the process has
	// exited or ctx is cancelled.
	Stop(ctx context.Context, grace time.Duration, sig Signal) error
	// Wait blocks until the process exits and returns its ExitValue — the real
	// one even when a running Reaper collected the child first. It is safe
	// to call once; concurrent or repeated calls observe the same outcome. Under
	// StdioCapture it returns only after every captured byte has reached the
	// caller's writers; if a writer itself failed, the ExitValue still reports the
	// real exit status and the error is StdioCaptureFailed.
	Wait() (ExitValue, error)
}

func (Process) PID
func (Process) PID() int

PID reports the process identifier of the leader.

func (Process) Signal
func (Process) Signal(sig Signal) error

Signal delivers sig to the leader process only.

func (Process) SignalGroup
func (Process) SignalGroup(sig Signal) error

SignalGroup delivers sig to the leader's entire process group (kill(-pgid, sig)), reaching forked grandchildren — the KillMode=control-group analogue.

func (Process) Stop
func (Process) Stop(ctx context.Context, grace time.Duration, sig Signal) error

Stop terminates the process group gracefully: it sends sig, waits up to grace for exit, then escalates to SIGKILL. It returns when the process has exited or ctx is cancelled.

func (Process) Wait
func (Process) Wait() (ExitValue, error)

Wait blocks until the process exits and returns its ExitValue — the real one even when a running Reaper collected the child first. It is safe to call once; concurrent or repeated calls observe the same outcome. Under StdioCapture it returns only after every captured byte has reached the caller's writers; if a writer itself failed, the ExitValue still reports the real exit status and the error is StdioCaptureFailed.

type Resource

Resource identifies a per-process resource governed by setrlimit(2). It is an alias of the core port type.

type Resource = coreproc.Resource

Resource is an alias of github.com/kitsunium/sdk/internal/core/proc.Resource, declared as:

type Resource int

func (Resource) Known
func (Resource) Known() bool

Known reports whether r is a defined resource other than the zero value.

func (Resource) String
func (Resource) String() string

String reports the canonical lowercase name of r (e.g. "nofile"), or "unknown" for an unrecognised value. The names match the suffix of the systemd Limit* directives.

type Signal

Signal is a typed, platform-portable OS signal. It is an alias of the core port type, so process.SIGTERM and a signal parsed elsewhere compare equal.

type Signal = coreproc.Signal

Signal is an alias of github.com/kitsunium/sdk/internal/core/proc.Signal, declared as:

type Signal int

func (Signal) Int
func (Signal) Int() int

Int reports the raw platform signal number underlying s.

func (Signal) Known
func (Signal) Known() bool

Known reports whether s names a signal in the platform table.

func (Signal) OS
func (Signal) OS() os.Signal

OS bridges s to the os.Signal expected by os/signal and Process.Signal. It is a zero-cost conversion to syscall.Signal — the canonical os.Signal carrier.

func (Signal) String
func (Signal) String() string

String reports the canonical upper-case name of s (e.g. "SIGTERM"). An unrecognised signal renders as "signal <n>" so logs stay unambiguous.

type Spec

Spec is the immutable description of a process to spawn — executable, environment, credentials, isolation topology, and scheduling attributes. It is an alias of the core port type.

type Spec = coreproc.Spec

Spec is an alias of github.com/kitsunium/sdk/internal/core/proc.Spec, declared as:

type Spec struct {
	// Path is the executable to run. Required. A value containing a path
	// separator is a file path, taken as written (a relative one is resolved
	// against Dir). A bare name — "go", "sh" — is searched in the PATH the
	// CHILD will see: the PATH entry of Env whenever Env names one (an
	// explicitly empty PATH= searches nothing), the parent's only when Env
	// names none (including when Env is nil). os/exec's rules apply: the first
	// executable in PATH order wins, and a match found only through a relative
	// entry ("." or an empty one) is refused with exec.ErrDot.
	Path        string
	// Args is the full argv including argv[0]; when empty the service uses
	// [Spec.Path] — the name as written, not the resolved file — as the sole
	// argument.
	Args        []string
	// Dir is the working directory; empty means inherit the parent's.
	Dir         string
	// Env is the explicit environment; nil yields an empty environment, never
	// implicit inheritance, so a spawned service starts from a known state.
	Env         []string
	// User is the name or numeric uid to setuid to; empty leaves the parent's.
	User        string
	// Group is the name or numeric gid to setgid to; empty leaves the parent's.
	Group       string
	// Groups are supplementary group names or gids (systemd SupplementaryGroups=).
	Groups      []string
	// Setpgid places the child in its own process group, enabling group-kill of
	// the whole tree (systemd KillMode=control-group without cgroups).
	Setpgid     bool
	// Setsid starts the child in a new session detached from the controlling tty.
	Setsid      bool
	// Umask, when non-nil, is the file-creation mask applied to the child
	// (systemd UMask=).
	Umask       *int
	// Nice, when non-nil, is the scheduling-priority adjustment applied to the
	// child (systemd Nice=).
	Nice        *int
	// OOMScoreAdj, when non-nil, is the /proc/<pid>/oom_score_adj value applied
	// to the child (systemd OOMScoreAdjust=).
	OOMScoreAdj *int
	// Rlimits are per-resource soft/hard limits applied post-fork, pre-exec
	// (systemd Limit*=); resources unsupported on the platform surface a typed
	// error rather than silently no-op.
	Rlimits     map[Resource]LimitValue
	// CgroupPath, when non-empty, is an already-created cgroup v2 directory the
	// child is placed into BEFORE exec, so controller limits (MemoryMax/TasksMax/
	// …) apply from its first instruction — closing the unconfined window that a
	// post-spawn Group.Add(pid) leaves open (systemd places the unit's payload in
	// its cgroup the same way). The service writes the child's pid to
	// <CgroupPath>/cgroup.procs in the pre-exec trampoline. Linux-only: a non-empty
	// value on any other GOOS surfaces UnsupportedPlatform; a path that is missing,
	// not a cgroup v2 directory, or not delegated surfaces a typed cgroup error
	// rather than spawning unconfined.
	CgroupPath  string
	// Stdio selects how the child's standard streams are wired: StdioInherit
	// (default — share the parent's), StdioNull (discard to the null device), or
	// StdioCapture (use the Stdout/Stderr/Stdin members below). systemd
	// StandardOutput=/StandardError=/StandardInput= is the analogue.
	Stdio       StdioMode
	// Stdout, when Stdio is StdioCapture, receives the child's standard output;
	// a nil writer discards that stream to the null device.
	Stdout      io.Writer
	// Stderr, when Stdio is StdioCapture, receives the child's standard error;
	// a nil writer discards that stream to the null device.
	Stderr      io.Writer
	// Stdin, when Stdio is StdioCapture, is copied to the child's standard input;
	// a nil reader gives the child an immediate-EOF stdin. The reader should be
	// EOF-terminating (a buffer, file, or strings.Reader); the copier closes the
	// child's stdin at EOF and never blocks Wait.
	Stdin       io.Reader
	// ExtraFiles are additional open files inherited by the child, in order,
	// starting at file descriptor 3 (after stdin/stdout/stderr). This is the
	// mechanism behind socket activation (sd_listen_fds): an activator binds
	// listening sockets and passes them here, then sets LISTEN_FDS/LISTEN_PID in
	// Env so the child finds them at fd 3..3+len-1. nil means no extra fds.
	ExtraFiles  []*os.File
}

type Stats

Stats is the running process at one instant: identity, scheduler settings, goroutines, heap, collections with their pause distribution, scheduling latency, and CPU time. Every count is cumulative since the process started. It is an alias of the service value.

type Stats = svcself.StatsValue

Stats is an alias of github.com/kitsunium/sdk/internal/service/proc/self.StatsValue, declared as:

type Stats struct {
	// At is when the snapshot was taken, on the wall clock: this describes
	// the machine, whatever clock the program's own logic runs on.
	At              time.Time
	// Started is when the process started, to within its initialisation.
	Started         time.Time
	// LastGC is when the most recent garbage collection ended; zero before
	// the first one.
	LastGC          time.Time
	// GCPauses is the distribution of stop-the-world pauses caused by the
	// garbage collector.
	GCPauses        DistributionValue
	// SchedLatencies is the distribution of the time goroutines spent ready
	// to run before they ran — the queueing a saturated scheduler adds.
	SchedLatencies  DistributionValue
	// PID is the process identifier.
	PID             int
	// MaxProcs is GOMAXPROCS: how many threads may run Go code at once.
	MaxProcs        int
	// CPUs is how many logical CPUs the process can use.
	CPUs            int
	// Goroutines is how many goroutines exist.
	Goroutines      int
	// HeapBytes is the memory occupied by live and not-yet-swept heap
	// objects.
	HeapBytes       uint64
	// HeapObjects is how many objects occupy HeapBytes.
	HeapObjects     uint64
	// HeapGoalBytes is the heap size at which the next collection starts.
	HeapGoalBytes   uint64
	// MemoryBytes is all the memory the Go runtime has mapped from the
	// system: the heap, the stacks, the runtime's own structures. It is the
	// runtime's number, not the kernel's resident set.
	MemoryBytes     uint64
	// TotalAllocBytes is every byte ever allocated on the heap.
	TotalAllocBytes uint64
	// GCCycles is how many collections have completed.
	GCCycles        uint64
	// CPUTime is the CPU time the process has consumed, user and system.
	CPUTime         time.Duration
	// Uptime is At minus Started.
	Uptime          time.Duration
	// CPUEstimated reports that CPUTime is the Go runtime's estimate rather
	// than the kernel's count: the runtime's total CPU capacity minus its
	// idle time, which counts only threads running Go code and so misses time
	// spent in C code or blocked in the kernel — and which the runtime
	// refreshes only at a garbage collection, so it lags by up to one GC
	// cycle and reads zero before the first. It is true where getrusage(2)
	// does not exist — Windows, Plan 9, wasm — and wherever it failed.
	CPUEstimated    bool
}

type StdioMode

StdioMode selects how a spawned child's standard streams (stdin, stdout, stderr) are wired. It is an alias of the core port type; set it on Spec.Stdio. The zero value is StdioInherit.

type StdioMode = coreproc.StdioMode

StdioMode is an alias of github.com/kitsunium/sdk/internal/core/proc.StdioMode, declared as:

type StdioMode uint8

func (StdioMode) Known
func (StdioMode) Known() bool

Known reports whether m is one of the defined StdioInherit/StdioNull/StdioCapture modes (i.e. m <= StdioCapture).

func (StdioMode) String
func (StdioMode) String() string

String returns the lowercase mode name ("inherit", "null", "capture"), or "stdiomode(N)" for an out-of-range value.

Generated from docs/api by tools/genindex.

Documentation

Overview

Package process is the public facade for the SDK's keystone process-spawn primitive: it starts a child process under explicit credentials, in its own process group and session, and returns a handle that can wait for exit, signal the leader or its whole group, and stop the group gracefully with a SIGTERM→SIGKILL escalation.

It is a thin facade over internal/service/proc/exec. The public types are aliases of the internal/core/proc port, so a value built here is the same type the service layer consumes — no conversion, no parallel hierarchy.

Spec → systemd directive mapping

Spec.Path / Args / Dir / Env   ExecStart= / WorkingDirectory= / Environment=
Spec.User / Group / Groups     User= / Group= / SupplementaryGroups=
Spec.Setpgid                   KillMode=control-group (group-killable tree)
Spec.Setsid                    a new session, detached from the parent tty
Spec.Nice                      Nice=
Spec.OOMScoreAdj               OOMScoreAdjust=
Spec.Umask                     UMask=        (applied via trampoline)
Spec.Rlimits                   Limit*=       (applied via trampoline)

Environment, and finding the executable

A nil Spec.Env yields an empty environment — the child never silently inherits the supervisor's, so a spawned service starts from a known state. Pass an explicit slice (including os.Environ()) to inherit deliberately.

Spec.Path may be a bare name. "go" is searched in the PATH the CHILD will see — the PATH entry of Spec.Env whenever it names one, even empty, the parent's only when it names none — with os/exec's rules: the first executable in PATH order wins, and a match found only through a relative entry ("." or an empty one) is refused with exec.ErrDot, which the returned SpawnFailed wraps (errors.Is answers). A path with a separator is taken as written.

Standard streams

Spec.Stdio selects how the child's stdin/stdout/stderr are wired: StdioInherit (the default) shares the parent's streams; StdioNull discards the child's output and gives it an immediate-EOF stdin; StdioCapture connects Spec.Stdout and Spec.Stderr (any io.Writer) and Spec.Stdin (any io.Reader), with a nil stream falling back to the null device for that one stream. In capture mode every byte the child writes reaches the writers before Wait returns, and the copier goroutines terminate at EOF (no leak). It composes with Setpgid/Setsid and credential drop. Stdin should be EOF-terminating.

Stopping a process group

Stop sends the chosen signal to the entire process group, waits up to grace for exit, then escalates to SIGKILL on the group. Because the child leads its own group (Spec.Setpgid), forked grandchildren are reached too — no survivor is left behind.

Example

spec := process.Spec{
	Path:    "/bin/sh",
	Args:    []string{"sh", "-c", "sleep 1000 & wait"},
	Setpgid: true,
}
p, err := process.Start(context.Background(), spec)
if err != nil {
	// err carries a central proc sentinel code (errs.HasCode).
	return err
}
// Graceful stop: SIGTERM to the group, escalate to SIGKILL after 5s.
if err := p.Stop(context.Background(), 5*time.Second, process.SIGTERM); err != nil {
	return err
}
exit, err := p.Wait()
_ = exit // exit.Code / exit.Signal / exit.UserTime / exit.MaxRSS

Resource limits and umask

The Go runtime exposes no hook to run setrlimit(2) or umask(2) in the child between fork and exec, so Start honours Spec.Rlimits and Spec.Umask via a re-exec trampoline: when either is set, Start spawns this binary as a tiny self-trampoline that applies the limits in the fresh child, then execs the real target — so the target starts already under its limits, with no cgo and no dependency. An Rlimit naming a resource with no RLIMIT_* mapping on the platform (NPROC, MEMLOCK — absent from stdlib syscall) is still rejected up front with UnknownResource; a limit the kernel refuses to set (e.g. raising a hard cap unprivileged) fails the spawn rather than running the child unconfined. Nice and OOMScoreAdj are applied post-start on the live pid and surface a typed error if the host refuses.

Platforms

Unix gets everything above. Windows spawns too, through CreateProcess: stdio wiring, Setpgid (a new console process group), Spec.Rlimits (enforced by a Job Object) and the PATH search (with PATHEXT) all work, while the fields with no Windows meaning at this layer — User/Group/Groups, Umask, Nice, OOMScoreAdj, ExtraFiles, CgroupPath — are refused with UnsupportedPlatform rather than dropped. There, SignalGroup reaches the leader only and Stop's escalation is TerminateProcess. Every other platform returns UnsupportedPlatform from Start; the package compiles everywhere.

The process itself

Everything above acts on a child. Self and Build read the process they run in, on every platform, and never fail:

stats := process.Self()
stats.Goroutines, stats.HeapBytes, stats.GCPauses.Quantile(0.99), stats.CPUTime

build, ok := process.Build()
sdk, found := build.Module("github.com/kitsunium/sdk")
// sdk.Version is a release ("v0.4.6"), or "" with sdk.Revision/sdk.Time
// for a pseudo-version, or "" with sdk.Local for a directory.

CPUTime is the kernel's count (getrusage) where the platform has one and the Go runtime's estimate elsewhere — refreshed only at a garbage collection; Stats.CPUEstimated says which. The distributions and every counter are cumulative since the process started — subtract two snapshots for a window.

A module's recorded version conflates three things, and Module keeps them apart: a RELEASE in Version, a COMMIT in Revision and Time (a pseudo-version names one, and so does the main module's version-control stamp), and a DIRECTORY in Local and Dir (a replace, a workspace module). What is in a local directory NOW is a question for the framework's git.Head (ADR 0158).

Package process — the running process itself: what it was built from and what it is doing.

Package process — ergonomic re-exports: the handful of signal constants and resource sentinels callers need to drive Stop/SignalGroup and read typed errors without importing internal/core/proc directly.

Package process — StdioMode re-exports for wiring a child's standard streams.

Index

Constants

View Source
const (
	// SIGTERM is the polite termination request — the default Stop signal.
	SIGTERM = Signal(syscall.SIGTERM)
	// SIGKILL is the unignorable kill Stop escalates to after the grace window.
	SIGKILL = Signal(syscall.SIGKILL)
	// SIGINT is the interrupt signal (Ctrl-C analogue).
	SIGINT = Signal(syscall.SIGINT)
	// SIGHUP is the hang-up / reload signal many daemons treat as "reconfigure".
	SIGHUP = Signal(syscall.SIGHUP)
	// SIGQUIT is the quit-with-core signal.
	SIGQUIT = Signal(syscall.SIGQUIT)
)

The common control signals as typed process.Signal values, so callers pass process.SIGTERM to Stop/Signal without a syscall import. They equal any Signal parsed from the same name (the underlying value is the platform number).

Variables

This section is empty.

Functions

This section is empty.

Types

type BuildInfo

type BuildInfo = svcself.BuildValue

BuildInfo is what the running binary was built from: the toolchain, the main package, the main module with its version-control stamp, and every dependency followed through its replacement. It is an alias of the service value.

func Build

func Build() (info BuildInfo, ok bool)

Build describes what the running binary was built from, and reports false when the binary carries no build information.

func ParseBuild

func ParseBuild(info *debug.BuildInfo) BuildInfo

ParseBuild describes a BuildInfo from elsewhere — one a test builds by hand, or debug.ParseBuildInfo's reading of another binary — exactly as Build describes the running one.

type Distribution

type Distribution = svcself.DistributionValue

Distribution is one of the Go runtime's cumulative histograms, read as durations: Count, and Quantile(q) as the upper bound of the bucket that reaches q. It is an alias of the service value.

type ExitResult

type ExitResult = coreproc.ExitValue

ExitResult is the outcome of a finished process — exit code, terminating signal, and resource usage. It is an alias of the core port's ExitValue.

type Limit

type Limit = coreproc.LimitValue

Limit is a soft/hard resource-limit pair for setrlimit(2). It is an alias of the core port's LimitValue.

type Module

type Module = svcself.ModuleValue

Module is one module of the running binary, with its release, its commit and its local directory told apart. It is an alias of the service value.

type Process

type Process = coreproc.Process

Process is the handle to a spawned process: PID, Wait, Signal, SignalGroup, and a group-aware Stop. It is an alias of the core port interface.

func MustStart

func MustStart(ctx context.Context, spec Spec) Process

MustStart is like Start but panics with the typed error when the spawn fails — UnsupportedPlatform off Unix/Windows, InvalidSpec, RlimitFailed, … It is the idiomatic Go MustX opt-in (like regexp.MustCompile) for a consumer that chooses crash-on-failure at its own startup; the SDK itself never panics, and Start is the non-panicking form for normal use. The panic value is the typed error, so a top-level recover() can classify it via errs.CodeOf / HasCode.

func Start

func Start(ctx context.Context, spec Spec) (proc Process, err error)

Start spawns the process described by spec and returns a live Process handle. It delegates to internal/service/proc/exec; ctx is honoured up to the fork/exec boundary. A bare Spec.Path is searched in the child's PATH (see the package documentation). On Windows the Unix-only Spec fields are refused with UnsupportedPlatform; on platforms that are neither Unix nor Windows, Start itself returns UnsupportedPlatform.

type Resource

type Resource = coreproc.Resource

Resource identifies a per-process resource governed by setrlimit(2). It is an alias of the core port type.

const (
	// ResourceNoFile limits the highest open file descriptor (RLIMIT_NOFILE).
	ResourceNoFile Resource = coreproc.ResourceNoFile
	// ResourceCore limits the size of a core dump in bytes (RLIMIT_CORE).
	ResourceCore Resource = coreproc.ResourceCore
	// ResourceCPU limits CPU time in seconds (RLIMIT_CPU).
	ResourceCPU Resource = coreproc.ResourceCPU
	// ResourceAS limits the process virtual address-space size (RLIMIT_AS).
	ResourceAS Resource = coreproc.ResourceAS
)

The resource sentinels callers most often set in Spec.Rlimits, re-exported so a Spec can be built without importing internal/core/proc. The full set lives on the core Resource type.

type Signal

type Signal = coreproc.Signal

Signal is a typed, platform-portable OS signal. It is an alias of the core port type, so process.SIGTERM and a signal parsed elsewhere compare equal.

type Spec

type Spec = coreproc.Spec

Spec is the immutable description of a process to spawn — executable, environment, credentials, isolation topology, and scheduling attributes. It is an alias of the core port type.

type Stats

type Stats = svcself.StatsValue

Stats is the running process at one instant: identity, scheduler settings, goroutines, heap, collections with their pause distribution, scheduling latency, and CPU time. Every count is cumulative since the process started. It is an alias of the service value.

func Self

func Self() Stats

Self takes a snapshot of the running process. It never fails: a figure the platform cannot give is zero, and Stats.CPUEstimated says when CPUTime is the runtime's estimate rather than the kernel's count.

type StdioMode

type StdioMode = coreproc.StdioMode

StdioMode selects how a spawned child's standard streams (stdin, stdout, stderr) are wired. It is an alias of the core port type; set it on Spec.Stdio. The zero value is StdioInherit.

const StdioCapture StdioMode = coreproc.StdioCapture

StdioCapture connects the child to Spec.Stdout/Stderr (io.Writer) and Spec.Stdin (io.Reader); a nil stream falls back to the null device for that one stream. The systemd analogue is StandardOutput=/StandardError=/StandardInput=.

const StdioInherit StdioMode = coreproc.StdioInherit

StdioInherit shares the parent's stdin/stdout/stderr with the child — the default and the only behaviour before per-process wiring existed.

const StdioNull StdioMode = coreproc.StdioNull

StdioNull connects every stream to the null device: output is discarded and the child's stdin returns EOF immediately.

Jump to

Keyboard shortcuts

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