Home > database >  Large POD struct copy to stack crashes in a worker thread
Large POD struct copy to stack crashes in a worker thread

Time:02-04

iOS project in a mix of Obj-C and C proper. I have a POD struct about 1 MB in size. There is a global instance of it. If I create a local instance of the same in a function that is invoked on a worker thread, the copy operation crashes (when invoked from a worker thread) in debug builds on a simulator. The release builds don't crash.

This smells like running out of stack size.

The worker thread in question is not created manually - is an NSOperationQueue worker.

The questions are twofold:

  • why does automatic stack growth fail?
  • how does one increase stack size on NSOperationQueue threads?

The repro goes:

 
struct S
{
     char s[1024*1024];
};

S gs;

-(void)f
{
    S ls;
    ls = gs; //Crash!
}

CodePudding user response:

I don't know how up to date this documentation is, but according to Apple non-main threads top out at 512kiB stack size unless otherwise configured during thread creation.

I struggle to see a good reason to store such a data structure on the stack, particularly with (Obj-)C where you can easily wrap it in something like a std::unique_ptr which manages heap allocations and deallocations automatically. (Or indeed any other RAII based abstraction, or even storing it as an ivar in an ARC-enabled Objective-C class if you're so inclined.)

CodePudding user response:

Under the hood the threading mechanism of Cocoa uses unix POSIX threads for which stack size follows the following rules:

  • Default stack size, if it's not explicitly specified (e.g. in macOS you can find this value by running ulimit -s command, which for my machine is 8192 bytes, but for iOS is very likely a few times less)
  • Arbitrary stack size if it's specified during creating of a thread

So for the main thread of an application it's always the default system stack size, and can only be altered in macOS (or rooted iOS device) with use of ulimit (size is given in KiB):

# Sets 32 MiB default stack size to a thread
% ulimit -s 32768

All other threads (to my knowledge) under both iOS and macOS have their size specified explicitly and it equals to 512 KiB. You will have to somehow forward the stack size to the pthread_create(3) function for them, something like this:

#import <Foundation/Foundation.h>
#import <pthread.h>

struct S {
    char s[1024 * 1024];
};

void *func(void *context) {
    // 16 MiB stack variable
    S s[16];
    NSLog(@"Working thread is finished");
    auto* result = new int{};
    return result;
}

int main(int argc, const char * argv[]) {
    pthread_attr_t attrs;
    auto s = pthread_attr_init(&attrs);
    // Allocates 32 MiB stack size
    s = pthread_attr_setstacksize(&attrs, 1024 * 1024 * 32);

    pthread_t thread;
    s = pthread_create(&thread, &attrs, &func, nullptr);
    s = pthread_attr_destroy(&attrs);
    void* result;
    s = pthread_join(thread, &result);
    if (s) {
        NSLog(@"Error code: %d", s);
    } else {
        NSLog(@"Main is finished with result: %d", *(int *)result);
        delete (int *)result;
    }


    @autoreleasepool {
    }
    return 0;
}

Unfortunately neither of queue API (GCD or NSOperation) exposes allocation part of their thread pools, let alone that NSThread doesn't let you to specify your own pthread explicitly for underlying execution. If you want to rely on those APIs, you will have to implement it "artificially"

  • Related