Thanks @Diggsey! That's a great description of the problem - and I love the visualizations!
futures-intrusive fair mutex types indeed work like you described - which means if an application stops polling the generated
Futures before they completed some concurrent tasks might be starved, due to the Mutex having been acquired in the background when another task released it.
We had some discussions around these issues before, with @Nemo157 for example. I think so far the common understanding was not continuing to
poll() things before completion is considered an application bug. Even if you have
select!, the typical flow will will either continue to
Future (if a loop is used) or the
Future will get dropped.
But it wouldn't be surprising if some unexpected bugs sneak into applications this way.
There are a couple of other issues with "intra-task-concurrency" due to
select! too - e.g. if one of the child-futures blocks the thread (e.g. with
thread::sleep or tokios
block_in_place), the other child
Futures will be starved. There is not a lot we can do besides adding documentation around the behavior.
However more interesting than just looking a
Futures which might not be polled to completion is actually looking at
Streams -> For those there is not necessarily a completion state.
Stream could wrap a
Future which does not act ideal if not polled to completion (like an async Mutex), then stopping to
Stream causes the same issue. Here the only remedy is to close/drop the whole
We had a bit of discussion in this CR, which builds a
Stream based of
Futures that require getting polled to completion: When the
Stream is polled initially, it will create and store a
Future in itself, and from there on the inner
Future is polled. If the
Stream is not continued to be polled, the
Future will just stick around.
Streams and other
AsyncRead/Write the contract is likely "you have to continue
polling the object until it returned
Poll::Pending was returned in the last poll, then stopping to poll might cause [not memory related] undefined behavior.
One API solution that could be added to those types is having some kind of
cancel() method which stops and in-progress operation. That one would need to be called if an application does not intend to
poll the object anymore. But that new API would raise some new questions. E.g. whether one would need to
poll after the cancellation had been initiated, since the cancellation might be async.