-
Notifications
You must be signed in to change notification settings - Fork 662
Description
The implementation of FuturesUnordered::poll_next
never yields as long as at least one future inside of it is ready. This is problematic if it manages a sufficient number of futures that the time it takes to poll all the ready futures is larger than the time it takes between when any given future it manages becomes ready. FuturesUnordered::poll_next
will then enter what is effectively an infinite loop, holding up the executor and starving other tasks in the system.
For a simple example of this, consider a FuturesUnordered
that manages futures that run on retry timers. If polling all the ready futures takes longer than the retry time of any one future, then by the time it finishes polling all the futures, the first future it polled will be ready again since its retry timer expired.
The easiest way to fix this is to place a limit on how many calls to Future::poll
a given call to FuturesUnordered::poll_next
can make. If the limit is exceeded, yield by calling cx.waker().wake_by_ref()
and return Poll::Pending
.
It is not inherently an issue to yield Some
many times in a row, so only counting the calls inside of poll_next
should be sufficient. It does place the burden on the caller to implement this kind of limiting themselves too though, so there is an argument for doing a "forced yield" every N
calls to Future::poll
, resetting the counter whenever we return Poll::Pending
(but not when we return Poll::Ready(Some(_))
). This would help other Future
implementors that wrap FuturesUnordered
, since they will get the protection from accidentally blocking the executor by no yielding for too long "for free".