As POSIX says:
The
sigaction()function provides a more reliable mechanism for controlling signals; new applications should usesigaction()rather thansignal().
The C standard (C17 7.14.1.1.7) says:
Use of this function in a multi-threaded program results in undefined behavior.
The signal(2) man page of Linux also warns against
using it and says to use sigaction() instead. Noteworthy
points from that say:
The only portable use of
signal()is to set a signal’s disposition toSIG_DFLorSIG_IGN. The semantics when usingsignal()to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.The effects of
signal()in a multithreaded process are unspecified.
While sigaction() is limited to POSIX, but signal() is
in standard C and so available in every OS, I assume that
Windows is the only non-POSIX OS that Radicle might intend
to support (for some time into the future). Since Windows
simply does not have asynchronously-generated signals
(IIUC), and because the Heartwood codebase does not (and
should continue to not) use libc::raise nor libc::abort,
there is no need to install any signal handlers when on
Windows. While the Rust std library does use
libc::abort internally, that will still work as expected
on Windows, because Heartwood doesn’t (and shouldn’t)
install a signal handler for SIGABRT. So, sigaction()
will work for all supported OSs where signal handling is
needed.
If/when supporting other OSs requires having support for
some sort of analogue to POSIX signals, for Radicle to
provide the same functionality, it already would be
necessary anyway to have conditional compilation
(#[cfg(...)]) to implement that separately from the
POSIX-signals approach. I.e. the use of POSIX-only
sigaction() doesn’t affect this.
With using sigaction(), it’ll need to be decided what
to give for sa_mask & sa_flags. This will depend on
what radicle_signals::handler needs to do and how it wants
to do that, which I’ve suggested (in issue#111cf90) should
be changed and decided on. But probably, sa_mask should
just mask all signals, and sa_flags won’t need
SA_SIGINFO unless and until radicle_signals has more
involved needs in the future.
Whether SA_RESTART should be used (given in sa_flags) is
probably the biggest question. I think I prefer to use it,
especially with projects where there’s a possibility that
some part(s), among all the transitive dependencies, forgot
to handle EINTR (which isn’t uncommon). But SA_RESTART
can cause timeouts to be significantly longer than
requested, when restarting an interrupted function (usually
a syscall) that took a timeout. This decision should be
made by someone much more familiar with the Radicle
codebases.
Note that all of these questions about how to use
sigaction() also apply to using signal(), but you just
have no control over nor consistency of them with
signal(). E.g. BSD signal() enables SA_RESTART; but
Linux without _BSD_SOURCE nor _DEFAULT_SOURCE does not
enable SA_RESTART but enables SA_RESETHAND | SA_NODEFER
which is usually undesirable; but Glibc on Linux usually is
like BSD because _DEFAULT_SOURCE is defined by default;
and Solaris man pages don’t say what its signal() does
regarding SA_RESTART. Further, the behaviors of
signal() w.r.t. masking and disposition-resetting are also
inconsistent between POSIX OSs. Given that C-preprocessor
defines can affect this mess, I don’t know what the libc
Rust crate does on different OSs regarding which semantics
libc::signal has for these aspects.
Using libc::sigaction instead will give consistent
semantics across OSs and guarantee support for
multi-threaded programs.
If
radicle_signalschanges to use thesignal_hookcrate, as described as an option in issue111cf90, thensigaction()will automatically be used by that, which would resolve this issue, but that hardcodes enablingSA_RESTART, so that would have to be OK.About
SA_RESTART, it turns-out that the platforms that Radicle provides prebuilt binaries for (whichbuild/README.mdimplies are the officially supported platforms) all have the semantics of enablingSA_RESTARTforsignal(). This means that all the supported preexisting uses of Radicle have been withSA_RESTART, which means that usingsigaction()instead should enableSA_RESTARTto preserve equivalent behavior.The Mac man page of
signal()says:musl always enables
SA_RESTART: https://git.musl-libc.org/cgit/musl/tree/src/signal/signal.cAlso, for further platforms that seem to be in current use:
IIUC, all the BSDs enable
SA_RESTARTwith theirsignal(). This means that HardenedBSD will have the same behavor withsigaction()withSA_RESTART.Glibc enables
SA_RESTARTwith itssignal()(per its man page). This means that builds for common Linux distros will have the same behavor withsigaction()withSA_RESTART.This issue is fixed by patch 8e05702.