Reputation: 3439
I'm running into situations where I have literally hundreds of threads sleeping on the same lock. I would like to find an efficient way to stop the program in the debugger and find the thread that currently owns the lock.
I have tried examining the stack backtrace for each thread by hand but this is (a) excruciatingly tedious and (b) most of the time I either miss or can't identify the thread that is not blocked.
(These locks are currently os_unfair_lock
, but this question would apply to NSLock
as well.)
I had a thought that I might add some debug code that, after a thread has obtained a lock, it can store its thread ID in a global variable. When I stop the app I could examine that global to help me find the lock owner's thread. My problem seems to be that I can't obtain the thread number that Xcode uses (the thread ID obtained via pthread_threadid_np
doesn't seem to correspond to any of the numbers in the Xcode thread list).
Solutions? Ideas? Thoughts?
Upvotes: 1
Views: 1098
Reputation: 27110
It looks like you figured out what you needed, but just to make everything explicit:
Darwin actually uses three different thread ID's. Each thread has a pthread id (reported by pthread_self
), a mach thread port (reported by mach_thread_self
), and a globally unique thread ID which gets filled in by the call to pthread_threadid_np
among other things). The latter is the one all the other system services (like sample, spindump, Instruments, etc.) display. So that's the one lldb prints as well. That's the one you are matching up in your answer.
Note, you don't need to go through the thread list by hand to find your thread. you can use the Python API's. For instance, if you knew the Thread ID you were looking for was 0x16cc5, you could do:
(lldb) script lldb.process.SetSelectedThreadByID(0x16cc5)
That will set the selected thread to this one (so bt
& other commands will operate on it.) It will also set this as the selected thread in Xcode.
Note, there probably should be a "thread select --thread-id" in the command set for lldb.
If you want to do more work when you find the thread, you can do:
(lldb) script
>>> for thread in lldb.process.threads:
... if thread.GetThreadID() == 0x16cc5:
... # Add code here
...
You could even get the Python to fetch your global variable and use that for the comparisons. And if you're really ambitious you could turn this into a custom lldb command for more convenient use...
You can read more about lldb's Python API here:
https://lldb.llvm.org/use/python-reference.html
and the API docs are here:
https://lldb.llvm.org/python_reference/index.html
Upvotes: 4
Reputation: 3439
Not the ideal solution, but by modifying my code to capture the thread ID of the thread that last obtained the lock (as described in the question), it's possible to determine the thread using lldb's thread list
command.
Add code to get the current thread's ID, something like:
static UInt64 DevCurrentThreadID( void )
{
pthread_t thread = pthread_self();
UInt64 threadID;
pthread_threadid_np(thread, &threadID);
return threadID;
}
After obtaining the lock, save the tread's ID in a global variable.
Once stopped in Xcode, switch to the lldb console pane and issue the command
threads list
It will list the thread along with each thread's ID (tid):
thread #1: tid = 0x4722c, 0x00007fff6eaef22a libsystem_kernel.dylib`mach_msg_trap + 10, queue = 'com.apple.main-thread'
* thread #4: tid = 0x47393, 0x00000001000b7ade StatsHelper`DevCurrentThreadID at PackageSource+RecordBlaster.m:86:9, name = 'verify', stop reason = breakpoint 16.1
thread #236: tid = 0x479fe, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #412: tid = 0x47d7f, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #437: tid = 0x47eef, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #450: tid = 0x47f0d, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #451: tid = 0x47f43, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #452: tid = 0x47f44, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #453: tid = 0x47f45, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #454: tid = 0x47f46, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #455: tid = 0x47f47, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #456: tid = 0x47f48, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #457: tid = 0x47f49, 0x00007fff6eaf0bfe libsystem_kernel.dylib`__workq_kernreturn + 10
... and so on
Get the value in the global variable, convert it to hex, then search the output for a matching tid = 0x<ThreadID>
. That's the thread # that owns the lock.
Upvotes: 1