Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
signals: `libc::signal` should not be used
Closed { reason: Solved } did:key:z6Mko5mi...bgMH opened 1 year ago

As POSIX says:

The sigaction() function provides a more reliable mechanism for controlling signals; new applications should use sigaction() rather than signal().

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 to SIG_DFL or SIG_IGN. The semantics when using signal() 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.

z6Mko5mi...bgMH commented 1 year ago

If radicle_signals changes to use the signal_hook crate, as described as an option in issue 111cf90, then sigaction() will automatically be used by that, which would resolve this issue, but that hardcodes enabling SA_RESTART, so that would have to be OK.

z6Mko5mi...bgMH commented 1 year ago

About SA_RESTART, it turns-out that the platforms that Radicle provides prebuilt binaries for (which build/README.md implies are the officially supported platforms) all have the semantics of enabling SA_RESTART for signal(). This means that all the supported preexisting uses of Radicle have been with SA_RESTART, which means that using sigaction() instead should enable SA_RESTART to preserve equivalent behavior.

The Mac man page of signal() says:

any handler installed with signal(3) will have the SA_RESTART flag set, meaning that any restartable system call will not return on receipt of a signal.

musl always enables SA_RESTART: https://git.musl-libc.org/cgit/musl/tree/src/signal/signal.c

Also, for further platforms that seem to be in current use:

IIUC, all the BSDs enable SA_RESTART with their signal(). This means that HardenedBSD will have the same behavor with sigaction() with SA_RESTART.

Glibc enables SA_RESTART with its signal() (per its man page). This means that builds for common Linux distros will have the same behavor with sigaction() with SA_RESTART.

z6Mko5mi...bgMH commented 1 year ago

This issue is fixed by patch 8e05702.