Search code examples
objective-ccocoastack-overflowxpcnsxpcconnection

XPC Service: Raise Stack Size on Worker Thread


Background Context:

I have an application built for macOS 10.11+. This app needs to run some third-party code that may occasionally crash (The Libsass compiler). To avoid bringing down my entire app, I've created an XPC service (using NSXPCConnection and the standard XPC Service Template in Xcode) that runs this code.


My Problem:

The XPC Service appears to be setting the stack size of the thread where it runs my code to 512kb, which is the macOS default for threads other than the main thread. (The main thread's stack size is 8MB by default.)

The Libsass compiler sometimes crashes with a stack overflow on certain files that involve deep recursion (and thus many stack frames).

NB: I KNOW this is the problem because if I run the exact same Libsass sequence on my app's main thread (without using XPC), everything works fine and the stack overflow never happens.


What I Need:

A way to increase the stack size of the thread where my XPC service performs work.


What I Have Tried:

I have already added -Wl,-stack_size,4000000 to the "other linker flags" build setting for my XPC service target. This sets the stack size to 64MB (the largest allowed). After building, I inspect the XPC package in the Finder and verify that this flag was applied correctly by the linker using this command:

otool -lV codekit-libsass-service.xpc/Contents/MacOS/codekit-libsass-service | grep stack

Which produces this output: stacksize 67108864, indicating that the stack size has indeed been set to 64MB.

The trouble is that this applies only to the main thread of the service and my XPC method does not appear to be called on the service's main thread. I verified this by using [NSThread isMainThread], which returns false when called within my XPC method.

So, I THEN tried forcing the work to be done on the main thread using dispatch_async(dispatch_get_main_queue() ^{...}); However, -isMainThread still returns false from within that block. Moreover, if I use [NSThread currentThread] stackSize], I get 524288, which indicates that the thread where this code is running has a 512kb stack. Fantastic.

I have also tried raising the stack size dynamically at runtime using setrlimit(). This did not resolve the issue.

I've read a bunch of docs. The specifics of which threads XPC services use and how they use them appear to be implementation details we're not supposed to worry about.


Solution

  • Although this works, I cannot imagine it's the best approach. Basically, if you have an XPC method like this:

    - (void) doStuffWithObject:(NSDictionary *)dict withReply:(void (^)(NSString *))reply 
    {
        // Code that needs a bigger stack. 
    }
    

    Then you can do this:

    - (void) doStuffWithObject:(NSDictionary *)dict withReply:(void (^)(NSString *))reply 
    {
        NSThread *thread = [[NSThread alloc] initWithBlock:
        ^{
             // Code that needs a bigger stack
        }];
    
        [thread setStackSize:67108864];     // 64MB. Must be in multiples of 4096.
        [thread start];
    }
    

    There's overhead involved in setting up a thread every time this method is called, but this is (so far) the only way I can think of to increase the stack size.

    Anyone have a better approach?