Signals package¶
The Signals package provides support for handling Unix style signals with capability security and support for multiple handlers per signal.
Overview¶
For each signal that you want to handle, you need to create a SignalHandler
and a corresponding SignalNotify object. Multiple SignalHandler actors can
be registered for the same signal — up to 16 per signal number — and all
registered handlers will be notified when the signal is received, in no
particular order. A handler that cannot be registered (the limit is reached,
or the runtime fails to register with the operating system) is notified via
its notify's registration_failed, with the reason, and automatically
disposed; apply will not have run.
Signal handling requires a SignalAuth capability derived from AmbientAuth,
consistent with how other I/O primitives in the standard library handle
resource access.
Signal numbers must be validated through MakeHandleableSignal before they can
be used with SignalHandler. Validation is a per-platform whitelist of
signals that can be meaningfully handled via the ASIO mechanism: it rejects
fatal signals (SIGILL, SIGTRAP, SIGABRT, SIGFPE, SIGBUS, SIGSEGV),
uncatchable signals (SIGKILL, SIGSTOP), SIGUSR2 (reserved by the Pony
runtime for scheduler sleep/wake, so a handler could never fire), and any
number outside the whitelist.
Example program¶
use "constrained_types"
use "signals"
actor Main
new create(env: Env) =>
let auth = SignalAuth(env.root)
match MakeHandleableSignal(Sig.term())
| let sig: HandleableSignal =>
// Multiple handlers for the same signal
SignalHandler(auth, LogHandler(env.out), sig)
SignalHandler(auth, CleanupHandler(env.out), sig where wait = true)
| let _: ValidationFailure =>
env.err.print("Cannot handle this signal")
end
class LogHandler is SignalNotify
let _out: OutStream
new iso create(out: OutStream) =>
_out = out
fun ref apply(count: U32): Bool =>
_out.print("Signal received, count: " + count.string())
true
class CleanupHandler is SignalNotify
let _out: OutStream
new iso create(out: OutStream) =>
_out = out
fun ref apply(count: U32): Bool =>
_out.print("Cleaning up...")
false
fun ref disposed() =>
_out.print("Cleanup handler disposed")
Signal portability¶
The Sig primitive provides support for portable signal handling across Linux,
FreeBSD and OSX. On Windows only the subset of signals the C runtime supports is
available: the accessors that do not depend on the platform (such as Sig.int()
for SIGINT) work, while platform-specific accessors (for example
Sig.trap()) raise a compile error there. A few platform-independent
accessors (such as Sig.hup()) compile on Windows but name signals it does
not have. Validation resolves this: on Windows only Sig.int() and
Sig.term() are accepted by MakeHandleableSignal — everything else, including
signals the C runtime knows about but treats as fatal (such as SIGABRT),
is rejected.
Disposing handlers¶
Disposing a SignalHandler unsubscribes it from the signal. This is important
because the signal dispatch mechanism holds a reference to each subscriber —
without explicit disposal, handlers will never be garbage collected. If a
SignalHandler is created with wait = true, disposing it (or returning
false from the notifier) is required to allow the program to terminate.
Note that raising a signal after every handler for it has been disposed
delivers it to the disposition the signal had before it was first handled —
the operating system default for most signals (process death for the
terminating ones), and SIG_IGN for one the runtime keeps ignored, like
SIGPIPE.