Under the hood, each service record has an associated fiber, a
lightweight execution thread (see Introduction in Fibers).
This 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 actor. 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!