Home > Net >  SIGSEGV in objc_release when using pthreads
SIGSEGV in objc_release when using pthreads

Time:11-18

I'm running some Metal code in a thread, but encountering some issues I don't fully understand. Running the following code with USE_THREAD 0 and USE_AUTORELEASEPOOL 0 works fine but setting either one to 1 results in a SIGSEGV in objc_release:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x20)
  * frame #0: 0x00007fff2020d4af libobjc.A.dylib`objc_release   31
    frame #1: 0x00007fff2022b20f libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**)   167
    frame #2: 0x00007fff2020de30 libobjc.A.dylib`objc_autoreleasePoolPop   161
    frame #3: 0x0000000100003d60 a.out`render(void*)   896
    frame #4: 0x0000000100003dd8 a.out`main   24
    frame #5: 0x00007fff20388f3d libdyld.dylib`start   1
    frame #6: 0x00007fff20388f3d libdyld.dylib`start   1

Using the autoreleasepool I can understand since the objects are already released (since release is called manually on them), but why does the same issue occur when the code is running inside a thread? Is this related to pthreads specifically? Is there a "hidden" autoreleasepool somewhere I am missing?

I understand using an autoreleasepool and not releasing manually will achieve the same result but I am trying to understand what is going on here.

// clang   main.mm -lobjc -framework Metal
#define USE_THREAD 0
#define USE_AUTORELEASEPOOL 1

#import <Metal/Metal.h>

void * render(void *) {
    #if USE_AUTORELEASEPOOL
    @autoreleasepool {
    #else
    {
    #endif
        NSArray<id<MTLDevice>> * devices = MTLCopyAllDevices();
        id<MTLDevice> device = devices[0];

        id<MTLCommandQueue> command_queue = [device newCommandQueue];

        MTLTextureDescriptor * texture_descriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:640 height:480 mipmapped:NO];
        texture_descriptor.usage = MTLTextureUsageRenderTarget;

        id<MTLTexture> texture = [device newTextureWithDescriptor:texture_descriptor];

        [texture_descriptor release];
        texture_descriptor = NULL;

        id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];

        MTLRenderPassDescriptor * render_pass_descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
        render_pass_descriptor.colorAttachments[0].texture = texture;

        id<MTLRenderCommandEncoder> render_command_encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];

        [render_pass_descriptor release];
        render_pass_descriptor = NULL;

        [render_command_encoder endEncoding];

        [render_command_encoder release];
        render_command_encoder = nil;

        [command_buffer commit];
        [command_buffer waitUntilCompleted];

        [command_buffer release];
        command_buffer = nil;

        [texture release];
        texture = nil;

        [command_queue release];
        command_queue = nil;
    }

    return 0;
}

#include <pthread.h>

int main() {
    #if USE_THREAD
        pthread_t thread;
        pthread_create(&thread, NULL, render, NULL);

        pthread_join(thread, NULL);
    #else
        render(NULL);
    #endif

    return 0;
}

CodePudding user response:

I'm not sure if Apple has ever updated their documentation in regards to this behaviour, but approximately since macOS 10.7, all POSIX threads were populated with autorelease pool block automatically. You can clearly see it from dealloc of autoreleased objects:

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

@interface TDWObject : NSObject

@end

@implementation TDWObject

- (void)dealloc {
    [super dealloc];
    NSLog(@"%@", [NSThread callStackSymbols]);
}

@end


void *run(void *objPtr) {
    TDWObject *obj = (__bridge TDWObject *)objPtr;
    [obj autorelease];
    return NULL;
}


int main() {
    pthread_t thread;
    TDWObject *obj = [TDWObject new];
    pthread_create(&thread, NULL, run, (__bridge void *)obj);
    pthread_join(thread, NULL);
    return 0;
}

This code (when compiled with MRC) will print the following stacktrace:

-[TDWObject dealloc]   83
_ZN19AutoreleasePoolPage12releaseUntilEPP11objc_object   168
objc_autoreleasePoolPop   227
_ZN20objc_tls_direct_baseIP19AutoreleasePoolPageL7tls_key3ENS0_14HotPageDeallocEE5dtor_EPv   140
_pthread_tsd_cleanup   607
_pthread_exit   70
_pthread_start   136
thread_start   15

objc_autoreleasePoolPop function is exactly what gets called in the end of autorelease pool blocks nowadays.

  • Related