Documentation
¶
Overview ¶
Package proc is a stdlib-only fabric slice of the spec contract module (github.com/opencharly/spec/proc, #55 step1) holding generic host-process lifecycle + launch plumbing: catchable-signal-triggered cleanup hooks, a stale-temp-file sweep, the current-binary subcommand runner, and the process-group shutdown + remote-launch-command renderers (launch.go). It is a host primitive a plugin physically cannot hold as a shared library, so by #55 Rule 1 it lives in a contained spec slice (deps: os/os-signal/os-exec + spec value types + spec.ShellQuote — stdlib-weight only, Rule-2-free) that charly core AND every plugin can import directly, without pulling any mechanism kit. Relocated here from sdk/proclifecycle (cleanup + run) and sdk/kit/process.go (the launch helpers) — a genuinely shared leaf.
Index ¶
- Constants
- Variables
- func InstallSignalHandler()
- func RegisterShutdownHook(fn func())
- func RegisterTempCleanup(path string)
- func RemoteLaunchCommand(launch spec.ProcessLaunch) string
- func ShutdownProcessGroup(cmd *exec.Cmd, stdin io.Closer, done <-chan struct{})
- func SortedEnvPairs(env map[string]string) []string
- func SweepStaleTemps()
- func UnregisterTempCleanup(path string)
Constants ¶
const ProcessShutdownGrace = 2 * time.Second
ProcessShutdownGrace bounds each stage of the graceful process-group shutdown (ShutdownProcessGroup). Named + documented, not a tuned magic value: it is the guarantee that closing a stdio-carried process ALWAYS terminates instead of wedging the caller on a child that ignores its stdin EOF.
Variables ¶
var RunCharlySubcommand = func(args ...string) error { exe := os.Args[0] cmd := exec.Command(exe, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }
RunCharlySubcommand shells out to the CURRENT binary (os.Args[0]) with args, inheriting stdin/stdout/stderr. Relocated from charly core (run_subcommand.go) as a stdlib-only host-exec leaf: the host-side update/deploy orchestration (podUpdateCmd, the unified-target Update/Rebuild methods, per-kind R10 sequences, `charly vm cycle`) spawns child `charly <args…>` invocations through it, and using os.Args[0] means an update loop picks up the local build-under-test automatically.
A package var (not a plain func) so tests can stub the child-process boundary (e.g. record the image-build / vm-cp-box calls a deploy makes without actually spawning the binary).
Functions ¶
func InstallSignalHandler ¶
func InstallSignalHandler()
InstallSignalHandler arms the global signal handler exactly once. Subsequent calls are no-ops. On SIGTERM/SIGINT/SIGHUP it runs the registered temp cleanups AND the registered shutdown hooks (plugin-client reaping, etc.), then re-raises the signal so the process exits with the conventional 128+signum status code.
SIGKILL is not catchable (kernel-level). Leftover temps from a SIGKILL'd charly are caught by the next invocation's SweepStaleTemps; orphaned plugin servers from a SIGKILL'd charly self-terminate via the plugin SDK's parent-death watch (see plugin/sdk/parentwatch.go).
func RegisterShutdownHook ¶
func RegisterShutdownHook(fn func())
RegisterShutdownHook registers fn to run on a catchable shutdown signal (SIGTERM/SIGINT/SIGHUP), in the signal handler, before it re-raises the signal and the process exits. It is the package-boundary seam that lets package main reap resources this package's signal handler cannot reference directly — notably charly's connected out-of-process plugin clients (providerRegistry.Close): a Ctrl-C'd / `systemctl stop`ped charly kills its plugin servers instead of orphaning them (the 77h-orphan leak). Hooks run in registration order; each MUST be best-effort and bounded (the handler is synchronous). A nil fn is a no-op.
func RegisterTempCleanup ¶
func RegisterTempCleanup(path string)
RegisterTempCleanup registers path for cleanup on graceful shutdown (SIGTERM/SIGINT/SIGHUP). Existing `defer os.Remove(...)` callers keep working for normal exit; this is a SUPPLEMENTAL safety net for the ungraceful-but-catchable signal path. Empty path is a no-op.
func RemoteLaunchCommand ¶
func RemoteLaunchCommand(launch spec.ProcessLaunch) string
RemoteLaunchCommand renders one exact-argv launch as the single command string OpenSSH must receive: POSIX single-quoting each token preserves the argv boundary and prevents interpolation by the remote login shell. The working directory and environment belong to the TARGET side, so they ride inside the remote command (`cd <dir> && env <K=V>… <argv>`); applying them to the local ssh carrier would conflate controller and target state.
func ShutdownProcessGroup ¶
ShutdownProcessGroup stops one Setpgid child process gracefully-first and reaps it. The escalation ladder is:
- close stdin — the polite EOF signal every stdio-carried protocol answers by exiting on its own;
- one ProcessShutdownGrace for the process to exit;
- SIGTERM to the whole process group — the explicit termination request, so a well-behaved child can still shut down cleanly;
- a second ProcessShutdownGrace;
- only then SIGKILL to the group — the final escalation a child cannot ignore — and reap.
The ladder never opens with a force-kill: SIGKILL orphans nothing (the whole group dies), but it also gives no child the chance to flush protocol state, so it stays the last resort.
done must be closed by the caller's cmd.Wait() monitor once the process is reaped; ShutdownProcessGroup returns only after that, so no zombie or running group member survives the call. Call it once per process — wrap in sync.Once when several paths may close the same process.
func SortedEnvPairs ¶
SortedEnvPairs renders an environment map as a deterministically ordered "KEY=value" slice. Sorting makes launched-process environments and rendered remote commands byte-reproducible across runs (map iteration order is not).
func SweepStaleTemps ¶
func SweepStaleTemps()
SweepStaleTemps removes /tmp/<prefix>* entries that are (a) NOT held open by any running process AND (b) older than sweepSafetyFloor AND (c) owned by the current user. Best-effort: never errors, never blocks. Called once from main().
func UnregisterTempCleanup ¶
func UnregisterTempCleanup(path string)
UnregisterTempCleanup removes a path from the registry. Called by the existing defer-cleanup paths after they successfully os.Remove the file, so the registry doesn't accumulate stale entries in long- running charly processes (e.g. `charly mcp serve`).
Types ¶
This section is empty.