1. 29
  1.  

  2. 5

    There’s another liberating advantage of async timers which went unmentioned in the article: it is very cheap to sleep in your concurrent tasks. If you have 10,000 things that do something once per minute on their own schedule, go right ahead and write a naive infinite loop with a delay. Tokio’s executor manages all the timers for you and you’re not wasting per-thread resources for each concurrent sleep. Then if you decide to shut one down you can reap its memory instantly - you don’t have to wait for the thread’s next wakeup so it can see the flag that it should terminate.

    1. 1

      I presume that tokio needs to keep a heap data structure for those timers rather than being able to use just a queue?

      1. 6

        Timers themselves are stored in an intrusive linked-list:

        https://docs.rs/tokio/latest/src/tokio/time/driver/entry.rs.html#437

        The on-heap data structure only stores constant-size index of these lists:

        https://docs.rs/tokio/latest/src/tokio/time/driver/wheel/mod.rs.html#38

        1. 1

          Thanks! I’m genuinely surprised.

    2. 2

      Unfortunately you can’t use async fn everywhere you can use fn (eg: traits) so async rust isn’t actually a superset of normal rust.

      Yeah there are hacky macro tricks but they make me feel uncomfortable.

      1. 3

        Rust project very much wants async Rust to be a superset of normal Rust. In particular, async trait is actively being worked on.

        It’s just that Rust project decided releasing async fn first without async trait is better than waiting until both are complete. While there are downsides, overall I can’t disagree with that.

        1. 1

          And this was almost certainly the right choice. But in 2022 async rust still feels weirder than non async rust.

      2. 1

        One of my biggest headaches in Ruby is with Thread.raise not being threadsafe https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/. We’ve talked about some new ideas around “safer” APIs. I’m advocating for something that allows code to keep cleaning up (if that’s what it’s doing) but has some timer to prevent deadlock or infinite loops in cleanup code.

        In the process of brainstorming on this problem. Someone mentioned that killing another thread could either be 100% safe (guaranteed to have no side effects like leaking resources) or guaranteed effective (meaning even in the case of infinite loop or hanging code, it’s guaranteed to return even if it’s in an unsafe manner). I was wondering if async bypasses this “choose one” scenario somehow, and if so…how?

        Is it assuming that all resource cleanup is due to Drop trait implementation and therefore Rust handles the cleanup? Is it possible to have non-exiting/infinite code in Drop?

        1. 2

          Yes, Drop handles all the cleanup. Dropping of a Future aborts it immediately. However, it’s only possible to drop between polling, so effectively a Future can be suddenly stopped only at .await points.

          It’s possible to have an infinite loop in Drop (Rust hasn’t solved the halting problem yet), but it’d be considered a bug.

          1. 1

            Thanks. Makes sense

        2. 1

          Wait, it really won’t do anything until you try to poll it? That sounds awful, why on earth was that behaviour chosen?