qsu's primary function is to provide a service runtime layer that sits between the operating system's service subsystem and the application server code. It provides abstractions that allow a server to behave in a unified way, regardless of the actual service subsystem type (it even strives to support the same interface and semantics when running as a foreground process).
In order to write a service application, the developer first chooses a runtime
to integrate against. This basically refers to choosing whether the service
runs async or non-async code. Next they implement a runtime type specific
trait, which has three methods init()
, run()
and shutdown()
.
Some service subsystems have special "startup" and "shutdown" phases. The
service handler's trait methods init()
and shutdown()
correspond to these
phases, and qsu will report (as appropriate) to the service subsystem that
these phases have been entered as the appropriate callback methods are called.
An application also needs to set up a service event handler. This is a closure
that will receive events from the service subsystem. When running as a
foreground process, qsu simulates the same behavior so the application will
receive the same events. Specifically, when a Window Service is stopped, a
SvcEvt::Shutdown(_)
event will be received by the event handler. When a
service application is run in foreground mode, pressing Ctrl+C or Ctrl+Break
will cause the same SvcEvt::Shutdown(_)
to be sent.
Once the service handler trait has been implemented and the service event
closure has been created, the qsu service runtime is called, passing along the
two handlers. At this point qsu will initialize logging and then call the
service handler's init()
implementation, and if successful it will call its
run()
implementation and wait until it returns before calling the
shutdown()
impelementation and then finally terminate the runtime.
Applications must themselves translate shutdown events received by the service event handler to an actual termination of the service application. This is typically done using channels, or cancellation tokens.
Errors
There are several application callbacks in qsu; primarily in the service
application runtime, but also in the argument parser. It is recognized that
service applications that encounter fatal errors may want to return the
application-specific error back to the original caller (for instance to return
different appropriate exit codes to the calling shell). To allow this
callbacks return an error type CbErr<ApEr>
which is generic over an
application-defined type used to carry the application error.
However, there are parts of qsu that doesn't have need for this, even parts
that become needlessly complicated having to support the generic parameter, so
there's an Error
type that does not carry the ApEr
generic.
CbErr
can carry the Error
type within one of its variants.
ToDo
- Document argument parser
- Document the service installer facilities