How are you meant to use Select.async correctly?

  1. The implementation is allowed to run the task eagerly, blocking the calling thread before the Select.async() call even returns
  2. The task has to append its completion value to a blocking queue before it can finishes
  3. The queue has a limited capacity

So, if the queue is full, and nobody is consuming results with select.await() (this will easily happen if the task that spawns Select.async tasks is the same task that calls Select.await), any call to select.async() can deadlock.

This sounds a rather serious footgun. So by my understanding, to use Select.async, you have to guarantee one of the following:

  • There is enough capacity in the internal queue for the result, including any other in-flight tasks. This is kind of annoying because you have to track the number of in-flight tasks yourself.
  • Someone else is calling await concurrently.

I’m not sure if this is an intended (or at least unavoidable) effect of how Select works, but it should probably at least be documented?

I don’t think this is unique to Select. This behavior is true for any of the async functions for Group, Batch, etc. This comes from Asynchrony is not Concurrency in that calling async says that it doesn’t matter the order in which it is run. It could be run immediately if the underlying implementation determines that should be done. Calling .concurrent() should be used in any cases where it MUST not block.


For the rest of you post, I think you have the right read on it, however I would push back on whether Select is the right thing to use in the case where you are spawning more tasks than the queue can handle. Select is supposed to be used for cases where you want at least N tasks to complete and cancel the rest at that point. The key use case is DNS requests where you don’t care which one succeeds, so long as one of them completes and you take the first result.
I’m not sure of a case that would lead to what you are outlining for the design of Select.

Of course. The issue is that the implementation can make any task a “must not block” task, and this is not immediately obvious.