This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 2815. `quick_exit` can deadlock

Section: 17.6 [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.

Our reasoning is that functions registered with `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:

1. Specify that calling `quick_exit` while servicing `quick_exit` is undefined; or
2. Specifying that calling `quick_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.6 [support.start.term] p13 (`quick_exit()`):

> > Remarks: The function `quick_exit()` is signal-safe (17.14.4 [csignal.syn]). [Note: It might still be unsafe to call `quick_exit()` from a handler, because the functions registered with `at_quick_exit()` might not be signal-safe. — end note]

[Issues Telecon 16-Dec-2016]

Priority 3

Proposed resolution:

This wording is relative to N4606.

1. Add at new element to the end of 17.6 [support.start.term] p13 (`quick_exit()`):

   >     [[noreturn]] void quick_exit(int status) noexcept;

   > > -13- Effects: Functions registered by calls to `at_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 calling `quick_exit`. If control leaves a registered function called by `quick_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]

   > > -?- Remarks: The function `quick_exit()` is signal-safe (17.14.4 [csignal.syn]). [Note: It might still be unsafe to call `quick_exit()` from a handler, because the functions registered with `at_quick_exit()` might not be signal-safe. — end note]