C++ Logo

std-proposals

Advanced search

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

From: Dr.ThomasCatalano <tomcatalano.0_at_[hidden]>
Date: Fri, 12 Jun 2026 08:34:43 -0600
How do you handle exceptions?

tomcatalano.0_at_[hidden]

On Wed, Jun 10, 2026, 3:33 AM Frederick Virchanza Gotham via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> 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 );
> }
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2026-06-12 14:35:00