Leak-free external process execution.
Every command runs under a small Rust shim (a port program, not a NIF)
that places the child in its own process group via setsid and kills
the whole group, SIGTERM then SIGKILL, when the run times out or the
BEAM dies. The shim detects BEAM death as stdin EOF, so cleanup happens
even on kill -9 of the VM.
Why not System.cmd in a Task?
Task.shutdown closes the Erlang port, and closing a port closes pipes;
it sends no signal. The external process runs on until it next touches a
closed pipe, and its children are never signaled at all. See the process
groups guide for the full mechanism.
Modes
Forcola.run/2- bounded one-shot run, mandatory timeoutForcola.Stream- line-by-line output as anEnumerableForcola.Daemon- long-running server under a supervision treeForcola.Duplex- bidirectional stdin/stdout session
Maintainers of CLI wrapper libraries who want to offer Forcola-backed execution without a mandatory dependency: see the adoption guide.
Summary
Types
Errors a bounded run can return.
Functions
Run argv ([binary | args]) to completion under the shim.
Types
@type run_error() :: {:timeout, Forcola.Result.t()} | {:spawn, term()}
Errors a bounded run can return.
The spawn reason is one of :shim_not_found, a reason string reported
by the shim, or {:shim_exited, %Forcola.Result{}}; see run/2.
Functions
@spec run( [String.t(), ...], keyword() ) :: {:ok, Forcola.Result.t()} | {:error, run_error()}
Run argv ([binary | args]) to completion under the shim.
Options
:timeout_ms- required. On expiry the child's process group is killed (SIGTERM, then SIGKILL after the kill grace) and{:error, {:timeout, partial_result}}is returned with output captured so far. The group is confirmed dead before the call returns, with one exception: if the shim itself never reports back, an Elixir-side backstop returns a result whose status is{:signal, :unconfirmed}, meaning death was not confirmed (seeForcola.Result). A child that exits exactly at the timeout boundary can be reported as a timeout whose result carries the normal exit status, includingstatus: 0.:kill_grace_ms- SIGTERM-to-SIGKILL grace in milliseconds, default5_000. Also accepted byForcola.Stream.lines/2.:cd- working directory.:env- list of{name, value}strings.:merge_stderr- route stderr into stdout; defaultfalse.
Any exit status is {:ok, %Forcola.Result{}}; callers branch on
:status. A non-zero exit is a result, not an error.
Spawn errors
{:error, {:spawn, :shim_not_found}}- no shim binary exists for this target (neither downloaded nor built).{:error, {:spawn, reason}}wherereasonis a string - the shim reported the spawn failure, for example a missing or non-executable command.{:error, {:spawn, {:shim_exited, %Forcola.Result{}}}}- the shim exited without reporting an exit or error, for example because it was SIGKILLed. The result's status is{:signal, :unconfirmed}. A SIGKILLed shim gets no chance to kill the group, so the child may survive, reparented to pid 1.