wrt. preemption - in Seastar we have maybe_yield(), which gives up the cpu, but only if the task quota (more or less a semantic equivalent for Tokio's budget) has passed. Wouldn't it make sense to have a similar capability in Tokio? Then, if somebody is not a big fan of the default preemption, they could run their tasks under tokio::task::unconstrained and only check the budget in very specific, explicitly chosen places - where they call maybe_yield(). That could of course also be open-coded by trying to implement maybe_yield on top of yield_now and some time measurements, but since the whole budgeting code is already there... Do you think it's feasible?
The problem is it requires you to write your code in a way which now _only_ will work with tokio.
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).
Any extension of executors will require having a trait abstracting the executor used, and there just isn’t one in std yet. Your code already has to be tokio specific if you do something as mundane as spawn a task.
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).
maybe_yield may or may not be the right solution here, but I think it may be useful in general - e.g. when you have long I/O-less computations. In such a case, I'd like to be able to say "yield here if my budget is drained, but continue otherwise and don't put my task at the end of the queue". Although for that the only thing I really need is a way to peek at your budget - with that, open-coding maybe_yield is trivial
Now that I think of it, it would probably be beneficial even outside of the unconstrained scope, especially for long computations. When iterating over millions of elements, it would be great to have a mechanism for maybe yielding if we're past the budget, but we don't really want to force-yield on every X iterations and put the task at the back of the queue. If the maybe_yield API is potentially controversial, a sufficient building block would be a function that allows peeking into the state of your budget - and then, if you're out of it, you just explicitly call yield_now().