It's likely that Future::poll may be confused with the "known bad" poll and select syscalls on Unix. We should be sure to compare and contrast this in the documentation somewhere, likely with this information
The old way to do networking in UNIX used to be poll/select, until people noticed this wastes cycles and RAM for many connections.
Is there any reason this is a better design?
I’m afraid I don’t even know what I’m asking here, but for me, polling has a bad aftertaste, so I’d like to know if this design is fundamentally different.
Thanks for the report! It's useful to talk about this in the context of _why_ poll/select ended up not having great performance.
The fundamental API of the poll and select syscalls is that you have to tell the kernel about an entire list of file descriptors every time you call them. This means that the kernel has to iterate everything on every syscall. The select syscall then suffers the problem that when you go back to the user the user now _also_ has to check all file descriptors because it's not sure which one is ready. While poll returns what events happened in the event structures the user still has to iterate over all events and check everything.
So there's basically one thing we need to do here to be performant, which that when an event happens we should never "iterate everything" regardless of what "everything" is.
With that in mind, the intention is that futures do indeed not suffer the same pitfalls of poll and select. The Future trait itself is quite flexible to multiple models, but a particular one is envisioned in the tokio-core crate. In that crate all spawned tasks are assigned a unique token with mio (which is epoll under the hood), so when mio comes back with a token we know immediately what to wake up. That is, given millions of futures spawned, we'll only ever wake up the precisely correct ones when events happen.
This means that at the Task granularity, we're fine (leveraging epoll to its fullest). There's another question, of what if a Future is itself composed of thousands of other futures? This means that one Task is being woken up if any number of events happen, so when the future is polled it shouldn't have to check everything to see what happened (as we'd be back at select again).
For this use case, we have the task::with_unpark_event function. This allows you to, when scheduling a blocking operation, arrange for an action to be taken when that blocking action resolves. The primary use case of this is pushing a token onto a list of "ready tokens". Using this, a future which internally has thousands of futures can maintain a precise list of what's happened and iterate over precisely the ready events (avoiding those that aren't ready).
Does all that make sense?
Note that we likely need better documentation around this!
Great, thank you! Now I understand that my question was actually “Do you also have to iterate everything” and you answered with the reason why you don’t.
For me everything is clear now, but I’ll leave this open in case you want to use it as reminder to document that part.
Ok, I'll retitle, thanks!
Most helpful comment
Thanks for the report! It's useful to talk about this in the context of _why_ poll/select ended up not having great performance.
The fundamental API of the
pollandselectsyscalls is that you have to tell the kernel about an entire list of file descriptors every time you call them. This means that the kernel has to iterate everything on every syscall. Theselectsyscall then suffers the problem that when you go back to the user the user now _also_ has to check all file descriptors because it's not sure which one is ready. Whilepollreturns what events happened in the event structures the user still has to iterate over all events and check everything.So there's basically one thing we need to do here to be performant, which that when an event happens we should never "iterate everything" regardless of what "everything" is.
With that in mind, the intention is that futures do indeed not suffer the same pitfalls of
pollandselect. TheFuturetrait itself is quite flexible to multiple models, but a particular one is envisioned in thetokio-corecrate. In that crate all spawned tasks are assigned a unique token with mio (which is epoll under the hood), so when mio comes back with a token we know immediately what to wake up. That is, given millions of futures spawned, we'll only ever wake up the precisely correct ones when events happen.This means that at the
Taskgranularity, we're fine (leveraging epoll to its fullest). There's another question, of what if aFutureis itself composed of thousands of other futures? This means that oneTaskis being woken up if any number of events happen, so when the future is polled it shouldn't have to check everything to see what happened (as we'd be back atselectagain).For this use case, we have the
task::with_unpark_eventfunction. This allows you to, when scheduling a blocking operation, arrange for an action to be taken when that blocking action resolves. The primary use case of this is pushing a token onto a list of "ready tokens". Using this, a future which internally has thousands of futures can maintain a precise list of what's happened and iterate over precisely the ready events (avoiding those that aren't ready).Does all that make sense?
Note that we likely need better documentation around this!