quick_exit
can deadlockSection: 17.5 [support.start.term] Status: New Submitter: Jean-François Bastien Opened: 2016-11-07 Last modified: 2020-09-06
Priority: 3
View other active issues in [support.start.term].
View all other issues in [support.start.term].
View all issues with New status.
Discussion:
While SG1 was processing NB comments CA1 and LATE2 regarding P0270R1,
we decided to remove the proposed guarantee that quick_exit
be made signal safe.
at_quick_exit
aren't forbidden from calling
quick_exit
, but quick_exit
implementations likely acquire some form of a lock before
processing all registered functions (because a note forbids the implementation from introducing data races).
The following code can therefore deadlock:
#include <cstdlib> int main() { std::at_quick_exit([] () { std::quick_exit(0); }); std::quick_exit(1); return 0; }
The same applies if a function registered in at_quick_exit
handles a signal, and that signal calls
quick_exit
. SG1 believes that both issues (same thread deadlock, and signal deadlock) can be resolved
in the same manner. Either:
quick_exit
while servicing quick_exit
is undefined; orquick_exit
while servicing quick_exit
is defined to not deadlock,
and instead calls _Exit
without calling further registered functions.Option 2. seems preferable, and can be implemented along the lines of:
#include <array> #include <atomic> #include <cstddef> namespace { typedef void (*func)(); std::array<func, 32> quick_exit_functions; const auto* quick_exit_functions_ptr = &quick_exit_functions; std::atomic_flag lock = ATOMIC_FLAG_INIT; struct scope { scope() { while (lock.test_and_set(std::memory_order_acquire)) ; } ~scope() { lock.clear(std::memory_order_release); } }; } namespace std { extern "C" void quick_exit(int status) noexcept { decltype(quick_exit_functions_ptr) f; { scope s; f = quick_exit_functions_ptr; quick_exit_functions_ptr = nullptr; } if (f) { size_t pos = f->size(); while (pos > 0) (*f)[--pos](); } _Exit(status); } extern "C++" int at_quick_exit(func f) noexcept { scope s; if (!quick_exit_functions_ptr || quick_exit_functions.size() == quick_exit_functions.max_size()) return -1; quick_exit_functions[quick_exit_functions.size()] = f; return 0; } }
Ideally, the resolution would also add back the wording which SG1 dropped from P0270R1:
Add at new element to the end of 17.5 [support.start.term] p13 (
quick_exit()
):Remarks: The function
quick_exit()
is signal-safe (17.13.4 [csignal.syn]). [Note: It might still be unsafe to callquick_exit()
from a handler, because the functions registered withat_quick_exit()
might not be signal-safe. — end note]
[Issues Telecon 16-Dec-2016]
Priority 3
Proposed resolution:
This wording is relative to N4606.
Add at new element to the end of 17.5 [support.start.term] p13 (quick_exit()
):
[[noreturn]] void quick_exit(int status) noexcept;-13- Effects: Functions registered by calls to
-?- Remarks: The functionat_quick_exit
are called in the reverse order of their registration, except that a function shall be called after any previously registered functions that had already been called at the time it was registered. Objects shall not be destroyed as a result of callingquick_exit
. If control leaves a registered function called byquick_exit
because the function does not provide a handler for a thrown exception,std::terminate()
shall be called. [Note:at_quick_exit
may call a registered function from a different thread than the one that registered it, so registered functions should not rely on the identity of objects with thread storage duration. — end note] After calling registered functions,quick_exit
shall call_Exit(status)
. [Note: The standard file buffers are not flushed. See: ISO C 7.22.4.5. — end note]quick_exit()
is signal-safe (17.13.4 [csignal.syn]). [Note: It might still be unsafe to callquick_exit()
from a handler, because the functions registered withat_quick_exit()
might not be signal-safe. — end note]