Sure it sometimes magically fixes problematic code, but it's in effect still a magic extension to how rust polling works which can have surprising side effects.
In a certain way tokios coperative-preempting is not adequate to handle any future which multiplexes multiple futures but such futures are a stable of rust async since the get to go.
Which isn't an option for a lot of libraries.
While the rest of the rust eco-system is increasingly moving to have increasingly more parts runtime independent...
Furthermore I think `maybe_yield` wouldn't be quite the right solution. The problem is that tokios magic works based on the assumption that a single task (future scheduled by the runtime) represents a single logical strang of exexution. (Which isn't guaranteed in rust.)
So I think a better tokio specific solution would be to teach tokio about the multiplexing effect in some way.
For example you could have some way which snapshots the budged when reaching the multiplexer, and reset the budget to the snapshot before every multiplexed feature is called. With this each logical strange of execution would have it's "own" budget (more or less).
There are only a few things you need the specific runtime for:
- spawn
- IO
- timeout
But you can mix the executor and reactor doing the IO (not recommended but you can).
Similar you can run your own timer.
And you can abstract in various ways about all of this, sure with limitation, hence why there is no std-abstraction. But there are enough high profile libraries which do support multiple runtimes just fine.
But tokios preemting-cooperated threads to require any code which does any form of future multiplexing to:
- be tokio specific (which btw. isn't fully solving the problem)
- add a bunch of additional complexity, including memory and runtime overhead
If you multiplex features on tokio you must:
- use custom wakers to detect yields
- (and) do not poll futures in a "repeating" order (preferable fully random order).
This is a lot of additional complexity for something like a join_all (for a "small" N).
(reminds me I should check if I need to open an issue with futures-rs, as their join_all impl. was subtle broken last time I checked).
And even with that you have the problem that the multiplexed futures as subtle de-prioritized as they share a budged.
The problem I have with this feature is not that it's a bad idea, it isn't it's in general a good idea. The problem is that it completely overlooks the multiplexing case. And worse, further in subtle ways divides the ecosystem (that is what I'm worried about).
So maybe we could find a way to provide a std (or just common-library) standardized way for just that feature. (I mean it's a task local int, it might not even need to be atomic, maybe. So there might be a way which doesn't have the problem async-std standardization has).
Yield = wake the `waker_ref`. Avoiding the yield would be clone().wake().
That said, "poll immediately" isn't actually a thing nor was it ever a thing except in incorrect implementations.
And that means that just because you get "Pending" (i.e. not ready) from one of the futures, doesn't mean you should return Pending now. I only means this future is not ready, but other futures might still be ready.
But in tokio it means this future is not ready, and we magically as a side-effect might have forced all other futures to be non-ready even if they are.
Which means tokio redefined what Pending means in a subtle but potentially massively-braking way.
Which is a problem.
And not a problem of futures-rs, but one of tokio.
And forcing all of the eco-system to increase the complexity of their code by trying to subtile detect weather something yielded or was force yielded IS NOT OK. That's not how rust standarized yielding or polling.
This is not quite right.
As far as I understand they did respect yield in the way it's defined by rust.
There yields mean just that your future returns `Pending`.
Which means only _this_ future is not ready.
But in tokio returning `Pending` means "not-ready" and "maybe as a _magic side effect_ also make all other futures return pending even if it is ready internally".
So lets step away from FuturesUnordered for a moment and instead just look at a future which multiplexes X-futures and polls each (not completed one) once and then yields, which should be 100% fine.
But with tokio it isn't as after just polling the first few, the "budget" might be consumed and polling all other will forcefully fail where it shouldn't, adding a lot of overhead. Worse if you just poll them in the same order every time you will de-facto starve all futures later in the list. Which means you need to add a bunch of complexity which should be unnecessary just because tokio changed what `Poll::Pending` means.
Also if you write scheduler independent futures (you should if you can) then you can not opt-out of it. Generally in a multiplexed future you don't want to opt. out anyway, you want to have a budged per logical strange of execution, which tokio doesn't provide.
Instead tokio assumes a task (which in the end is just a future polled by it) represents a single logical strange of execution. But that is simple NOT how rust futures are designed!! (Through often it happens to play out that way.)
This doesn't mean that tokios idea is bad, it's just not compatible with rusts future design in subtle edge cases.
I think it would be a nice thing to add it to the future design, but then you would need a standardized way to properly handle multiplexing. (Which as a side not tokio doesn't provide, it only provides opt. out `unconstrained`, but what you need is to snapshot and restore the budget or something similar, i.e. snapshot it when entering the multiplexer and restoring it after each multiplexed future or similar, the only solution futures unordered could take is speculative adding yields, but that's _not_ a proper solution as it subtle de-prioritize multiplexed futures compared to spawned futures and also can easily fall apart if you nest multiplexed futures....).
Also I have no idea why they call it cooperative scheduling. Futures are cooperative scheduling. What they do is preemting futures (i.e. force full yield them), but only in places where they could have yielded in context of cooperative scheduling. So it's some in between solution.