Capture of 'self' with non-sendable closure

I've been working on this libmodbus wrapper, and I want to serialize accesses to the libmodbus context. I do so like this:

	public
	func
	readRegister(fromDevice inDeviceID: Int, atAddress inAddr: Int)
		async
		throws
		-> UInt16
	{
		try await withCheckedThrowingContinuation
		{ inCont in
			self.workQ.async
			{
				do
				{
					self.deviceID = inDeviceID    //  <-- capture of 'self' with non-sendable type 'MODBUSContext' in a `@Sendable` closure
					let r = try self.readRegister(address: inAddr)
					inCont.resume(returning: r)
				}
				
				catch (let e)
				{
					inCont.resume(throwing: e)
				}
			}
		}
	}

This method exists with in a final class MODBUSContext definition.

I went down the path of trying to make MODBUSContext conform to Sendable, but it turns out DispatchQueue doesn’t conform. So, I’m not sure how to clean this up. Any suggestions?

Use @unchecked Sendable. If the only thing that doesn't conform is DispatchQueue, then you can tell the compiler to shut up: you know that DispatchQueue is actually thread-safe, so you can just mark your type appropriately.

1 Like

Does that mean DispatchQueue should conform?

That is a strong word that infers moral obligation. I would say that; DispatchQueue safely meets the requirements of Sendable except one set of caveats; DispatchQueue.sync. If you avoid that and similar functions then the type is reasonable to use in an actor and in conjunction with existing systems.

I mean "should" because it's extraordinarily common to use it in this manner, isn't it, to make an Actor? It should conform in order to relieve the burden on everyone using it like this.

But, maybe I'm Doing It Wrong.

While Swift concurrency does strongly discourage the use of thread-blocking patterns like DispatchQueue.sync, the method itself doesn't violate any sendability restrictions. And as long as the queue isn't backed by a width-limited queue, you won't get exhaustion deadlocks. You can, of course, get other kinds of deadlock if you call it while holding some other thread-exclusive resource. @rokhinip may be able to correct me here.

So DispatchQueue ought to be Sendable.

2 Likes

For now, then, it should be enough to just declare conformance with an extension, right?

extension DispatchQueue : Sendable { }