Dead lock cause by NSTask's os_unfair_lock

when I use NSTask to execute cmd in a loop, sometime the NSTask will hang, code is like this

- (BOOL)waitUntilExitWithTimeout:(CFTimeInterval)TO sendTerm:(BOOL)SENDTERM sendKill:(BOOL)SENDKILL
{
    CFAbsoluteTime      started;
    CFAbsoluteTime      passed;
    BOOL                exited = NO;

    if (![self isRunning])
        return YES;

    int pro_pid = [self processIdentifier];
    started = CFAbsoluteTimeGetCurrent();
    for (
            CFAbsoluteTime now = started;
            !exited && ((passed = now - started) < TO);
            now = CFAbsoluteTimeGetCurrent()
            )
    {
        if (![self isRunning])
        {
            exited = YES;
        } else {

            CFAbsoluteTime sleepTime = 0.1;
            useconds_t sleepUsec = round(sleepTime * 1000000.0);
            if (sleepUsec == 0) sleepUsec = 1;
            usleep(sleepUsec); // sleep for 0.1 sec

        }
    }

    if (!exited)
    {
        //NSLog(@"%@ didn't exit after timeout of %0.2f sec", self, TO);

        if (SENDTERM)
        {
            TO = 2; // 2 second timeout, waiting for SIGTERM to kill process

            //NSLog(@"%@ sending SIGTERM", self);
            //[self terminate];
            // UNIX way
            pid_t pid = [self processIdentifier];
            kill(pid, SIGTERM);

            started = CFAbsoluteTimeGetCurrent();
            for (
                    CFAbsoluteTime now = started;
                    !exited && ((passed = now - started) < TO);
                    now = CFAbsoluteTimeGetCurrent()
                    )
            {
                if (![self isRunning])
                {
                    exited = YES;
                } else {
                    usleep(100000);
                }
            }
        }

        if (!exited && SENDKILL)
        {
            TO = 2; // 2 second timeout, waiting for SIGKILL to kill process

            //NSLog(@"%@ sending SIGKILL", self);
            pid_t pid = [self processIdentifier];
            kill(pid, SIGKILL);
            started = CFAbsoluteTimeGetCurrent();
            for (
                    CFAbsoluteTime now = started;
                    !exited && ((passed = now - started) < TO);
                    now = CFAbsoluteTimeGetCurrent()
                    )
            {
                if (![self isRunning])
                {
                    exited = YES;
                } else {
                    usleep(100000); // sleep for 0.1 sec
                }
            }
        }
    }

    return exited;
}

- (void) Exec(const char * cmdWithArgs)
{
    NSTask *task = [[NSTask alloc] init];
    
    [task setLaunchPath:@"/bin/sh"];
    NSArray *arguments = [NSArray arrayWithObjects:
            @"-c" ,
            [NSString stringWithFormat:@"%s", cmdWithArgs],
                    nil];

    [task setArguments: arguments];

    NSPipe *pipe = [NSPipe pipe];
    [task setStandardOutput: pipe];
    [task launch];

    NSFileHandle *file = [pipe fileHandleForReading];
    
    NSData *data = [file readDataToEndOfFile];

    [task waitUntilExitWithTimeout:30.0f sendTerm:YES sendKill:YES];
//sometime waitUntilExit will hang, sohttps://stackoverflow.com/questions/33423993/hanging-nstask-using-waituntilexit
    //[task waitUntilExit];
    [file closeFile];
}

std::string cmd =  R"(netstat -rn | awk '/default/ {if ( index($6, "en0") > 0 ){print $2} }')";
while(1){
    Exec(cmd.c_str());
}

when the test is hang, use lldb attached process, a deadlock occur. it seems caused by the lock in NSConcreteTask

anybody can help me ?

Replies

the call stack

* thread #40
  frame #0: 0x00007ff815f6720a libsystem_kernel.dylib`__ulock_wait + 10
  frame #1: 0x00007ff815fb62c5 libsystem_platform.dylib`_os_unfair_lock_lock_slow + 162
 * frame #2: 0x00007ff816f82ffe Foundation`-[NSConcreteTask processIdentifier] + 28
  frame #3: 0x00000001053e95e5 libqaxbase.macos.dylib`-[NSTask(self=0x0000600002261590, _cmd="waitUntilExitWithTimeout:sendTerm:sendKill:", TO=2, SENDTERM=YES, SENDKILL=YES) waitUntilExitWithTimeout:sendTerm:sendKill:] at os_base_api.mm:79:25
  frame #4: 0x00000001053ea84a libqaxbase.macos.dylib`OSBaseApi::ExecCmd(this=0x0000600002cb5700, cmdWithArgs="netstat -rn | awk '/default/ {if ( index($4, \"llw0\") > 0 ){print $2} }'", result=0x0000600000df0930) at os_base_api.mm:278:5
  frame #5: 0x000000010557ed77 libnacplugin.dylib`NacPlugin::Exec(this=0x00007f795df04150, cmd="netstat -rn | awk '/default/ {if ( index($4, \"llw0\") > 0 ){print $2} }'") at NacPlugin.cpp:697:19
  frame #6: 0x000000010557c0a7 libnacplugin.dylib`NacPlugin::GetInterfaceAddrs(this=0x00007f795df04150, interfaces=size=0) at NacPlugin.cpp:503:35
  frame #7: 0x000000010557a821 libnacplugin.dylib`NacPlugin::KeepAlive(this=0x00007f795df04150) at NacPlugin.cpp:156:5
  frame #8: 0x000000010557a4d0 libnacplugin.dylib`NacPlugin::NacReport(this=0x00007f795df04150) at NacPlugin.cpp:84:9
  frame #9: 0x000000010558eb38 libnacplugin.dylib`NacPlugin::PluginInit(this=0x00006000000b81e8)::$_0::operator()() const at NacPlugin.cpp:66:15
  frame #10: 0x000000010558eae5 libnacplugin.dylib`decltype(__f=0x00006000000b81e8)::$_0>(fp)()) std::__1::__invoke<NacPlugin::PluginInit()::$_0>(NacPlugin::PluginInit()::$_0&&) at type_traits:3918:1
  frame #11: 0x000000010558ea85 libnacplugin.dylib`void std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, NacPlugin::PluginInit()::$_0>(__t=size=2, (null)=__tuple_indices<> @ 0x0000700005927f68)::$_0>&, std::__1::__tuple_indices<>) at thread:280:5
  frame #12: 0x000000010558e305 libnacplugin.dylib`void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, NacPlugin::PluginInit()::$_0> >(__vp=0x00006000000b81e0) at thread:291:5
  frame #13: 0x00007ff815fa2514 libsystem_pthread.dylib`_pthread_start + 125
  frame #14: 0x00007ff815f9e02f libsystem_pthread.dylib`thread_start + 15
(lldb) di -f
Foundation`-[NSConcreteTask processIdentifier]:
   0x7ff816f82fe2 <+0>: pushq %rbp
   0x7ff816f82fe3 <+1>: movq  %rsp, %rbp
   0x7ff816f82fe6 <+4>: pushq %r14
   0x7ff816f82fe8 <+6>: pushq %rbx
   0x7ff816f82fe9 <+7>: movq  %rdi, %r14
   0x7ff816f82fec <+10>: movq  0x408ad6f5(%rip), %rbx  ; NSConcreteTask._lock
   0x7ff816f82ff3 <+17>: addq  %rdi, %rbx
   0x7ff816f82ff6 <+20>: movq  %rbx, %rdi
   0x7ff816f82ff9 <+23>: callq 0x7ff8171534e8      ; symbol stub for: os_unfair_lock_lock
->  0x7ff816f82ffe <+28>: movq  0x408ad713(%rip), %rax  ; NSConcreteTask._pid
   0x7ff816f83005 <+35>: movl  (%r14,%rax), %r14d
   0x7ff816f83009 <+39>: movq  %rbx, %rdi
   0x7ff816f8300c <+42>: callq 0x7ff8171534f4      ; symbol stub for: os_unfair_lock_unlock
   0x7ff816f83011 <+47>: movl  %r14d, %eax
   0x7ff816f83014 <+50>: popq  %rbx
   0x7ff816f83015 <+51>: popq  %r14
   0x7ff816f83017 <+53>: popq  %rbp
   0x7ff816f83018 <+54>: retq  
   0x7ff816f83019 <+55>: nop   
   0x7ff816f8301a <+56>: nop   
   0x7ff816f8301b <+57>: nop   
   0x7ff816f8301c <+58>: nop   
   0x7ff816f8301d <+59>: nop   
   0x7ff816f8301e <+60>: nop   
   0x7ff816f8301f <+61>: nop   
   0x7ff816f83020 <+62>: nop 

* thread #42, queue = 'com.apple.root.utility-qos.overcommit'
 * frame #0: 0x00007ff815f6b262 libsystem_kernel.dylib`__wait4 + 10
  frame #1: 0x00007ff817086097 Foundation`__45-[NSConcreteTask launchWithDictionary:error:]_block_invoke.239 + 110
  frame #2: 0x00007ff815debcc9 libdispatch.dylib`_dispatch_client_callout + 8
  frame #3: 0x00007ff815dee746 libdispatch.dylib`_dispatch_continuation_pop + 460
  frame #4: 0x00007ff815dffa5a libdispatch.dylib`_dispatch_source_invoke + 2150
  frame #5: 0x00007ff815dfc1b4 libdispatch.dylib`_dispatch_kevent_worker_thread + 1554
  frame #6: 0x00007ff815f9f0d7 libsystem_pthread.dylib`_pthread_wqthread + 398
  frame #7: 0x00007ff815f9e01b libsystem_pthread.dylib`start_wqthread + 15
(lldb) f 1
frame #1: 0x00007ff817086097 Foundation`__45-[NSConcreteTask launchWithDictionary:error:]_block_invoke.239 + 110
Foundation`__45-[NSConcreteTask launchWithDictionary:error:]_block_invoke.239:
->  0x7ff817086097 <+110>: movl  %eax, %ebx
   0x7ff817086099 <+112>: testl %eax, %eax
   0x7ff81708609b <+114>: jns  0x7ff8170860a7      ; <+126>
   0x7ff81708609d <+116>: callq 0x7ff817152bb2      ; symbol stub for: __error
(lldb) di -f
Foundation`__45-[NSConcreteTask launchWithDictionary:error:]_block_invoke.239:
   0x7ff817086029 <+0>:  pushq %rbp
   0x7ff81708602a <+1>:  movq  %rsp, %rbp
   0x7ff81708602d <+4>:  pushq %r15
   0x7ff81708602f <+6>:  pushq %r14
   0x7ff817086031 <+8>:  pushq %r13
   0x7ff817086033 <+10>: pushq %r12
   0x7ff817086035 <+12>: pushq %rbx
   0x7ff817086036 <+13>: subq  $0x68, %rsp
   0x7ff81708603a <+17>: movq  %rdi, %r15
   0x7ff81708603d <+20>: movq  0x41ded264(%rip), %rax  ; (void *)0x00007ff8577b9260: __stack_chk_guard
   0x7ff817086044 <+27>: movq  (%rax), %rax
   0x7ff817086047 <+30>: movq  %rax, -0x30(%rbp)
   0x7ff81708604b <+34>: movq  0x20(%rdi), %r14
   0x7ff81708604f <+38>: addq  0x407aa692(%rip), %r14  ; NSConcreteTask._lock
   0x7ff817086056 <+45>: movq  %r14, %rdi
   0x7ff817086059 <+48>: callq 0x7ff8171534e8      ; symbol stub for: os_unfair_lock_lock
   0x7ff81708605e <+53>: movq  0x28(%r15), %rax
   0x7ff817086062 <+57>: movq  0x407aa6b7(%rip), %rcx  ; NSConcreteTask._dsrc
   0x7ff817086069 <+64>: movq  $0x0, (%rax,%rcx)
   0x7ff817086071 <+72>: movq  0x30(%r15), %rdi
   0x7ff817086075 <+76>: callq 0x7ff817152f66      ; symbol stub for: dispatch_source_cancel
   0x7ff81708607a <+81>: movq  0x407aa697(%rip), %r13  ; NSConcreteTask._pid
   0x7ff817086081 <+88>: leaq  -0x34(%rbp), %r12
   0x7ff817086085 <+92>: movq  0x28(%r15), %rax
   0x7ff817086089 <+96>: movl  (%rax,%r13), %edi
   0x7ff81708608d <+100>: movq  %r12, %rsi
   0x7ff817086090 <+103>: xorl  %edx, %edx
   0x7ff817086092 <+105>: callq 0x7ff817153a04      ; symbol stub for: waitpid
->  0x7ff817086097 <+110>: movl  %eax, %ebx
   0x7ff817086099 <+112>: testl %eax, %eax
   0x7ff81708609b <+114>: jns  0x7ff8170860a7      ; <+126>
   0x7ff81708609d <+116>: callq 0x7ff817152bb2      ; symbol stub for: __error
   0x7ff8170860a2 <+121>: cmpl  $0x4, (%rax)
   0x7ff8170860a5 <+124>: je   0x7ff817086085      ; <+92>
   0x7ff8170860a7 <+126>: movl  $0xffffffff, %eax     ; imm = 0xFFFFFFFF 

NSTask has a lock that it uses to protect its internal state. It’s usually pretty good about only holding that lock for a short period of time, so this deadlock is most likely caused by a failure to unlock.

You can confirm this by looking at the state of the lock. Consider this program:

 1 #include <os/lock.h>
 2 
 3 static os_unfair_lock sLock = OS_UNFAIR_LOCK_INIT;
 4 
 5 int main(int argc, char **argv) {
 6     #pragma unused(argc)
 7     #pragma unused(argv)
 8     os_unfair_lock_lock(&sLock);
 9     return 0;
10 }

Set a breakpoint on line 9 and print the lock:

(lldb) p/x sLock
(os_unfair_lock) $0 = (_os_unfair_lock_opaque = 0x00000000)

A value of 0 indicates that it’s not locked. Now do a step over and re-print:

(lldb) p/x sLock
(os_unfair_lock) $1 = (_os_unfair_lock_opaque = 0x00000103)

0x103 is the Mach port name of the thread holding the lock:

(lldb) p/x mach_thread_self()
(mach_port_t) $2 = 0x00000103

This allows you to track down which thread is holding the lock, and then focus on its backtrace. Is it inside NSTask? Does it look like a place where it’s should be holding that lock?


Alternatively, do a spin dump of the process, because that does a lot of this tracking automatically.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • hello eskimo, Maybe you have misunderstood me, this issue cause by the process which launched by NSTask cann't exit, the NSTask hanged waitpid.

    I get a Sampling file captured by Activity. it seems caused by os_unfair_lock dead lock too! due to cann't upload file in comment, I put the file to [https://we.tl/t-pLlpar3SfD]

  • hi, eskimo, I also send an email to you, Look forward to your reply!

Add a Comment

this issue cause by the process which launched by NSTask can't exit

OK.

Looking at the sample log you sent me, it seems that your test process is deadlocking at startup time. Here’s the backtraces from the log, reformatted to make them easier to read:

Thread 0:
 0 __ulock_wait  (in libsystem_kernel.dylib) + 10  [0x7ff81ba8d20a]
 1 _dlock_wait  (in libdispatch.dylib) + 45  [0x7ff81b9124b0]
 2 _dispatch_thread_event_wait_slow  (in libdispatch.dylib) + 40  [0x7ff81…
 3 _dispatch_apply_invoke_and_wait  (in libdispatch.dylib) + 325  [0x7ff81…
 4 _dispatch_apply_with_attr_f  (in libdispatch.dylib) + 1179  [0x7ff81b92…
 5 __104-[CFPrefsSearchListSource synchronouslySendDaemonMessage:andAgentM…
 6 CFPREFERENCES_IS_WAITING_FOR_SYSTEM_AND_USER_CFPREFSDS  (in CoreFoundat…
 7 -[CFPrefsSearchListSource synchronouslySendDaemonMessage:andAgentMessag…
 8 -[CFPrefsSearchListSource alreadylocked_generationCountFromListOfSource…
 9 -[CFPrefsSearchListSource alreadylocked_getDictionary:]  (in CoreFounda…
10 -[CFPrefsSearchListSource alreadylocked_copyValueForKey:]  (in CoreFoun…
11 -[CFPrefsSource copyValueForKey:]  (in CoreFoundation) + 47  [0x7ff81bb…
12 __76-[_CFXPreferences copyAppValueForKey:identifier:container:configura…
13 __108-[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier…
14 -[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurat…
15 -[_CFXPreferences copyAppValueForKey:identifier:container:configuration…
16 _CFPreferencesCopyAppValueWithContainerAndConfiguration  (in CoreFounda…
17 _CFBundleCopyUserLanguages  (in CoreFoundation) + 63  [0x7ff81bb5d594]
18 _CFBundleCopyLanguageSearchListInBundle  (in CoreFoundation) + 72  [0x7…
19 _copyQueryTable  (in CoreFoundation) + 50  [0x7ff81bb5d111]
20 _copyResourceURLsFromBundle  (in CoreFoundation) + 388  [0x7ff81bb5ca95]
21 _CFBundleCopyFindResources  (in CoreFoundation) + 1400  [0x7ff81bb5be72]
22 CFBundleCopyResourceURL  (in CoreFoundation) + 31  [0x7ff81bb5b8f1]
23 _CFBundleCopyLocalizedStringForLocalizationTableURLAndMarkdownOption  (…
24 CFBundleCopyLocalizedString  (in CoreFoundation) + 27  [0x7ff81bb79d74]
25 _CFCopyLocalizedVersionKey  (in CoreFoundation) + 172  [0x7ff81bb79d17]
26 _CFCopyVersionDictionary  (in CoreFoundation) + 182  [0x7ff81bb79ac0]
27 ___CFCopySystemVersionDictionary_block_invoke  (in CoreFoundation) + 40…
28 _dispatch_client_callout  (in libdispatch.dylib) + 8  [0x7ff81b911cc9]
29 _dispatch_once_callout  (in libdispatch.dylib) + 20  [0x7ff81b912ec1]
30 _CFCopySystemVersionDictionary  (in CoreFoundation) + 55  [0x7ff81bb799…
31 _CFCopySystemVersionDictionaryValue  (in CoreFoundation) + 28  [0x7ff81…
32 ___CFOperatingSystemVersionGetCurrent_block_invoke  (in CoreFoundation)…
33 _dispatch_client_callout  (in libdispatch.dylib) + 8  [0x7ff81b911cc9]
34 _dispatch_once_callout  (in libdispatch.dylib) + 20  [0x7ff81b912ec1]
35 _CFOperatingSystemVersionGetCurrent  (in CoreFoundation) + 71  [0x7ff81…
36 -[NSProcessInfo operatingSystemVersion]  (in Foundation) + 40  [0x7ff81…
37 ???  (in QtCore)  load address 0x11232f000 + 0x2a4248  [0x1125d3248]
38 ???  (in QtCore)  load address 0x11232f000 + 0x13c29  [0x112342c29]
39 invocation function for block in dyld4::Loader::findAndRunAllInitialize…
40 invocation function for block in dyld3::MachOAnalyzer::forEachInitializ…
41 invocation function for block in dyld3::MachOFile::forEachSection(void …
42 dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command c…
43 dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo co…
44 dyld3::MachOAnalyzer::forEachInitializerPointerSection(Diagnostics&, vo…
45 dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnal…
46 dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const  (…
47 dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Arr…
48 dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Arr…
49 dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeSta…
50 dyld4::APIs::dlopen_from(char const*, int, void*)  (in dyld) + 563  [0x…
51 Loader::CModuleLoader::LoadModuleByApi(wchar_t const*)  (in libsafebase…
52 PEUtils::CSafeModuleLoader::LoadModuleById(wchar_t const*)  (in libqaxb…
53 Unix::CUIThreadCore::Init()  (in libqaxbase.dylib) + 125  [0x1119de23d]
54 UIThread::CUIThread::CUIThread()  (in libqaxbase.dylib) + 58  [0x1119dd…
55 void std::__1::__call_once_proxy<std::__1::tuple<unsigned int internal_…
56 std::__1::__call_once(unsigned long volatile&, void*, void (*)(void*)) …
57 unsigned int Base::CQAXObjectImpl<UIThread::CUIThread, Base::empty_lock…
58 CQAXBase::CreateSomeObjects()  (in libqaxbase.dylib) + 383  [0x111a4416…
59 non-virtual thunk to CQAXBase::Initialize()  (in libqaxbase.dylib) + 36…
60 Base::CSafeBase::InitQAXBase(char const*)  (in libsafebase.dylib) + 134…
61 Base::CSafeBase::InitAllQAXBase()  (in libsafebase.dylib) + 404  [0x110…
62 non-virtual thunk to Base::CSafeBase::Initialize()  (in libsafebase.dyl…
63 SafeBase::CSafeBaseHelper::InitModule(void*, bool)  (in test) + 359  [0…
64 QAXBase::CAppHelper::Init(int, char**, void*, bool)  (in test) + 66  [0…
65 main  (in test) + 109  [0x10fc7233d]
66 start  (in dyld) + 462  [0x11855d4fe]

Thread 1:
 0 __ulock_wait  (in libsystem_kernel.dylib) + 10  [0x7ff81ba8d20a]
 1 _os_unfair_lock_lock_slow  (in libsystem_platform.dylib) + 162  [0x7ff8…
 2 dyld4::APIs::dlopen_from(char const*, int, void*)  (in dyld) + 271  [0x…
 3 __CFStringEncodingGetExternalConverter  (in CoreFoundation) + 222  [0x7…
 4 __CFGetConverter  (in CoreFoundation) + 316  [0x7ff81bb138d9]
 5 CFStringEncodingGetConverter  (in CoreFoundation) + 9  [0x7ff81bb1378f]
 6 __CFStringDecodeByteStream3  (in CoreFoundation) + 825  [0x7ff81bb38956]
 7 __CFStringAppendBytes  (in CoreFoundation) + 362  [0x7ff81bb428c2]
 8 __CFStringAppendFormatCore  (in CoreFoundation) + 9817  [0x7ff81bb4058f]
 9 _CFStringCreateWithFormatAndArgumentsReturningMetadata  (in CoreFoundat…
10 CFStringCreateWithFormatAndArguments  (in CoreFoundation) + 141  [0x7ff…
11 CFStringCreateWithFormat  (in CoreFoundation) + 128  [0x7ff81bb3def3]
12 -[CFPrefsSource copyOSLogDescription]  (in CoreFoundation) + 214  [0x7f…
13 -[CFPrefsPlistSource handleReply:toRequestNewDataMessage:onConnection:r…
14 __93-[CFPrefsSearchListSource handleReply:toRequestNewDataMessage:onCon…
15 xpc_array_apply  (in libxpc.dylib) + 62  [0x7ff81b80ca80]
16 -[CFPrefsSearchListSource handleReply:toRequestNewDataMessage:onConnect…
17 __80-[CFPrefsSearchListSource alreadylocked_generationCountFromListOfSo…
18 __104-[CFPrefsSearchListSource synchronouslySendDaemonMessage:andAgentM…
19 -[_CFXPreferences withConnectionForRole:performBlock:]  (in CoreFoundat…
20 __104-[CFPrefsSearchListSource synchronouslySendDaemonMessage:andAgentM…
21 _dispatch_client_callout2  (in libdispatch.dylib) + 8  [0x7ff81b911cfc]
22 _dispatch_apply_invoke  (in libdispatch.dylib) + 213  [0x7ff81b9231d5]
23 _dispatch_client_callout  (in libdispatch.dylib) + 8  [0x7ff81b911cc9]
24 _dispatch_root_queue_drain  (in libdispatch.dylib) + 680  [0x7ff81b9215…
25 _dispatch_worker_thread2  (in libdispatch.dylib) + 160  [0x7ff81b921b5a]
26 _pthread_wqthread  (in libsystem_pthread.dylib) + 256  [0x7ff81bac5049]
27 start_wqthread  (in libsystem_pthread.dylib) + 15  [0x7ff81bac401b]

Let’s start with thread 0. Frames 66 and 65 are the standard start sequence, calling into your code in frame 64. Frames 64 through 51 is your code starting up. Frame 50 indicates that your code has called dlopen. Frames 50 through 39 are the dynamic linker doing stuff, eventually calling a library initialiser in frame 38. Frames 38 through 37 are that library initialiser, which eventually calls -[NSProcessInfo operatingSystemVersion] in frame 36. That runs a whole bunch of CF initialisation code which deadlocks in frame 0.

Now consider thread 1. This is much simpler (-: Frames 27 through 21 are the standard Dispatch ‘run a block on a queue’ setup. Frames 20 through 3 are CF initialisation code. Frame 2 indicates that it’s called dlopen, and frames 1 through 0 confirm that dlopen has deadlocked waiting for the dynamic linker’s global lock.

So, the dynamic linker’s global lock is one part of this deadlock. It’s held in frame 50 of thread 0 and you’re trying to acquire it in frame 2 of thread 1. The second lock is more subtle. I’m not 100% confident of my conclusions but it’s most likely related to implicit serialisation done by the XPC connection to cfprefsd.

There’s a bunch of different conclusions here:

  • We generally discourage folks from creating library initialisers, so frame 38 of thread 0 is less than ideal.

  • However, the work being done by that initialiser isn’t exactly onerous. -[NSProcessInfo operatingSystemVersion] should be fairly lightweight.

  • It’s not clear why this is only deadlocking when you run it repeatedly via NSTask but, *shrug*, that’s probably just changed the timing.

  • Regardless, it’s definitely a problem that Apple should investigate, so I encourage you to file a bug about this. Make sure to attach a sysdiagnose log taken while the process is deadlocked. Also, if you can attach the code necessary to reproduce the problem that’d be grand.

    Please post your bug number, just for the record.

  • As to how you can work around this, I suspect that adding a call to -[NSProcessInfo operatingSystemVersion] to the start of your main function (thread 0 frame 65) should avoid the problem. That’ll get all the CF initialisation done outside of the context of the dynamic linker’s global lock.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"