Live data from Hacker News

Futurelock: A subtle risk in async Rust

rfd.shared.oxide.computer

211–220 of 251 posts

Re: Futurelock: A subtle risk in async Rust

#211
post #166

Earlier quoted context omitted.

I love Rust. But I’m 100% convinced Rust chose the wrong tradeoffs with their async model. Just give me green threads and use malloc to grow the stack. It’s fine. That would have been better imho.

You can't have a low-level language and green threads at the same time.

Why not?

Re: Futurelock: A subtle risk in async Rust

#212
post #166

Earlier quoted context omitted.

I love Rust. But I’m 100% convinced Rust chose the wrong tradeoffs with their async model. Just give me green threads and use malloc to grow the stack. It’s fine. That would have been better imho.

You can't have a low-level language and green threads at the same time.

Why not?

Re: Futurelock: A subtle risk in async Rust

#213

Earlier quoted context omitted.

> if tokio::select!() only accepted tasks (or implicitly turned any futures it receives into tasks, like Python's asyncio.gather() does) then it wouldn't have this problem? Yes, this is correct. However, many of the use cases for select rely on the fact that it doesn't run all the tasks to completion. I've written many a select! statement to implements timeouts or other forms of intentionally preempting a task. Somet…

> However, many of the use cases for select rely on the fact that it doesn't run all the tasks to completion. That seems like a different requirement than "all arguments are tasks". If I understand it right (and quite possibly I don't), making them all tasks means that they are all polled and therefore continue progressing until they are dropped. It doesn't mean that select! would have to run them all the way to comp…

Doing select on tasks doesn’t really make sense semantically in the first place. Tasks are already getting polled by the executor. The purpose of select is to run some set of futures the executor doesn’t know about, until the first one of them completes. If you wanted to wait for one of a set of tasks to do something, you don’t need any additional polling, you’d just use something like a signal or a channel to communicate with them.

Re: Futurelock: A subtle risk in async Rust

#214

Earlier quoted context omitted.

> This seems like a contradiction to me. How can future1 acquire the Mutex in the first place, if it cannot run? The word "given" is really odd to me. `future1` did run for a bit, and it got far enough to acquire the mutex. (As the article mentioned, technically it took a position in a queue that means it will get the mutex, but that's morally the same thing here.) Then it was "paused". I put "paused" in scare quotes…

So my understanding was correct, it requires the programmer to deal with scheduling explicitly in userspace. If I'm writing bare metal code for e.g. a little cortex M0, I can very much see the utility of this abstraction. But it seems like an absolutely absurd exercise for code running in userspace on a "real" OS like Linux. There should be some simpler intermediate abstraction... this seems like a case of forcing a…

To be clear, if you restrict yourself to `async`/`.await` syntax, you never see any of this. To await something means to poll it to completion, which is usually what you want. "Joining" two futures lets you poll both of them concurrently until they're both done, which is kind of the point of async as a concept, and this also doesn't really require you to think about scheduling. One place where things get hairy (like in this article) is "selecting" on futures, which polls them all until one of them is done, and then stops polling the rest. (Normally I'd loosely say it "drops the rest on the floor", but the deadlock in this article actually hinges on exactly what gets "dropped" when, in the Rust sense of the `Drop` trait.) This is where scheduling as you put it, or "cancellation" as Rust folks often put it, starts to become important. And that's why the article concludes "In the end, you should always be extremely careful with tokio::select!" However, `select!` is not the only construct that raises these issues. Speaking of which...

> But it seems like an absolutely absurd exercise for code running in userspace on a "real" OS like Linux

Clearly you have a point here, which is why these blog posts are making an impact. That said, one counterpoint is, have you ever wished you could kill a thread? The reason there are so many old Raymond Chen "How many times does it have to be said: Never call TerminateThread" blog posts, is that lots of real world applications really desperately want to call TerminateThread, and it's hard to persuade them to stop! The ability to e.g. put a timeout on any async function call is basically this same superpower, without corrupting your whole process (yay), but still with the unavoidable(?) difficulty of thinking about what happens when random functions give up halfway through.

Re: Futurelock: A subtle risk in async Rust

#215
post #195

Earlier quoted context omitted.

Your confusion is very natural: > It's more typical in my experience that the act of granting the lock to a thread is what makes it runnable, and it runs right then. This gets at why this felt like a big deal when we ran into this. This is how it would work with threads. Tasks and futures hook into our intuitive understanding of how things work with threads. (And for tasks, that's probably still a fair mental model,…

> tasks must poll them for them to keep running. So async Rust introduces an entire novel class of subtle concurrent programming errors? Ugh, that's awful. > The analogous thing with threads would seem to be something like if the kernel forgot to enqueue some runnable thread on a run queue. Yes. But I've never written code in a preemptible protected mode environment like Linux userspace where it is possible to make t…

I definitely hear that!

To keep it in perspective, though: we've been operating a pretty good size system that's heavy on async Rust for a few years now and this is the first we've seen this problem. Hitting it requires a bunch of things (programming patterns and runtime behavior) to come together. It's really unfortunate that there aren't guard rails here, but it's not like people are hitting this all over the place.

The thing is that the alternatives all have tradeoffs, too. With threaded systems, there's no distinction in code between stuff that's quick vs. stuff that can block, and that makes it easy to accidentally do time-consuming (blocking) work in contexts that don't expect it (e.g., a lock held). With channels / message passing / actors, having the receiver/actor go off and do something expensive is just as bad as doing something expensive with a lock held. There are environments that take this to the extreme where you can't even really block or do expensive things as an actor, but there the hidden problem is often queueing and backpressure (or lack thereof). There's just no free lunch.

I'd certainly think carefully in choosing between sync vs. async Rust. But we've had a lot fewer issues with both of these than I've had in my past experience working on threaded systems in C and Java and event-oriented systems in C and Node.js.

Re: Futurelock: A subtle risk in async Rust

#216

LOL. All the Rust evangelists talk about safety when stuff like this exists? JFC. Can we stop calling Rust safe now? Finally? I mean we all know deep in our hearts that trivial memory safety doesn't mean programs are correct or "safe" by any means but its nice to have proof that Rust is fundamentally unsafe for asynchronous tasks at least. Or at least "unsound". Structured concurrency will always win IMO.

> All the Rust evangelists talk about safety when stuff like this exists? JFC. Deadlocks can happen anywhere? You can replicate this pattern in golang.

While I don't like the tone of the grandparent, comparing to Go is kinda irrelevant when it used structured concurrency as the example of how to solve it. It is of course also not a panacea..

Re: Futurelock: A subtle risk in async Rust

#217
post #195

Earlier quoted context omitted.

Your confusion is very natural: > It's more typical in my experience that the act of granting the lock to a thread is what makes it runnable, and it runs right then. This gets at why this felt like a big deal when we ran into this. This is how it would work with threads. Tasks and futures hook into our intuitive understanding of how things work with threads. (And for tasks, that's probably still a fair mental model,…

> tasks must poll them for them to keep running. So async Rust introduces an entire novel class of subtle concurrent programming errors? Ugh, that's awful. > The analogous thing with threads would seem to be something like if the kernel forgot to enqueue some runnable thread on a run queue. Yes. But I've never written code in a preemptible protected mode environment like Linux userspace where it is possible to make t…

Rust can't assume you're running on a real OS though.

Re: Futurelock: A subtle risk in async Rust

#218

Earlier quoted context omitted.

In case anyone else was confused: the link/quote in this comment are from the previous "async cancellation issue" write-up, which describes a situation where you "drop" a future: the code in the async function stops running, and all the destructors on its local variables are executed. The new write-up from OP is that you can "forget" a future (or just hold onto it longer than you meant to), in which case the code in…

async rust continues to strike me as half-baked and too complex, if you’re developing an application (as opposed to some high performance utility like e.g. a data plane component) just use threads, they’re plenty cheap and not even half as messy.

I work on an application that has various components split between sync and async rust. For certain tasks, async actually makes things a lot simpler.

Re: Futurelock: A subtle risk in async Rust

#219
post #165

Earlier quoted context omitted.

In case anyone else was confused: the link/quote in this comment are from the previous "async cancellation issue" write-up, which describes a situation where you "drop" a future: the code in the async function stops running, and all the destructors on its local variables are executed. The new write-up from OP is that you can "forget" a future (or just hold onto it longer than you meant to), in which case the code in…

Yeah, Rust mostly just eliminates memory safety and data race problems, which is an enormous improvement compared to what we had previously. Unfortunately right now if you really want to write software that's guaranteed to be correct, there's not alternative to formal verification.

Only if the data structures aren't exposed to outside of the program, in which case, Rust cannot guarantee safety from data race problems caused by OS IPC mechanisms like memory mapped data, shared memory segments or DMA buffers, accessed by external events.

Re: Futurelock: A subtle risk in async Rust

#220

Earlier quoted context omitted.

In case anyone else was confused: the link/quote in this comment are from the previous "async cancellation issue" write-up, which describes a situation where you "drop" a future: the code in the async function stops running, and all the destructors on its local variables are executed. The new write-up from OP is that you can "forget" a future (or just hold onto it longer than you meant to), in which case the code in…

async rust continues to strike me as half-baked and too complex, if you’re developing an application (as opposed to some high performance utility like e.g. a data plane component) just use threads, they’re plenty cheap and not even half as messy.

The main issue was shipping it without proper runtime support, and even nowadays async/await is synonym with Tokio.

Look at .NET, it took almost a decade to sort out async/await across all platform and language layers, and even today there are a few gotchas.

https://github.com/gerardo-lijs/Asynchronous-Programming

Rust still has a similar path to trail, with async traits, better Pin ergonomics, async lambdas, async loops,..... (yes I know some of them have been dealt with).

Post reply on HN