subproc

    Synchronous process management for MoonBit: spawn with env/cwd/redirection, waitpid and pidfd waits, kill, procfs sampling and platform probes

    process
    spawn
    pidfd
    procfs
    signal
    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    6 hours ago
    Downloads
    1

    Dependencies

    #chensuiyi/subproc

    Synchronous process management · 同步进程管理

    同步子进程管理:spawn(参数/env/cwd/日志重定向/独立进程组)、waitpid 与 pidfd 等待、进程组信号、procfs 采样(rss/cpu)、平台探针(uname/SO_REUSEPORT/self_exe)。

    • pidfd(Linux ≥ 5.3):内核句柄钉住原始进程,pid 复用不干扰存活/退出判定,支持收养非子进程
    • wait_child_timeout:硬超时,到期 SIGKILL 整组并有界回收
    • instance_env_blob:白名单环境 + 保留变量的 NUL blob
    • 依赖:chensuiyi/fsx、chensuiyi/fndash

    Synchronous child-process management: spawn (argv/env/cwd/log redirection/own process group), waitpid and pidfd waits, process-group signals, procfs sampling (rss/cpu), platform probes (uname/SO_REUSEPORT/self_exe).

    • pidfd (Linux ≥ 5.3): a kernel handle pinned to one process — pid reuse never fools liveness/exit checks, and adopted (non-child) processes are supported
    • wait_child_timeout: hard deadline; on expiry SIGKILLs the group and reaps with a bounded retry
    • instance_env_blob: whitelisted env plus reserved vars as a NUL blob
    • Depends on chensuiyi/fsx and chensuiyi/fndash

    #API

    接口 Interface说明 Description
    spawn(spec)启动子进程返回 pid;Spawn a child, return pid
    wait_child / wait_child_timeout / drain_child回收;Reaping, deadline-bounded variants
    pidfd_open / pidfd_alive / pidfd_exited / wait_pidfdpidfd 生命周期;pidfd lifecycle
    kill_group / pid_alive / SIGTERM / SIGKILL信号;Signalling
    read_rss_kb / read_cpu_ticks / clk_tckprocfs 采样;procfs sampling
    hostname / uname / get_uid / self_exe / probe_reuseport平台探针;Platform probes
    instance_env_blob / base_env_blob环境 blob;Environment blobs
    exit / eprintln / sleep_ms / close运行辅助;Runtime helpers

    License: MIT

    ProcessError

    pub(all) suberror ProcessError {
    Failed(op~ : String, errno~ : Int)
    }

    ProcessError::output

    fn ProcessError::output(self : ProcessError, logger : &Logger) -> Unit

    ProcessError::to_string

    fn ProcessError::to_string(self : ProcessError) -> String

    ChildExit

    pub(all) enum ChildExit {
    Exited(Int)
    Signaled(Int)
    } derive(Eq,
    Debug
    )

    ChildExit::equal

    fn ChildExit::equal(ChildExit, ChildExit) -> Bool

    ChildExit::not_equal

    fn ChildExit::not_equal(x : ChildExit, y : ChildExit) -> Bool

    SpawnSpec

    pub(all) struct SpawnSpec {
    bun_path : String
    script : String
    cwd : String
    out_log : String
    err_log : String
    env_blob : Bytes
    } derive(
    Debug
    )

    SIGKILL

    let SIGKILL : Int

    SIGTERM

    let SIGTERM : Int

    base_env_blob

    fn base_env_blob() -> Bytes

    Whitelisted environment only; used to launch helper processes.

    clk_tck

    fn clk_tck() -> Int

    Clock ticks per second (sysconf(_SC_CLK_TCK)), the divisor for cpu ticks.

    close

    fn close(fd : Int) -> Unit

    Close a pidfd or lock fd. Owned by the supervision layer.

    drain_child

    fn drain_child(pid : Int) -> Unit

    Reap a just-killed child until it is gone (bounded), so a failed spawn attempt never leaves a zombie behind. Returns immediately when the child is not reapable (already reaped, adopted, or not ours).

    eprintln

    fn eprintln(text : String) -> Unit

    Write a line to stderr (for CLI diagnostics; stdout stays machine-usable).

    exit

    fn exit(code : Int) -> Unit

    Exit the process immediately with the given code.

    get_pid

    fn get_pid() -> Int

    get_uid

    fn get_uid() -> Int

    hostname

    fn hostname() -> String

    Host name from the kernel, or "unknown" when it cannot be read. Used to say which machine a notification came from.

    instance_env_blob

    fn instance_env_blob(app_name : String, instance_id : Int, port : Int) -> Bytes

    Minimal instance environment: whitelisted vars from the daemon's own environment plus the reserved app vars, encoded as a NUL-separated "KEY=VALUE" blob with a trailing extra NUL for spawn. Reserved vars are written last, so nothing can override them.

    kill_group

    fn kill_group(pgid : Int, sig : Int) -> Unit raise ProcessError

    pid_alive

    fn pid_alive(pid : Int) -> Bool

    kill(pid, 0): true when the pid exists and we may signal it.

    pidfd_alive

    fn pidfd_alive(fd : Int) -> Bool

    True while the pidfd's process is alive; false once it has exited.

    pidfd_exited

    fn pidfd_exited(fd : Int, pid : Int) -> Bool

    True when the process behind the pidfd has really exited. The pidfd becomes readable on exit, but also on stop events; confirming with pid_alive (ESRCH) rules those out. waitid(P_PIDFD) itself only works for child processes, so this poll-based check is the only reliable exit signal for adopted (reparented) instances.

    pidfd_open

    fn pidfd_open(pid : Int) -> Int raise ProcessError

    Open a pidfd pinned to pid (Linux >= 5.3). The fd always refers to that original process, never to a pid-reused successor.

    platform_error

    fn platform_error() -> String?

    The library is Linux-only. Host binaries refuse to run elsewhere with a clear reason instead of failing later on the first POSIX-specific syscall. Returns None when the platform is supported.

    probe_reuseport

    fn probe_reuseport(port : Int) -> Int

    Ask the kernel whether port accepts a SO_REUSEPORT join: 0 = free (nothing listening yet), 1 = joinable (listener enables reusePort), 2 = held exclusively (reusePort missing), negative = errno.

    read_cpu_ticks

    fn read_cpu_ticks(pid : Int) -> UInt64?

    utime + stime of a process in clock ticks, None when it is gone. A single sample is meaningless on its own; CPU percent comes from two samples taken clk_tck() apart.

    read_rss_kb

    fn read_rss_kb(pid : Int) -> Int?

    VmRSS in kB from /proc//status, None when the process is gone.

    self_exe

    fn self_exe() -> String raise ProcessError

    Absolute path of the running executable (/proc/self/exe).

    sleep_ms

    fn sleep_ms(ms : Int) -> Unit

    spawn

    fn spawn(spec : SpawnSpec) -> Int raise ProcessError

    uname

    fn uname() -> String raise ProcessError

    Kernel name from uname(2), e.g. "Linux".

    wait_child

    fn wait_child(pid : Int, nohang? : Bool) -> (Int, ChildExit)? raise ProcessError

    Reap a child with waitpid. pid = -1 reaps any child. Returns None when nohang is set and no child has exited.

    wait_child_timeout

    fn wait_child_timeout(pid : Int, timeout_ms : Int) -> (Int, ChildExit)?

    Wait for a child with a hard deadline, through a pidfd so the wait cannot be fooled by pid reuse. When the deadline passes the whole process group is SIGKILLed and the reap is retried for a short bounded window, so a helper that hangs (a stuck mount, a wrapper script) can never freeze the single-threaded daemon forever. Returns None when no exit could be observed in time.

    wait_pidfd

    fn wait_pidfd(fd : Int, nohang? : Bool) -> (Int, ChildExit)? raise ProcessError

    Reap a process through a pidfd (waitid P_PIDFD). Works for adopted (non-child) processes too. ECHILD means the exit was already reaped, which is reported as None rather than an error.