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.
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() (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(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(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() 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(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.
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.
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.
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.
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
}
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
}
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(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.
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.
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.
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
}
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
}
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.