Async-signal-safety of ```core``` (and ```std```)

Hello everyone!

In POSIX, there are "async-signal-safety" requirements defined, that certain parts of the code must conform to to work safely (e.g., signal handlers and fork pre-exec routines in multithreaded processes). POSIX defines a list of async-signal-safe functions that will not cause undefined behavior when called by a signal handler. I wonder, which parts of core (and possibly std) are async-signal-safe? Is it okay to use format_args!() for formatting (for example, for logging)? For multithreaded environments, is it okay to use thread_local!() variables (it shouldn't allocate)? What can be used for async-signal-safe thread-local objects (like errno in C)?

No, tls is not async-signal-safe. Glibc lazily allocates tls variables for each thread on first access from said thread.

Therefore, to communicate with the rest of the program, the signal handler needs to set some variable that is both thread-safe and async-signal-safe. I guess only atomics can be used in this case?

Generally, functionality from core alone should be fine, although unless specially mentioned, it isn't guaranteed. In this way, it's very similar to use outside of main, with the top level std docs saying

This means that use of std before/after main, especially of features that interact with the OS or global state, is exempted from stability and portability guarantees and instead only provided on a best-effort basis. Nevertheless bug reports are appreciated.

On the other hand core and alloc are most likely to work in such environments with the caveat that any hookable behavior such as panics, oom handling or allocators will also depend on the compatibility of the hooks.

This points out the biggest caveat: in order to be signal safe you need to be completely free from any potential panicking conditions. So you really want to minimize the amount of code that runs in the restricted context.

Generally a signal hook will place any events into a global wait-free atomic circular buffer, and any real work will happen outside the signal hook, such as in a worker thread, due to the limitations of signal hook compatibility. Wait freedom isn't sufficient on its own to guarantee signal safety (and is distinct from lock freedom) but often goes hand in hand with the properties actually relevant to wait freedom.

You probably should use or at least look one of the signal handling crates like ctrlc or signal-hook.

I also wonder about using Rust formatting system. Obviously using println!() or format!() is unacceptable, because they allocate (and the former also panics on I/O errors), however, as far as I understand, format_args!() neither allocates nor panics. Can therefore signal handlers and child process pre-execution handlers use it as long as implementation of formatting traits in the formatted objects and core::fmt::Write implementation in the "receiving" object are async-signal-safe?

format_args!() is part of core, so you can expect it’s async-signal-safe as much as the rest of core (as already discussed: likely but not guaranteed). But you also have to consider every formatting trait implementation that gets invoked recursively by whatever you format.

2 Likes