just::thread Complete C++ Standard Thread Library by Just Software Solutions

Documentation Home

In order to use the deadlock detection debug mode, ensure that _JUST_THREAD_DEADLOCK_CHECK is defined before any of the just::thread headers are included. It is important that this macro is defined in all translation units that include just::thread headers if it is defined in any, in order to avoid link-time errors.

When a deadlock occurs, the library will report the full stack trace of the threads involved in the deadlock, so you can see the exact location of the problem. Crucially, the library will also report the thread and callstack for where the ownership of the synchronization objects was acquired.

On Windows, if the library detects deadlock it will then display message boxes showing the full stack trace of the threads involved in the deadlock, before breaking into the debugger. For the most useful stack trace information, the executable should be built with debug information.

On Linux, the deadlock message and stack trace is written to the standard error stream, and then std::terminate() is called. For the most useful stack trace information the executable should be built with both the -g switch and the -rdynamic switch:

g++ -std=c++0x -pthread -D_JUST_THREAD_DEADLOCK_CHECK -g -rdynamic -o sample sample.cpp -ljustthread -lrt

Example

Take a look at the following code:

#include <thread>
#include <mutex>
#include <iostream>

std::mutex io_mutex;

void thread_func()
{
    std::lock_guard<std::mutex> lk(io_mutex);
    std::cout<<"Hello from thread_func"<<std::endl;
}

int main()
{
    std::thread t(thread_func);
    std::lock_guard<std::mutex> lk(io_mutex);
    std::cout<<"Hello from main thread"<<std::endl;
    t.join();
    return 0;
}

Now, it is obvious just from looking at the code that there's a potential deadlock here: the main thread holds the lock on io_mutex across the call to t.join(). Therefore, if the main thread manages to lock the io_mutex before the new thread does then the program will deadlock: the main thread is waiting for thread_func to complete, but thread_func is blocked on the io_mutex, which is held by the main thread!

If you compile this program and run it a few times without deadlock checking enabled then it will eventually hang when it hits the deadlock. On the other hand, if you compile it with deadlock checking enabled, then if deadlock does occur, then this will be detected by the library, and you will get output similar to the following:

Hello from main thread
Deadlock Detected!
Deadlock detected in thread 1 at:
./sample(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x40b6f4]
Called from ./sample(__jss::__checked0x::mutex::lock()+0x35) [0x40d445]
Called from ./sample(std::__jss_checked0x::lock_guard<std::__jss_checked0x::mutex>::lock_guard(std::__jss_checked0x::mutex&)+0x2a) [0x40837c]
Called from ./sample(thread_func()+0x19) [0x407caf]
Called from ./sample(__jss::__checked0x::__invoke_v<void, void (&)()>::operator()(void (*)())+0x16) [0x407efc]
Called from ./sample(_ZN5__jss11__checked0x9__invokerIvRFvvEE11__do_invokeIEEvU10__variadicRT_+0x1c) [0x407f1a]
Called from ./sample(_ZN5__jss11__checked0x10__unpackerINS0_12__args_tupleIEEE19__unpack_and_invokeINS0_9__invokerIvRFvvEEEEENT_13__result_typeEPSA_RKS3_U10__variadicRT0_+0x19) [0x407f35]
Called from ./sample(__jss::__checked0x::__invoker<void, void (&)()>::operator()()+0x1d)[0x407f55]
Called from ./sample(__jss::__checked0x::thread_data<__jss::__checked0x::__invoker<void,void (&)()> >::run()+0x19) [0x407f71]
Called from ./sample(thread_start_function+0x1e4) [0x4090e4]
Called from /lib/libpthread.so.0 [0x7f252f0f73ba]
Called from /lib/libc.so.6(clone+0x6d) [0x7f252ee63fcd]

Deadlock on object locked in thread 2 at:
./sample(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x40b6f4]
Called from ./sample(__jss::__checked0x::mutex::lock()+0x35) [0x40d445]
Called from ./sample(std::__jss_checked0x::lock_guard<std::__jss_checked0x::mutex>::lock_guard(std::__jss_checked0x::mutex&)+0x2a) [0x40837c]
Called from ./sample(main+0x27) [0x407bfb]
Called from /lib/libc.so.6(__libc_start_main+0xe6) [0x7f252ed9c5a6]
Called from ./sample [0x407a89]

Thread 2 blocked at:
./sample(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x40b6f4]
Called from ./sample(std::__jss_checked0x::thread::join()+0x30) [0x408b60]
Called from ./sample(main+0x4c) [0x407c20]
Called from /lib/libc.so.6(__libc_start_main+0xe6) [0x7f252ed9c5a6]
Called from ./sample [0x407a89]


terminate called without an active exception
Aborted

This output comes from a run under linux. The output on Windows is similar, but will appear in a message box, and includes source line numbers. As you can see, the stack trace tells us that thread 1 is blocked in a call to mutex::lock() from the lock_guard constructor in thread_func(). Not only that, but it tells us that the deadlock is on an object locked in thread 2, from a lock_guard constructor in main, and that thread 2 is blocked on a call to thread::join.

This is all the information we need to see the deadlock in this case, but in more complex cases we might need to go further up the call stack, particularly if the deadlock occurs in a function called from lots of different threads, or the mutex being used in the function depends on its parameters.

If you compile your program with deadlock detection enabled and run it under a debugger (or you have a Just-In-Time debugger enabled on Windows) then the program will break into the debugger when the deadlock is detected. This will enable you to inspect the state of the objects on the call stack.

See Also