Skip to content

counting_semaphore deadlocks? #46357

Closed
@AlexGuteniev

Description

@AlexGuteniev
Bugzilla Link 47013
Version 11.0
OS Windows NT
CC @mclow

Extended Description

counting_semaphore::release only notifies waiting thread when count reaches zero. it only notifies one waiting thread if __update == 1:

void release(ptrdiff_t __update = 1)
{
    if(0 < __a.fetch_add(__update, memory_order_release))
        ;
    else if(__update > 1)
        __a.notify_all();
    else
        __a.notify_one();
}

void release(ptrdiff_t __update = 1)
{
if(0 < __a.fetch_add(__update, memory_order_release))
;
else if(__update > 1)
__a.notify_all();
else
__a.notify_one();
}

counting_semaphore::acquire enters waiting when __a is observed to be zero:

void acquire()
{
    auto const __test_fn = [=]() -> bool {
        auto __old = __a.load(memory_order_relaxed);
        return (__old != 0) && __a.compare_exchange_strong(__old, __old - 1, memory_order_acquire, memory_order_relaxed);
    };
    __cxx_atomic_wait(&__a.__a_, __test_fn);
}

void acquire()
{
auto const __test_fn = [=]() -> bool {
auto __old = __a.load(memory_order_relaxed);
return (__old != 0) && __a.compare_exchange_strong(__old, __old - 1, memory_order_acquire, memory_order_relaxed);
};
__cxx_atomic_wait(&__a.__a_, __test_fn);
}

Now assume two threads are waiting on acquire call:
T1: { s.acquire(); }
T2: { s.acquire(); }

And third thread calls release, to release two times by one:
T3: { s.release(1); s.release(1); }

first release call unblocks one waiting thread. Assume it is T1, and assume that before T1 did compare_exchange_strong, T3 executes the second release call. Since second release observes __a to be 1 (from the previous call), it never releases T2. So T2 stays blocked while __a == 1.


If this analysis is correct, I would have called notify_all() for all cases if __a was observed to be 0. Except for counting_semaphore<1> specialization, where it is always safe to call notify_one().

Another alternative could be avoiding 0 < __a.fetch_add check, this will unblock T2 in subsequent release in my example, but it looks like to be less efficient and more complex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugzillaIssues migrated from bugzillalibc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.threadingissues related to threading

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions