Forcola.Daemon (forcola v0.1.0)

Copy Markdown View Source

A long-running external server process under a supervision tree.

For processes that are supposed to outlive a single call (redis-server, a dev proxy) but must never outlive their supervisor. The child runs in its own process group; when this GenServer terminates for any reason, including supervisor shutdown and owner crash, the shim kills the group (SIGTERM, then SIGKILL after the kill grace) and the terminate blocks until the shim confirms the group is dead. If the daemon process is killed brutally (no terminate), the port closes, the shim sees stdin EOF, and the group is killed anyway; the same path covers BEAM death.

children = [
  {Forcola.Daemon,
   argv: ["redis-server", "--port", "6399"],
   name: MyApp.Redis}
]

Unlike bounded runs, a daemon has no :timeout_ms; its bound is its supervisor. Passing :timeout_ms raises ArgumentError.

Options

  • :argv - required, [binary | args] as in Forcola.run/2.

  • :name - optional GenServer registration name.
  • :cd, :env, :merge_stderr - as in Forcola.run/2.
  • :kill_grace_ms - SIGTERM-to-SIGKILL grace, default 5_000.
  • :output - where child output goes; see below. Default :logger.
  • :log_output - Logger level for output: :logger, default :info.
  • :log_prefix - string prepended to each logged line, default "".
  • :ready - optional zero-arity readiness check; see below.
  • :ready_timeout_ms - readiness deadline, default 5_000.
  • :ready_poll_ms - readiness poll interval, default 100.

Output handling

  • output: :logger (default) - stdout and stderr are logged line by line at the :log_output level, prefixed with :log_prefix. A partial line held across frames is logged once its newline arrives (or at termination).
  • output: fun - a 2-arity function called as fun.(:stdout | :stderr, chunk) with raw chunks, from the daemon process.
  • output: {:send, pid} - pid receives {Forcola.Daemon, daemon_pid, {:stdout | :stderr, chunk}} messages.

With merge_stderr: true everything arrives as :stdout.

Readiness

With ready: fun, init polls fun.() (every :ready_poll_ms) until it returns a truthy value, so start_link and supervisor startup block until the server accepts connections:

{Forcola.Daemon,
 argv: ["redis-server", "--port", "6399"],
 ready: fn -> match?({:ok, _}, :gen_tcp.connect(~c"localhost", 6399, [])) end}

If the check does not pass within :ready_timeout_ms, or the child exits first, the group is killed and start_link returns {:error, :ready_timeout} or {:error, {:exited_before_ready, reason}}.

Without :ready, start_link returns as soon as the spawn request is written to the shim: it succeeds without any proof that the shim or the child is alive, and a failed spawn only surfaces later as a daemon exit. Use :ready for any daemon whose availability matters.

Exit and restart behavior

When the child exits on its own, the daemon stops: status 0 stops :normal, a non-zero status stops {:exit_status, n}, and death by signal stops {:exit_signal, n}. Under restart: :permanent any of these restarts the daemon; under :transient only the abnormal ones do.

Summary

Types

Where child output is routed.

Functions

Returns a specification to start this module under a supervisor.

Starts a daemon running opts[:argv] under the shim.

Types

output()

@type output() :: :logger | (:stdout | :stderr, binary() -> any()) | {:send, pid()}

Where child output is routed.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts a daemon running opts[:argv] under the shim.

See the module docs for options. Raises ArgumentError on invalid options (missing :argv, a :timeout_ms, a bad :output shape).