Bug 103382 - condition_variable::wait() is not cancellable because it is marked noexcept
Summary: condition_variable::wait() is not cancellable because it is marked noexcept
Status: ASSIGNED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 10.2.0
: P3 normal
Target Milestone: ---
Assignee: Jonathan Wakely
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-11-23 13:01 UTC by Ivan Sorokin
Modified: 2022-09-06 12:53 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-11-23 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ivan Sorokin 2021-11-23 13:01:35 UTC
At the moment condition_variable::wait() is marked noexcept. It means that if pthread_cond_wait() acts as cancellation point and throws an exception the program is terminated with std::terminate(). This program demonstrates the issue:

#include <condition_variable>
#include <thread>

int main()
{
    std::mutex m;
    std::condition_variable cv;

    std::thread th([&]
    {
        std::unique_lock lock(m);
        cv.wait(lock);
    });

    pthread_cancel(th.native_handle());
    th.join();
}

This program terminates with SIGABRT.

Because of this using condition_variable::wait() in cancellable threads is tricky: the programmer has to guard all calls to condition_variable::wait() with disabling/restoring cancellation state. Also this stops the thread from being cancellable while in wait(). Therefore the outer thread has to know which condition_variable the thread waits and notify this condition_variable after pthread_cancel(). Also one should add cancellation point pthread_testcancel() immediately after restoring cancellation state after wait().

I believe it would be great if condition_variable::wait interacted nicer with POSIX-cancellation. I would like to suggest removing noexcept from condition_variable::wait(). This also matches the C++ standard very well [thread.condition.condvar] where condition_variable::wait() is not marked as noexcept.
Comment 1 Ivan Sorokin 2021-11-23 13:06:16 UTC
Please note there was a related issue PR67726. I hope it is possible to meet the requirements mentioned in the issue as well as enabling cancellation.
Comment 2 Jonathan Wakely 2021-11-23 13:44:06 UTC
(In reply to Ivan Sorokin from comment #0)
> This also matches the C++ standard very well
> [thread.condition.condvar] where condition_variable::wait() is not marked as
> noexcept.

Huh, I thought it was noexcept. Then yes, we should remove it.

There are still lots of other places where the stadnard does require 'noexcept' and cancellation will terminate. NPTL thread cancellation via __forced_unwind just doesn't work well in C++.
Comment 3 Ivan Sorokin 2021-11-24 14:46:03 UTC
> Huh, I thought it was noexcept. Then yes, we should remove it.

Thank you very much! I'm looking forward for a fix.

> There are still lots of other places where the stadnard does require 'noexcept' and cancellation will terminate.

Do you have any specific functions in mind? If so perhaps something can be done about them too.

Some people claim that noexcept and cancellation and mutually incompatible, but I don't think this is the case. I believe that by following a simple discipline noexcept and cancellation can interact very well.

First of all not all noexcept functions are problematic: noexcept functions that don't call cancellation points are perfectly fine.

The noexcept functions that do call some cancellation points can be fixed by suppression/restoring of cancellation. For example, a destructor that calls close() which is a cancellation point should just suppress/restore cancellation. Same for a destructor that calls pthread_join(). One might say that because of this we lose some cancellation points and this is true, but I believe that noexcept are the places where program can not recover preserving exception guarantees and having cancellation suppressed in these places is perfectly fine.
Comment 4 Jonathan Wakely 2021-11-24 22:10:56 UTC
Yes, it's just a lot of work to implement correctly, and non-zero overhead to change the cancellation state.
Comment 5 GCC Commits 2021-12-09 23:27:23 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:9e18a25331fa25c3907249fede65a02c6817b06e

commit r12-5877-g9e18a25331fa25c3907249fede65a02c6817b06e
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue Dec 7 15:11:15 2021 +0000

    libstdc++: Allow std::condition_variable waits to be cancelled [PR103382]
    
    std::condition_variable::wait(unique_lock<mutex>&) is incorrectly marked
    noexcept, which means that the __forced_unwind exception used by NPTL
    cancellation will terminate the process. It should allow exceptions to
    pass through, so that a thread can be cleanly cancelled when waiting on
    a condition variable.
    
    The new behaviour is exported as a new version of the symbol, to avoid
    an ABI break for existing code linked to the non-throwing definition of
    the function. Code linked against older releases will have a reference
    to the @GLIBCXX_3.4.11 version, andcode compiled against the new
    libstdc++ will get a reference to the @@GLIBCXX_3.4.30 version.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/103382
            * config/abi/pre/gnu.ver (GLIBCXX_3.4.11): Do not export old
            symbol if .symver renaming is supported.
            (GLIBCXX_3.4.30): Export new symbol if .symver renaming is
            supported.
            * doc/xml/manual/evolution.xml: Document change.
            * doc/html/manual/api.html: Regenerate.
            * include/bits/std_mutex.h (__condvar::wait, __condvar::wait_until):
            Remove noexcept.
            * include/std/condition_variable (condition_variable::wait):
            Likewise.
            * src/c++11/condition_variable.cc (condition_variable::wait):
            Likewise.
            * src/c++11/compatibility-condvar.cc (__nothrow_wait_cv::wait):
            Define nothrow wrapper around std::condition_variable::wait and
            export the old symbol as an alias to it.
            * testsuite/30_threads/condition_variable/members/103382.cc: New test.
Comment 6 Jonathan Wakely 2021-12-09 23:38:57 UTC
The incorrect noexcept is fixed for GCC 12, but isn't suitable to backport.

For the branches we should consider disabling cancellation as suggested in https://gcc.gnu.org/pipermail/gcc-patches/2021-December/586406.html
Comment 7 Ivan Sorokin 2022-09-06 12:35:56 UTC
As the bug is fixed I'm closing the issue.
Comment 8 Jonathan Wakely 2022-09-06 12:53:21 UTC
As I said in comment 6, we might want to disable cancellation in the release branches.