Leaking tasks if a Channel receiver is no longer listening

So excited to see where these new async algorithms go! I've been playing around with them and wondering about a tweaked case of this example in the docs.

let channel = AsyncChannel<String>()
Task {
  while let resultOfLongCalculation = doLongCalculations() {
    await channel.send(resultOfLongCalculation)
  }
  await channel.finish()
}

for await calculationResult in channel {
  print(calculationResult)
}

How would the sender of channel values know that the listener has stopped listening? For instance, what if the for loop looked like this

for await calculationResult in channel {
  if weGotEnough { break }
  // ....
}

In this case we have a condition that leaves the loop early. But the sender of the channel has already started awaiting on the send() operation and is now just sitting there. Could there some way to signal back that the listening is done? Even if we kept around some sort of Task to cancel or even another Channel that signals that we're done that the sender can listen to, it seems that once the await channel.send() happens, the task is effectively blocked until someone asks for the result.

Take this example...

let channel = AsyncChannel<Int>()
let task = Task {
  print("awaiting")
  await channel.send(1)
  print("done sending")
}
try! await Task.sleep(nanoseconds: 1_000_000_000)
task.cancel()
print("cancelled task")

This outputs:

awaiting
cancelled task

And the await within the task never returns. This is an easy footgun for leaking tasks.

It seems like Channel.send should be throwing so that a cancellation of the outer task would make sure we don't leak like this.

3 Likes

Perhaps we could put a withTaskCancellationHandler in there somehow to guard against that? Please file a bug - I think it is achievable to fix (but may be tricky).

1 Like

So you’re saying it would just return immediately once cancelled? And then it should be the senders responsibility to check for cancellation itself? That seems reasonable to me.

Yea, if the sending task gets cancelled it perhaps also should claim itself to be a finished state too (making subsequent sends a no-op.

2 Likes