C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Exceptions : lost all hope -- do not resuscitate

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 10 Jun 2026 10:33:00 +0100
On Sun, Jun 7, 2026 at 6:27 PM Giuseppe D'Angelo wrote:
>
> Deadlocking across *different* threads is well-defined behavior
<snip>
> An implementation that detects this case, and makes lock() throw the
> corresponding exception, is completely conforming.
<snip>
> In practice, I don't know of any std::mutex implementation that actually
> does this.


I think it could be coded something like this:

    https://godbolt.org/z/6a6Es514q

And here it is copy-pasted:

#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <array>
#include <atomic>
#include <bitset>
#include <mutex>
#include <system_error>
#include <thread>
#include <tuple>

// --- System Constraints ---
constexpr std::size_t MAX_MUTEX_COUNT = 255u; // Maximum number of
mutexes in the system
constexpr std::size_t MAX_THREAD_COUNT = 127u; // Maximum number of
threads in the system

// --- Global State ---
// Tuple structure:
// 1. Address of the underlying mutex
// 2. Index of the thread which currently has the lock (0 means unowned)
// 3. Bitset for indices of threads waiting on the lock (sized by
MAX_THREAD_COUNT)
std::array<
    std::tuple< std::mutex*, std::uintptr_t,
std::bitset<MAX_THREAD_COUNT + 1u> >,
    MAX_MUTEX_COUNT + 1u
> locked;

// A meta-mutex to protect read/write access to the global 'locked' array
std::mutex meta_mtx;

// Helper to get a small, unique integer ID for each thread
std::uintptr_t get_small_thread_id(void) noexcept
{
    static std::atomic<std::uintptr_t> thread_counter{0};
    static thread_local std::uintptr_t local_id = ++thread_counter;

    if ( local_id > MAX_THREAD_COUNT )
    {
        std::fputs("Too many threads!\n", stderr);
        std::abort();
    }

    return local_id;
}

class Mutex {
    typedef std::size_t size_t;
private:
    std::mutex mtx;
    size_t mutex_idx;

    // Helper to get a unique integer ID for each mutex
    static size_t get_next_mutex_idx(void) noexcept
    {
        static std::atomic<size_t> mutex_counter{0};
        size_t id = ++mutex_counter;

        if ( id > MAX_MUTEX_COUNT )
        {
            std::fputs("Too many mutices!\n", stderr);
            std::abort();
        }

        return id;
    }

public:
    Mutex(void) : mutex_idx(get_next_mutex_idx())
    {
        std::lock_guard mylock(meta_mtx);
        std::get<0>(locked[mutex_idx]) = &mtx; // Store address
        std::get<1>(locked[mutex_idx]) = 0; // 0 = Unowned initially
        std::get<2>(locked[mutex_idx]).reset(); // No threads waiting
    }

    void lock(void) noexcept(false) // might throw std::system_error
    {
        std::uintptr_t my_tid = get_small_thread_id();

        {
            // Lock the meta-state to check the deadlock chain safely
            std::lock_guard<std::mutex> lock(meta_mtx);

            size_t current_check_mutex = mutex_idx;

            // Follow the chain of waiting threads
            for (; /* ever */ ;)
            {
                std::uintptr_t owner = std::get<1>(locked[current_check_mutex]);

                if ( 0u == owner )
                {
                    break; // Nobody owns this mutex, the chain ends safely.
                }

                if ( owner == my_tid )
                {
                    // Throw std::system_error specifically designed
for deadlocks
                    throw std::system_error(

std::make_error_code(std::errc::resource_deadlock_would_occur),
                        "Deadlock Detected! Cycle found in waiting chain."
                    );
                }

                // Is the owner thread waiting on any other mutex?
                bool owner_is_waiting = false;

                for ( size_t i = 0; i <= MAX_MUTEX_COUNT; ++i )
                {
                    // Check the bitset to see if 'owner' is waiting
on mutex 'i'
                    if ( std::get<2>(locked[i]).test(owner) )
                    {
                        current_check_mutex = i; // Follow the chain
to the next mutex
                        owner_is_waiting = true;
                        break;
                    }
                }

                // If the owner isn't waiting on anything, the chain
ends safely.
                if ( false == owner_is_waiting )
                {
                    break;
                }
            }

            // No deadlock detected. Register THIS thread as waiting
in the bitset.
            std::get<2>(locked[mutex_idx]).set(my_tid);
        } // meta_mtx is released here!

        // Actually wait for the underlying lock
        mtx.lock();

        {
            // We successfully acquired the lock. Update the meta-state.
            std::lock_guard<std::mutex> lock(meta_mtx);

            // We are no longer waiting
            std::get<2>(locked[mutex_idx]).reset(my_tid);

            // We are now the official owner
            std::get<1>(locked[mutex_idx]) = my_tid;
        }
    }

    void unlock(void) noexcept
    {
        {
            std::lock_guard<std::mutex> lock(meta_mtx);
            // Relinquish ownership (set back to 0)
            std::get<1>(locked[mutex_idx]) = 0;
        }

        // Release the underlying mutex
        mtx.unlock();
    }
};


//=======================================================
// Here comes test code:

#include <chrono>

void ThreadEntryPoint(unsigned const index)
{
    static Mutex m1, m2, m3;

    Mutex* first = nullptr;
    Mutex* second = nullptr;

    switch ( index )
    {
    case 1u: first = &m1; second = &m2; break; // T1: m1 → m2
    case 2u: first = &m2; second = &m3; break; // T2: m2 → m3
    case 3u: first = &m3; second = &m1; break; // T3: m3 → m1
    }

    first->lock();

    std::this_thread::sleep_for( std::chrono::milliseconds(200u) );

    try
    {
        second->lock();
        second->unlock();
    }
    catch ( std::system_error const& e )
    {
        std::fprintf(stderr, "Thread %u: %s\n", index, e.what());
    }

    first->unlock();
}

int main(void)
{
    std::jthread a( ThreadEntryPoint, 1u );
    std::jthread b( ThreadEntryPoint, 2u );
    std::jthread c( ThreadEntryPoint, 3u );
}

Received on 2026-06-10 09:33:15