Live data from Hacker News

Why you might want async in your project

notgull.net

71–80 of 182 posts

Re: Why you might want async in your project

#71

Earlier quoted context omitted.

I don’t think this is true. Context managers call special magic “dunder” methods on the instance (I don’t remember the specific ones), and I’m pretty sure those don’t get called during regular garbage collection of those instances. It’s been a few years since I was regularly writing python, so I might be wrong, but I don’t believe that context manager friendly instances are the same as Rust’s Drop trait, and I don’t…

Python is a fun case of "all of the above" (or rather, a layering of styles once it turns out a previous one isn't workable). Originally, they used pure reference counting GC, with finalizers used to clean up when freed. This was "fine", since RC is deterministic. Everything is freed when the last reference is deleted, nice and simple. But reference counting can't detect reference cycles, so eventually they added a s…

Wrote Python professionally for years and didn’t know all of this. Thanks!

Re: Why you might want async in your project

#72

The author starts by citing greenspun's tenth rule and goes on to elaborate on the argument that if you are going to have a half implementation of async anyway, why not just pull it in? Yet fails to interrogate the relationship between this argument and the cited "rule". If you should use async because you might need it in the future, shouldn't we all be writing in lisp? If we presuppose that all software eventually…

The author said what you wrote in the first sentence, ie "use async if you are going to have a half implementation of async anyway". "Use async because you might need it in the future" is something you made up, not what the author said.

Re: Why you might want async in your project

#73
post #32

do any of the async libraries for rust have good visualization tools for inspecting the implicit state machine that is constructed via this type of concurrency primitive?

The state machine transformation is not specific to any async libraries. The compiler is the one that desugars async fns / blocks to state machines. AFAIK there is nothing other than dumping the HIR / MIR from rustc to inspect it. But even without that the transformation is pretty straightforward to do mentally.

The first transformation is that every async block / fn compiles to a generator where `future.await` is essentially replaced by `loop { match future.poll() { Ready(value) => break value, Pending => yield } }`. ie either polling the inner future will resolve immediately, or it will return Pending and yield the generator, and the next time the generator is resumed it will go back to the start of the loop to poll the future again.

The second transformation is that every generator compiles to essentially an enum. Every variant of the enum represents one region of code between two `yield`s, and the data of that variant is all the local variables that in the scope of that region.

Putting both together:

    async fn foo(i: i32, j: i32) {
        sleep(5).await;
        i + j
    }
... essentially compiles to:

    fn foo(i: i32, j: i32) -> FooFuture {
        FooFuture::Step0 { i, j }
    }

    enum FooFuture {
        Step0 { i: i32, j: i32 }
        Step1 { i: i32, j: i32, sleep: SleepFuture }
        Step2,
    }

    impl Future for FooFuture {
        fn poll(self) -> Poll {
            loop {
                match self {
                    Self::Step0 { i, j } => {
                        let sleep = sleep(5);
                        self = Self::Step1 { i, j, sleep };
                    }

                    Self::Step1 { i, j, sleep } => {
                        let () = match sleep.poll() {
                            Poll::Ready(()) => (),
                            Poll::Pending => return Poll::Pending,
                        };
                        self = Self::Step2;
                        return Poll::Ready(i + j);
                    }

                    Self::Step2 => panic!("already run to completion"),
                }
            }
        }
    }

Re: Why you might want async in your project

#74

Earlier quoted context omitted.

I don’t think this is true. Context managers call special magic “dunder” methods on the instance (I don’t remember the specific ones), and I’m pretty sure those don’t get called during regular garbage collection of those instances. It’s been a few years since I was regularly writing python, so I might be wrong, but I don’t believe that context manager friendly instances are the same as Rust’s Drop trait, and I don’t…

Python is a fun case of "all of the above" (or rather, a layering of styles once it turns out a previous one isn't workable). Originally, they used pure reference counting GC, with finalizers used to clean up when freed. This was "fine", since RC is deterministic. Everything is freed when the last reference is deleted, nice and simple. But reference counting can't detect reference cycles, so eventually they added a s…

Are you saying that a finalizer is guaranteed to run when the last reference is deleted? So you could actually rely on them to handle the resources, as long as you are careful not to use reference cycles?

Re: Why you might want async in your project

#75
post #67

Earlier quoted context omitted.

The problem with garbage collection is that it doesn't work for other kinds of resources than memory, so basically every garbage collected runtime ends up with an awkward and kinda-broken version of RAII anyway (Closeable, defer, using/try-with-resources, context managers, etc). Static lifetimes are also a large part of the rest of Rust's safety features (like statically enforced thread-safety). A usable Rust-without…

You make an interesting point. Has any language introduced a generic-resource-collector? You're not supposed to use deconstructors to clean up resources because you're left to the whims of the GC which is only concerned about memory. Has anyone build a collector that tracks multiple types of resources an object might consume? It seems possible.

Erlang is probably the closest. The word you want to search for is "port". If it doesn't seem like it at first, keep reading. It's a very idiosyncratic take on the topic of you view it from this perspective because it isn't exactly their focus. But it does have a mechanism for collecting files, sockets, open pipes to other programs, and a number of other things. Not fully generic, though.

Re: Why you might want async in your project

#77
>Except, this isn’t a problem with Rust’s async, it’s a problem with tokio. tokio uses a 'static, threaded runtime that has its benefits but requires its futures to be Send and 'static.

It's not a problem with tokio either. The author's point is specifically about the multi-threaded tokio runtime that allows tasks to be moved between worker threads, which is why it requires the tasks to be Send + 'static. Alternatively you can either a) create a single-threaded tokio runtime instead which will remove the need for tasks to be Send, or b) use a LocalSet within the current worker that will scope all tasks to that LocalSet's lifetime so they will not need to be Send or 'static.

If you go the single-threaded tokio runtime route, that doesn't mean you're limited to one worker total. You can create your own pseudo-multi-threaded tokio runtime by creating multiple OS threads and running one single-threaded tokio runtime on each. This will be similar to the real multi-threaded tokio runtime except it doesn't support moving tasks between workers, which means it won't require the tasks to be Send. This is also what the author's smol example does. But note that allowing tasks to migrate between workers prevents hotspots, so there are pros and cons to both approaches.

Re: Why you might want async in your project

#78
> tokio uses a 'static, threaded runtime that has its benefits but requires its futures to be Send and 'static.

This is only partly true -- if you want to `spawn` a task on another thread then yes it has to be Send and 'static. But if you use `spawn_local`, it spawns on the same thread, and it doesn't have to be Send (still has to be 'static).

Re: Why you might want async in your project

#80
post #56

Earlier quoted context omitted.

FastAPI docs, case when you don't create an async route > When you declare a path operation function with normal def instead of async def, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server). https://fastapi.tiangolo.com/async/#path-operation-functions OP either meant this, or its variation, such as async_to_sync and sync_to_async. https://github.c…

NB: In Python >= 3.9 the idiomatic way to do this is to_thread(), not familiar with these ASGI functions but I would guess they're a polyfill and/or predate 3.9. https://docs.python.org/3/library/asyncio-task.html#asyncio....

They are not polyfills. Multiple scheduling modes are provided for libraries that are not thread safe (it's a total mess and I avoid these wrappers like the plague)
Post reply on HN