7 ms·
Not being far enough into rust to know: is there a reason they can't settle on shared APIs? For single-threaded vs multi-threaded obviously you'll need to spli
by Groxx 3y ago
Not being far enough into rust to know: is there a reason they can't settle on shared APIs?
For single-threaded vs multi-threaded obviously you'll need to split the APIs, but why can't all 1/N-threaded runtimes work with all 1/N-threaded coroutines (and perhaps another split for no_std)? Ignoring historical differences of course - backwards compatibility means early ones probably can't ever work together. But the ecosystem isn't forever bound to those early implementations.
- thinkharderdev 3y agoI'm not sure there is any reason in principle but it doesn't work now because the APIs for various things are not really standardized. So for instance timers. If anywhere in my code I do `tokio::time::sleep(Duration::from_millis(100)).await` (which is quite common thing to do) then my code will no longer work with a non-tokio runtime.
- charcircuit 3y agoWhy wouldn't that work? The internal sleeping and calling the waker should not depend on the runtime.
- thinkharderdev 3y agoIt does. Under the hood it may just be creating a kernel timer but something needs to actually wake the task back up when the timer elapses, which is what the runtime does. The solution would probably just to create standard interfaces in std for this so you could just do `std::async::sleep(Duration::from_millis(100)).await` and just delegate the implementation details to the runtime (or something like that).
- Groxx 3y ago"You can wake up the sleeping loop" clearly depends on the runtime in that you have to contact the runtime to wake it up, but beyond that I don't really see it. Like, if I start a thread that calls `sleep(100); timer.resolve(); runtimeInstance.wake()` how is that related to the implementation details?
- thinkharderdev 3y agoSure, you could implement your own sleep functionality that way and it would be independent of the async runtime but the async runtimes already provide that out of the box in a way that doesn't require spawning a new thread so that is typically what gets used.
- Groxx 3y agoYeah, I get the ergonomic benefits. Same imports you already have, fewer arguments, etc - there are a lot of reasons why people would prefer the specific versions. It's more that a shared API keeps getting presented as an impossibility without stdlib support, and I don't see why that would be true. Stdlib isn't special like that, nor should it be, and nothing seems to be asking for compiler magic (necessary for await in the first place, but not really beyond that). If anything, the failure of the ecosystem to settle on a shared API seems to imply there should not be a stdlib version - let the competition continue, don't choose any until it's clearly the best choice forever.
- jcranmer 3y agoThere's roughly three pieces to async/await. The first piece is the compiler support for the keywords, to do the state machine transformation for you. This can only be done by the compiler, so it's agnostic of the runtime. This support also requires some ancillary standard library support for what an async task is, to know what the result of the state machine transformation looks like. In pseudocode, this is: do_async_task(wake) -> Poll<result>: if !ready_to_read: schedule a call to wake when ready to read return Poll::not_ready return compute(read()) The second piece of the puzzle is the top-level code to drive the polling. This is what's commonly thought of as the runtime; you generally have a top-level event loop, and asking to wake means injecting a new event in the event loop that will call the async task again. The third piece of the puzzle is the "schedule a call" part, essentially this is the code that works with low-level system calls like epoll or io_uring or IOCP or boring old select. Except, as you immediately see if you have experience with such system calls, writing that code requires that the top-level event loop essentially be consistently triggering the call to check for new work. So this piece of puzzle, especially for I/O, generally needs to be intimately connected to the top-level executor to work well. What could the standard library do (or have done) to make things work better? The obvious thing is standardize basic async concepts like AsyncRead or async variant of iterators, except the difficulty with that for I/O in particular is that the library would be standardizing interfaces without providing implementations. Less obvious is baking in a standard I/O event loop interface--a standard way that would allow adding new events to the runtime's main epoll or whatever interface. However, it sort of turns out that many OSes don't actually provide nice interfaces for "wait for I/O or timer or child process status change or GUI event or ..." which is what you really want to have.
- Groxx 3y agoCouldn't that "schedule a call to wake when ready to read" be "it's just another future that does whatever it needs"? Whether that's "have the shared epoll-poller check every millisecond via a timer and resolve the relevant futures that it is observing" or "wait on a blocked thread" doesn't seem like it matters. It needs checking either constantly (hot loop), after a period, or it'll be externally resolved and that resolver will let the event loop know something's ready, and... those seem rather straightforward to label and support. I can definitely see why an enforced-shared tightly-integrated epoll-er has implicit performance benefits, but that hardly seems necessary either. And stuff chasing the last bits of performance basically always give up some interoperability.