Processes
A process is an instance of a program on a node. Programs
are compiled into a node; processes are the live thing doing work. A process
exists from the moment it is created: it has a pid, it appears in ps, and it
answers to kill. It is either running or suspended - created
suspended, and running it lifts the suspension.
ps lists the processes inside the node - Dusk's own, like docker ps, not
the host operating system's - kill signals one by pid, and waitpid waits for
one to exit.
What you implement
A program's runtime is a type implementing ProcessMixin (from dusk_program),
declared #[async_trait(?Send)] - processes are executor-local, like the
namespace they run in. You implement three methods:
with_context(ctx)- build the process from itsProcessContext(its pid, the node's namespace, and the caller's program args). It is async, so construction can do setup work.main(signal_receiver, ready)- the process body: do the work, announce readiness, then handle signals.portal()- return the process's typed portal.
The identity methods (pid, program_id, name, version) are generated by
#[derive(Process)] from a #[process_context] field - you don't write them.
Signals
main is given a signal_receiver (a DynamicReceiver<Signal>). A client's
Dusk.kill(pid, signal) is delivered here: wire value 15 arrives as
Signal::Terminate (the request to exit); any other value arrives as
Signal::Unknown(n). Three are exceptions: wire values 7
(Sweep) and 8
(Reap) are handled by the node itself and never
delivered to a process, and Signal::Rerun(args) does not come
from kill at all - it arrives when someone builds a process at a pid this one
already holds. The common shape is a loop on
signal_receiver.receive().await that returns from main on Terminate. A
process that also does ongoing work selects that work against the receiver.
Readiness
main is also given a ready handle. A process announces it can accept portal
calls by sending true on it. A client's process.portal() resolves only once
readiness fires, so a client can call into a process the moment it is reachable
without racing its startup.
Daemon vs in-session
Dusk.process creates a process and registers it in the namespace, suspended.
The client then chooses the process's lifetime:
Dusk.run(process)spawns the process as its own task on the node, so it outlives the client session that created it. This is how long-lived processes (and the node's owninit) run.process.run()runs the process inside the calling session, so it is torn down when that session ends.
Running a process that is already running does nothing. A process that has
exited - or was terminated while suspended, which has nothing running to deliver
a signal to - stays in the namespace with its exit status until something
waitpids it, the way a Unix zombie does. waitpid is what takes a process
out; so does the Reap signal, for a caller that
wants the process gone without reading its exit status. A process that was
created and never run is not a zombie and has no exit status to read, so
Sweep is what takes that one out. The runtime machinery behind this is internal; see
Architecture. A
process that wants to keep running after a one-shot shell command does so through
a shell convention, not a core process mechanism.