[Pitch] Inherit isolation by default for async functions

(This is a fast-moving discussion, so apologies for butting in to reply to this) This is actually one of the main reasons why I am +1 on this proposal. Being stuck on the calling isolation "for a bit longer" is actually very useful; for example, it allows for putting queueing logic at the top of your function, with the knowledge that within an isolation domain all calls are sequenced. This has been the cause of real-world bugs I have seen–here is a toy example:

func send(data: Data) async {
	await withCheckedContinuation { continuation in
		legacySendDataWithCompletion(data: data) {
			continuation.resume()
		}
	}
}

The bug here is that calling send(data:) in program order (let's say the two calls come from the same isolation domain) does not necessarily mean legacySendDataWithCompletion(data:completion:) is called in the same order. What might happen is the first call to send(data:) is scheduled but never executed, and the second one goes through and hits legacySendDataWithCompletion(data:completion:) first. This might have observable side effects if this is doing something like sending data over a socket, which would now appear out of order. In this case the critical section before control is lost is (depending on how you look at it) one or even zero lines of code. But the invariant of "I called this and it hasn't done anything async yet, so surely no switching has happened yet" is pervasive and creates concurrency issues. Of course, inheriting isolation explicitly "solves" this problem–but my point is that I believe this is how people already feel the code should behave. Especially since we bridge callbacks in as async functions which do behave this way, and from the call site it is impossible to tell them apart even though they have very different semantics.

8 Likes