'inout sending' parameter cannot be task-isolated at end of function

I'm trying to convert some code from using NSLock to the new Mutex now that this bug is fixed.

In the following, I don't understand what is substantively different about these two functions that causes a compiler error in the second one:

    public init(blocks: [VolBlock]) throws {
        self.blocks = try Self.alloc.withLock { alloc in
            try blocks.withUnsafeBufferPointer { ptr in
                try alloc.alloc(blocks: ptr.baseAddress!, count: blocks.count)
            }
        }
    }

    public init(blocks: UnsafeBufferPointer<VolBlock>) throws {
        self.blocks = try Self.alloc.withLock { alloc in
            try alloc.alloc(blocks: blocks.baseAddress!, count: blocks.count)
               // Error: 'inout sending' parameter 'alloc' cannot be task-isolated at end of function
        }
    }

Perhaps related to this thread but I can't make sense of it.

EDIT: ok now I get the error on the first one too when moving the withLock inside:

public init(blocks: [VolBlock]) throws {
        self.blocks = try blocks.withUnsafeBufferPointer { ptr in
            try Self.alloc.withLock { alloc in
                try alloc.alloc(blocks: ptr.baseAddress!, count: blocks.count)
                    // Error: 'inout sending' parameter 'alloc' cannot be task-isolated at end of function
            }
        }
    }

Still don't really understand this.

i think the 'spirit' of this diagnostic is to deal with the fact that a Mutex can be populated with a non-Sendable value, but in order to do so it must be known to be in a 'disconnected' isolation region (i.e. is a sending value). since Mutex grants 'Sendability' to its contents, it must ensure that any subsequent changes to its contents do not alias non-Sendable values from other isolation regions.

as an simplified example, consider:

// non-Sendable type
class NS {}

final class C: Sendable {
    let lock = Mutex(NS()) // okay to init since the initial value isn't in any isolation region

    func setValue(_ v: NS) {
        lock.withLock { existing in
            // not okay to assign b/c `v` could have come from another isolation
            // region so could introduce a data race
            existing = v
        } // 🛑
    }
}

i feel like what you're 'supposed' to do in this circumstance is ensure the new value being assigned is also known to the compiler to be sending. AFAIK however, this for some reason still doesn't work currently (see a similar thread here).

1 Like

Thanks for your help! I get it now and I was able to make it work :slight_smile:

1 Like