Crash in deinit when using Task

I'm doing this:

public class BlockHandle {
    public var offset: UInt64 = 0

    public init(block: VolBlock) async {
        offset = await manager.addBlock(block: block)
    }

    deinit {
        Task {
           await manager.rmBlock(offset: offset)
        }
    }
}

(manager is an actor)

I get:

xctest(62712,0x100098600) malloc: Incorrect checksum for freed object 0x102e29108: probably modified after being freed.

when I change the deinit to:

    deinit {
        let off = offset
        Task {
           await manager.rmBlock(offset: off)
        }
    }

then the crash goes away.

Isn't this supposed to be safe? I'm new to async/await.

Thanks for any help!

This is not supposed to be safe: you have escaped a self reference in deinit, and this is never ok.

Your proposed alternative works, as does:

    deinit {
        Task { [offset] in
           await manager.rmBlock(offset: offset)
        }
    }

This should almost certainly be accompanied by a diagnostic, and Swift is entirely capable of performing that diagnosis (self captured by an @escaping closure). So I recommend filing a bug report asking for it.

7 Likes

Ok, but aren't there many legitimate cases of @escaping closures capturing self? Wouldn't that be the case for many uses of Task? Seems the crux of this issue is that the object is going to be deallocated after deinit and there's no way to keep it alive, no?

Yup, but not in deinit: they're all invalid. That's what can be detected.

2 Likes

Ok, cool. I filed FB9957094.