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. Also, the shared library version of the checked library must be
linked with -ljustthread_check rather than -ljustthread
(the static library contains both checked and unchecked versions). 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 -I/usr/include/justthread \ -D_JUST_THREAD_DEADLOCK_CHECK -g -rdynamic \ -o sample sample.cpp -ljustthread_check
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::lock_guard<std::mutex> lk(io_mutex); std::thread t(thread_func); 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 locks the io_mutex
and holds it across the starting of the new thread and the call to t.join(). Therefore 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 without deadlock checking enabled then it will hang when it hits the deadlock. On the other hand, if you compile it with deadlock checking enabled, then the deadlock 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: /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x7fc197621044] Called from /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::__thread_base_data::__join()+0x26) [0x7fc19761ef86] Called from /usr/lib/libjustthread_check4.4.so.1(std::__jss_checked0x::thread::join()+0x23) [0x7fc19761f223] Called from ./sample(main+0x55) [0x402d75] Called from /lib/libc.so.6(__libc_start_main+0xfd) [0x7fc1968e8c4d] Called from ./sample() [0x402bf9] Thread 2 blocked at: /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x7fc197621044] Called from /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::mutex::lock()+0x35) [0x7fc197623d75] Called from ./sample(std::__jss_checked0x::mutex::lock()+0x18) [0x4030d8] Called from ./sample(std::__jss_checked0x::lock_guard<std::__jss_checked0x::mutex>::lock_guard(std::__jss_checked0x::mutex&)+0x2a) [0x40316c] Called from ./sample(thread_func()+0x1c) [0x402cd0] Called from ./sample(__jss::__checked0x::__invoke_v<void, void (&)()>::operator()(void (*)())+0x16) [0x403698] Called from ./sample(void __jss::__checked0x::__invoker<void, void (&)()>::__do_invoke<>()+0x22) [0x403680] Called from ./sample(__jss::__checked0x::__invoker<void, void (&)()>::__result_type __jss::__checked0x::__unpacker<__jss::__checked0x::__args_tuple<> >::__unpack_and_invoke<__jss::__checked0x::__invoker<void, void (&)()>>(__jss::__checked0x::__invoker<void, void (&)()>*, __jss::__checked0x::__args_tuple<> const&)+0x1c) [0x40365b] Called from ./sample(__jss::__checked0x::__invoker<void, void (&)()>::operator()()+0x23) [0x40363d] Called from ./sample(__jss::__checked0x::thread_data<__jss::__checked0x::__invoker<void, void (&)()> >::__run()+0x20) [0x4035c8] Called from /usr/lib/libjustthread_check4.4.so.1(+0x19f15) [0x7fc19761ef15] Called from /lib/libpthread.so.0(+0x69ca) [0x7fc196c539ca] Called from /lib/libc.so.6(clone+0x6d) [0x7fc1969b072d] Waiting for object locked on thread 1 at: /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::__stack_trace::__stack_trace()+0x64) [0x7fc197621044] Called from /usr/lib/libjustthread_check4.4.so.1(__jss::__checked0x::mutex::lock()+0x35) [0x7fc197623d75] Called from ./sample(std::__jss_checked0x::mutex::lock()+0x18) [0x4030d8] Called from ./sample(std::__jss_checked0x::lock_guard<std::__jss_checked0x::mutex>::lock_guard(std::__jss_checked0x::mutex&)+0x2a) [0x40316c] Called from ./sample(main+0x1c) [0x402d3c] Called from /lib/libc.so.6(__libc_start_main+0xfd) [0x7fc1968e8c4d] Called from ./sample() [0x402bf9] 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.