Forcola.Shim (forcola v0.4.0)

Copy Markdown View Source

Locates and speaks to the forcola_shim binary.

The shim is a Rust port program (source under native/forcola_shim). Release builds are published per target to GitHub Releases and fetched at compile time with SHA256 verification, so consumers need no Rust toolchain; a locally built binary in priv/ takes precedence during development.

Wire protocol (BEAM <-> shim), v0, documented in full in native/forcola_shim/src/main.rs:

Frames are {:packet, 4}-style: a 4-byte big-endian length prefix (handled by the Erlang port itself), then a 1-byte tag, then the payload. This module owns the tag constants and the JSON payload shapes for SPAWN/EXIT/ERROR; Forcola.run/2 drives the protocol.

Summary

Functions

Closes the port and drains its remaining relayed messages.

Decodes the contained flag from an EXIT frame payload.

Decodes an ERROR frame payload into its reason string.

Decodes an EXIT frame payload into {status_or_signal, timed_out}.

Decodes the native EXIT report without discarding the observed child status when cleanup was unconfirmed. decode_exit/1 retains its legacy status shape.

Encodes a CREDIT frame payload: an 8-byte big-endian byte count.

Encodes a SPAWN frame payload from Forcola.run/2 options.

Opens the shim binary as a port, framed with {:packet, 4}.

Absolute path to the shim binary for the current target.

Sends a tagged frame to the shim port.

Functions

close(port)

@spec close(port()) :: :ok | {:error, :guard_close_timeout}

Closes the port and drains its remaining relayed messages.

Call once from the process that opened the port, after consuming its result. The OS port is closed synchronously; draining normally finishes immediately. If the guard itself fails to finish, draining is bounded by five seconds and returns {:error, :guard_close_timeout}.

decode_contained(payload)

@spec decode_contained(binary()) :: boolean()

Decodes the contained flag from an EXIT frame payload.

true when Linux cgroup v2 containment was actually active for the run; false on the default path, on fallback (macOS, no cgroup v2, or no delegated subtree), and whenever the field is absent (older shim). Reports which kill mechanism was used.

decode_error(payload)

@spec decode_error(binary()) :: String.t()

Decodes an ERROR frame payload into its reason string.

decode_exit(payload)

@spec decode_exit(binary()) ::
  {non_neg_integer() | {:signal, atom() | non_neg_integer()}, boolean()}

Decodes an EXIT frame payload into {status_or_signal, timed_out}.

A new shim reports confirmed: false when its bounded cleanup probes still observed a live process group or cgroup. That maps to {:signal, :unconfirmed} in every execution mode. The field defaults to true when absent for compatibility with older shims.

decode_exit_report(payload)

@spec decode_exit_report(binary()) :: map()

Decodes the native EXIT report without discarding the observed child status when cleanup was unconfirmed. decode_exit/1 retains its legacy status shape.

encode_credit(bytes)

@spec encode_credit(non_neg_integer()) :: binary()

Encodes a CREDIT frame payload: an 8-byte big-endian byte count.

Grants the pump selected by the frame tag that many more bytes of read budget. Forcola.Stream uses stdout credit; bounded Forcola.Duplex grants separate stdout and stderr credit.

encode_spawn(argv, opts)

@spec encode_spawn(term(), keyword()) :: binary()

Encodes a SPAWN frame payload from Forcola.run/2 options.

Shared by all four modes. Besides :cd/:env/:merge_stderr/:timeout_ms/ :kill_grace_ms and the pty options, it threads :user and :group (each a string name or an integer id) through to the shim so the child can be run as a different user; see Forcola.run/2 for the semantics.

:cgroup opts into Linux cgroup v2 containment. Only added to the payload when truthy, so the default SPAWN payload is unchanged; the shim defaults it to false when the key is absent. See Forcola.run/2 for the Linux-only, delegation-required, graceful-fallback semantics.

:window_bytes opts into demand-driven backpressure on the child's stdout (see Forcola.Stream.lines/2). Only added to the payload when present, so the default SPAWN payload is unchanged; the shim gates its stdout pump when the field is present and reads eagerly otherwise. Duplex pull mode also supplies :stderr_window_bytes and :strict_output so both pumps stay gated until output is consumed or explicitly discarded.

open(opts \\ [])

@spec open(keyword()) :: {:ok, port()} | {:error, term()}

Opens the shim binary as a port, framed with {:packet, 4}.

The caller controls the returned port: send it SPAWN/STDIN/EOF/KILL frames via send_frame/2 and receive {port, {:data, <<tag, payload::binary>>}} messages for STDOUT/STDERR/EXIT/ERROR frames.

A guard process owns the port and relays its messages in order. It monitors the caller and closes the port if the caller dies. This retains EOF-driven process cleanup without letting a broken shim's port errors (such as :epipe) kill the caller or change the caller's exit-trapping behavior. When finished, the opening process should call close/1 to close the port and drain the guard's remaining messages.

Accepts the trusted :shim_path override documented in path/1. Synchronous operating-system errors opening the port return {:error, {:shim_start_failed, reason}}.

path(opts \\ [])

@spec path(keyword()) ::
  {:ok, Path.t()} | {:error, :not_found | {:invalid_shim_path, atom()}}

Absolute path to the shim binary for the current target.

Returns {:error, :not_found} if no binary has been built or downloaded yet. An explicit :shim_path overrides discovery and must name an absolute path to a regular file with executable permissions. Invalid overrides return {:error, {:invalid_shim_path, reason}}.

The override is trusted executable code. The caller must supply the matching Forcola shim for this version and target, protect the file and its ancestor directories from replacement, and own its lifetime and cleanup. Symlinks are followed and must satisfy the same trust contract. Validation does not authenticate the binary or prevent path replacement.

send_frame(port, tag, payload \\ "")

@spec send_frame(port(), non_neg_integer(), iodata()) :: boolean()

Sends a tagged frame to the shim port.

Returns false if the port has already closed. The caller still receives the terminal port event and handles it through its ordinary shim-death path.