Live data from Hacker News

Tokio 1.0 – async runtime for Rust

tokio.rs

271–280 of 422 posts

Re: Tokio 1.0 – async runtime for Rust

#271
post #146
post #77

I don't really get these modern async APIs. In languages like Javascript I thought they only made sense because JS interpreters are (historically) single-threaded, so you really have no choice but async to express some concepts. Fine. But in Rust you can just spawn threads, share data through channels or mutexes, use OS-provided async IO primitives to poll file descriptors and do event-driven programming etc... I tri…

I had the same thing initially. The upside of async over a simple event loop is, in my experience, when things become less simple, and you end up with hard-to-read little state machines all over the place. With async, you can have your event loop, but the state machines are handled by the compiler. Code is like threaded code. That can be very convenient. Threads, obviously, accomplish the same thing, and arguably mor…

> Cross-thread communication is expensive. Single-threaded async task interaction is very cheap, comparatively

This all depends on how threads are implemented. If they're scheduled preemptively then communication can be expensive, relatively speaking, because of the need for locking and atomic operations. But you can also schedule cooperatively in user space, just as Tokio does when serially resuming async tasks; or as Java's Project Loom does for its new "lightweight" threads.

Note that unlike JavaScript, Tokio and Project Loom can also run different tasks on different, preemptively scheduled threads. And while I don't know that much Rust, I imagine you're going to need to use either unsafe or Rc or maybe even Arc if you intend to share data between different Tokio tasks--i.e. data that doesn't fit the normal caller/callee borrow semantics.

The other part of the problem is space requirements. Usually where you have preemptively scheduled threads the stack space for a thread is allocated lazily as a function is called and faults in pages via the OS' virtual memory system, much like single-thread, single-stack processes in a preemptive process OS. This means the minimum space allocation for a thread is at least 2x the page size (e.g. 4096 * 2). But many times a thread of execution only goes a couple of function calls deep, with minimal amounts of function-local (i.e. stack-allocated) data. If you have 1 thread per network connection, with hundreds of thousands or millions of connections that overhead could be significant.

But this, too, is a function of the implementation. Goroutines in Go use normal heap memory for stacks, and the compiler emits code to grow and move threads automatically. Rust proponents will tell you that async functions don't require any runtime cost because the stack requirements can be calculated statically. But to calculate this statically you can't support recursive functions. And if you can statically calculate your space requirements for the hidden async state object, you could also statically calculate the stack size for a thread just the same.

So really what it all comes down to isn't whether "async" is better or worse than "threads" along any of these dimensions. Abstractly, all threading implementations are async, and all async implementations effectively implement threads (i.e. a data structure that encapsulates a program counter, local automatic storage, etc). The real reason you choose one over the other is external factors. For Rust that dominate factor is interoperability with native C ABIs, particularly native stack disciplines. Because Rust can't implement much magic in the lower layers of the runtime environment while maintaining the degree of interoperability with C, C++, and other language libraries (via the C ABI) that they're committed to, they have no choice but to put most of the instrumentation into the language itself. And this necessitates the async contortions, independent of any other preferences. Contrast that with Go, where calling into C is slightly more costly because they preferred to push more of the async/thread abstraction beneath the language syntax.

But perhaps what this tells us is that we should think about revisiting native stack disciplines and thread scheduling semantics. IIRC, Linux will soon get scheduler activations (i.e. ability for userland to efficiently switch execution to another specified kernel-visible thread). That's a small step in the right direction, and if it catches on more operating systems will adopt this--after having ditched them 20 years ago, ironically, before async network I/O became popular and when 1:1 thread scheduling became the preferred kernel model).

Re: Tokio 1.0 – async runtime for Rust

#272
It irks me that the "async runtime" isn't simply part of the rust runtime. Making it be a separate library simply increases the likelihood of having to deal with libraries expecting different version, or even different async runtimes entirely.

Re: Tokio 1.0 – async runtime for Rust

#273

Always found these APIs a little hard to work with. For instance, if I tried to use `actix-web`, then using `reqwest` and `tokio` felt like pulling teeth. If anyone's got minimal code lining up a web framework (any one, not stuck to actix) with some reqwest, I'd be thankful to look over it. Just some trivial stuff so I can add an API gateway that proxies a specific API.

The examples in the package docs have always been great starting points for me.

Also, I love the fact that Rust will complain if the examples in your comments don't compile. Such a great feature. As a result, copy-and-pasting examples out of rustdoc pages (nearly) always gives you a working starting point to hack from.

Re: Tokio 1.0 – async runtime for Rust

#274

It is unfortunate, that libraries have to be coded against specific runtime and not generically. There is tokio and there is smol (likely discontinued, since author left rust), maybe other runtimes will emerge, but whole ecosystem is already tied to tokio.

That's not entirely true. If you want to write an entire standalone application that compiles into a binary and starts up a scheduler, then yes, you have to pick a scheduler runtime.

If you're writing a library to be used by others you can very often expose only types which come from std::futures. The result will work with all of the runtimes.

Re: Tokio 1.0 – async runtime for Rust

#275

It is unfortunate, that libraries have to be coded against specific runtime and not generically. There is tokio and there is smol (likely discontinued, since author left rust), maybe other runtimes will emerge, but whole ecosystem is already tied to tokio.

async-std [0] is pretty widely used as well. [0]: https://github.com/async-rs/async-std Most libraries can be used with different runtimes. Hyper for example, which uses Tokio by default, can be configured to use an async-std executor.

It bugs me that that library will spin up a scheduler without being asked to do so.

As I understand it, that difference (vs Tokio) was the main driver behind the projects splitting.

I also think it's a bit presumptuous for them to name themselves "std". It'll be even more ridiculous in the likely event that Tokio becomes the std:: asynchronous I/O library. It's asking for confusion.

Re: Tokio 1.0 – async runtime for Rust

#276

Earlier quoted context omitted.

Can't find async-std feature here: https://github.com/hyperium/hyper/blob/master/Cargo.toml do you have an example? Either way, "can be configured" means that custom code for each runtime must be written, it is not like lets say "Futures", which can be used generically.

Here is an example: https://github.com/async-rs/async-std-hyper/blob/master/READ... You do have to write a ~50 loc compat layer. However, most of the compat layer is due to the fact that tokio's `AsyncRead` and `AsyncWrite` are different from the standard futures crate, which may change in the future [0]. After that, you just have to implement `hyper::Executor` for async-std's `spawn`, and `hyper::Accept` for async-s…

> tokio's `AsyncRead` and `AsyncWrite` are different from the standard futures

The standard futures library does not have an AsyncRead or AsyncWrite:

https://doc.rust-lang.org/std/future/index.html

Re: Tokio 1.0 – async runtime for Rust

#277

Earlier quoted context omitted.

Rust is intended as a systems programming language, it's for people who are writing "the next nginx". It turns out that there are also a bunch of people who want to write webapp servers in Rust, too, but that's never really been the goal .

Eh, so Rust is not for me? I seem to have heard Rust being inclusive and empowering everyone blah blah. I must have misheard.

It's clear this is one of your hobby horses. Every comment thread here is encumbered with you pointing out that you wouldn't like it if async were the default, fair enough. In fact, you don't seem to like the idea in general.

ctrl-f "sanxiyn" yields 28 instances, most of them restating in every subtree the same point about how you think threads > async.

Since I think most of us tend to read the comments section top to bottom, it seems ideal to limit your opinion to a couple comments and then put your effort into making those comments a good rundown of your position. It would certainly be more interesting to read and consider.

Re: Tokio 1.0 – async runtime for Rust

#278
post #272

It irks me that the "async runtime" isn't simply part of the rust runtime. Making it be a separate library simply increases the likelihood of having to deal with libraries expecting different version, or even different async runtimes entirely.

Rust doesn't have a runtime.

That's part of its awesomeness. That's why it can target microcontrollers.

You're probably used to languages with garbage collectors. Having garbage collection forces you to have a "runtime" since that's where the GC code goes. Then more and more stuff accretes onto this unavoidable runtime, and before you know it you're writing Java code...

Re: Tokio 1.0 – async runtime for Rust

#279
post #146

Earlier quoted context omitted.

I had the same thing initially. The upside of async over a simple event loop is, in my experience, when things become less simple, and you end up with hard-to-read little state machines all over the place. With async, you can have your event loop, but the state machines are handled by the compiler. Code is like threaded code. That can be very convenient. Threads, obviously, accomplish the same thing, and arguably mor…

> A non-async function is "regular logic", it must complete without blocking. What does 'blocking' mean? I would expect the definition of synchronous to be the exact opposite; i.e., a synchronous function must block the caller until the function has finished executing. For that matter, what is "regular logic"? The name implies there is some sort of "irregular logic" to contrast it with. I get the feeling that the wri…

My reading of that was 'you must write your non-async "regular logic" functions so that they cannot block.'

Re: Tokio 1.0 – async runtime for Rust

#280
post #141
post #76

Can someone tell me why tokio is so damn big and full of transitive dependencies :D?

How big is it?

Here are the compiled-in dependencies:

    $ cargo tree -e no-dev,no-build --no-dedupe -p tokio

    tokio v1.0.0
    ├── bytes v1.0.0
    ├── libc v0.2.81
    ├── memchr v2.3.4
    ├── mio v0.7.6
    │   ├── libc v0.2.81
    │   └── log v0.4.11
    │       └── cfg-if v0.1.10
    ├── num_cpus v1.13.0
    │   └── libc v0.2.81
    ├── once_cell v1.5.2
    ├── parking_lot v0.11.1
    │   ├── instant v0.1.9
    │   │   └── cfg-if v1.0.0
    │   ├── lock_api v0.4.2
    │   │   └── scopeguard v1.1.0
    │   └── parking_lot_core v0.8.2
    │       ├── cfg-if v1.0.0
    │       ├── instant v0.1.9 (*)
    │       ├── libc v0.2.81
    │       └── smallvec v1.4.2
    ├── pin-project-lite v0.2.0
    └── signal-hook-registry v1.3.0
        └── libc v0.2.81
There are a lot more dependencies that are used only for the build scripts (build-dependencies) or for running the tests, examples, and benchmarks (dev-dependencies). None of those dependencies cause any additional code to wind up in your binaries when your project depends on tokio.

PS, I think there's a bug in "cargo tree"... the command above actually prints out only one line ("pin-project"). I had to remove the "-p tokio" and then copy-and-paste out the relevant section.

Post reply on HN