15 Jun 2014 NSAutoreleasePool within pthreads on Debian.

Ever used such combination? Probably not, oh well... But if you did, you'd see a segfault or two (or dozens), I promise you that.

Consider a really simple example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <Foundation/NSAutoreleasePool.h>
#import <pthread.h>
#import <stdlib.h>

static void *func(void *arg) {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    printf(arg);
    [pool release];
    return arg;
}

int main() {
    pthread_t t;
    void *res;
    pthread_create(&t, NULL, func, "HELLO ");
    pthread_join(t, &res);

    NSAutoreleasePool pool = [NSAutoreleasePool new];
    printf("WORLD\n");
    [pool release];
    return 0;
}

and that's it. Multithreaded, memory corrupting application is ready!

Now, if this very snippet was using NSThreads instead of pthreads, it would work perfectly. But GNUStep's NSThreads are actually implemented using ptheads! So what's happening here?

The truth lies in the NSThread implementation. Burried under tons of weird code lines... The current NSAutoreleasePool needs a way to know what thread it is in at the given time. NSThread initializer is preparing some global structs (yuk?!) and other pieces of some strange machinery for this to work. But when we just create a pthread directly, GNUStep gets lost.

After some hours of digging in the NSThread code, I've found the one: the _createThreadForCurrentPthread method. Place [NSThread _createThreadForCurrentPthread] message just before creating NSAutoreleasePool from a different thread than it was used before and you're done.

1
2
3
4
5
6
7
...
    pthread_join(t, &res);

    [NSThread _createThreadForCurrentPThread];
    NSAutoreleasePool pool = [NSAutoreleasePool new];
    printf("WORLD\n");
...

Hang on, hang on, but that's clearly a GNUStep problem! What does Debian have to do with all this?! Well, it is a GNUStep bug indeed, but it's been fixed for like, 3 years! Yet Debian folks are still on a buggy version, even in unstable. That's what got me into this bug in the first place. If not for that, nobody would probably ever experience it nowadays. And then there is this one, a more than 2 years old thread on the mailing list considering movement to the newer version of GNUStep. There were quite some lengthy messages, some philosophical thoughts (Debian, you know), etc. and in the meanwhile, nobody's got the damned job done to this day... Shame.

Okay, enough with that. One more road sign for those who feel adventurous and wanna dig into GNUStep: GSCurrentThread is the function responsible for retrieving proper thread for the current context. It is used internally by NSAutoreleasePool. You can check out how it changed for the bug fix.

You might also want to strip this hack out, if your application is compiled against new enough GNUStep version. This is easily done by using #if GNUSTEP_BASE_MINOR_VERSION < 24 directive.