Under the hood, each service record has an associated fiber, a
lightweight execution thread and shepherd
as a whole follows
the CSP (Concurrent Sequential Processes) model
(see Introduction in Fibers).
A service’s fiber encapsulates all the state of its corresponding
service: its status (whether it’s running, stopped, etc.), its “running
value” (such as the PID of its associated process), the time at which
its status changed, and so on. Procedures that access the state of a
service, such as service-status
, or that modify it, such as
start-service
(see Interacting with Services), merely send a
message to the service’s associated fiber.
This pattern follows the actor model: each of these per-service fibers is an actor6. There are several benefits:
There are other actors in the code, such as the service registry (see Service Registry). Fibers are used pervasively throughout the code to achieve concurrency.
Note that Fibers is set up such that the shepherd
process has
only one POSIX thread (this is mandated by POSIX for processes that call
fork
, with all its warts), and fibers are scheduled in a
cooperative fashion. This means that it is possible to block the
shepherd
process for instance by running a long computation or by
waiting on a socket that is not marked as SOCK_NONBLOCK
. Be
careful!
We think this programming model makes the code base not only more robust, but also very fun to work with—we hope you’ll enjoy it too!
There is one noteworthy difference compared to the actor model. In the “real” actor model, messaging among actors is asynchronous; in the CSP model, messaging is synchronous, which means that the sender and receiver must always rendezvous and their send/receive operation blocks until the other end is here to receive/send the message. See Context in Fibers, for more info.