objective-cmacosmemory-leaksmetalmtlbuffer

Block passed to deallocator for newBufferWithBytesNoCopy is never called/used


I have the following inside the main function. I have ensured earlier that CONTENT_SIZE is page aligned.

if (!(Queue = [Device = MTLCreateSystemDefaultDevice() newCommandQueue])) // Set global variables
    return EXIT_FAILURE;
{
    const id<MTLBuffer> data = [Device newBufferWithBytesNoCopy:({
        void *const map = ({
            NSFileHandle *const file = [NSFileHandle fileHandleForReadingAtPath:[NSBundle.mainBundle pathForResource:@"Content" ofType:nil]];
            if (!file)
                return EXIT_FAILURE;
            mmap(NULL, CONTENT_SIZE, PROT_READ, MAP_SHARED, file.fileDescriptor, 0);
        });
        if (map == MAP_FAILED)
            return EXIT_FAILURE;
        map;
    }) length:CONTENT_SIZE options:MTLResourceStorageModeShared deallocator:^(void *const ptr, const NSUInteger len){

        munmap(ptr, len); // Never called

    }];
    if (!data)
        return EXIT_FAILURE;
    const id<MTLCommandBuffer> buffer = [Queue commandBuffer];
    const id<MTLBlitCommandEncoder> encoder = [buffer blitCommandEncoder];
    if (!encoder)
        return EXIT_FAILURE;

    // Below, the contents of buffer are copied to MTLTexture

    MTLTextureDescriptor *const descriptor = [MTLTextureDescriptor new];
    descriptor.width = descriptor.height = 32;
    descriptor.mipmapLevelCount = 6;
    descriptor.textureType = MTLTextureType2DArray;
    descriptor.storageMode = MTLStorageModePrivate;
    const enum MTLPixelFormat format[] = {MTLPixelFormatR8Unorm, MTLPixelFormatRG8Unorm, MTLPixelFormatRGBA8Unorm};
    const NSUInteger len[] = {TEX_LEN_1, TEX_LEN_2, TEX_LEN_4};
    for (NSUInteger i = 3, off = 0; i--;) {
        descriptor.pixelFormat = format[i];
        const NSUInteger l = descriptor.arrayLength = len[i];
        const id<MTLTexture> texture = [Device newTextureWithDescriptor:descriptor];
        if (!texture)
            return EXIT_FAILURE;
        const NSUInteger br = 32<<i, bi = 1024<<i;
        for (NSUInteger j = 0; j < l; off += bi)
            [encoder copyFromBuffer:data sourceOffset:off sourceBytesPerRow:br sourceBytesPerImage:bi sourceSize:(const MTLSize){32, 32, 1} toTexture:texture destinationSlice:j++ destinationLevel:0 destinationOrigin:(const MTLOrigin){0}];
        [encoder generateMipmapsForTexture:BlockTexture[i] = texture]; // BlockTexture is a global variable
    }

    [encoder endEncoding];
    [buffer commit];
}

But I noticed that the block passed for deallocator: is never called. I can put puts(), abort(), whatever in there, and nothing happens. This means no deallocation is taking place, even long after the data has been copied to the textures and is no longer needed and the reference to the buffer goes out of scope.

I view this as a memory leak. How can I ensure that the mmap()ed memory is properly munmap()ed after the buffer is no longer needed?


Solution

  • With the code in the main() function, and no @autoreleasepool, the object is never released because there is no pool that drains before the program terminates. This causes the deallocator to never be called.

    The solution is to enclose all relevant code inside the main() function in an @autoreleasepool { } block, as originally suggested in a comment.