←back to thread

517 points bkolobara | 2 comments | | HN request time: 0s | source
Show context
Spivak ◴[] No.45041771[source]
How do you encode the locking issue in the type system, it seems magical? Can you just never hold any locks when calling await, is it smart enough to know that this scheduler might move work between threads?
replies(5): >>45041806 #>>45041833 #>>45041852 #>>45041891 #>>45041898 #
vlovich123 ◴[] No.45041833[source]
Presumably the author is using tokio which requires the future constructed (e.g the async function) to be Send (either because of the rules of Rust or annotated as Send) since tokio is a work-stealing runtime and any thread might end up executing a given future (or even start executing and then during a pause move it for completion on another thread). std::sync::MutexGuard intentionally isn't annotated with Send because there are platforms that require the acquiring thread be the one to unlock the mutex.

One caveat though - using a normal std Mutex within an async environment is an antipattern and should not be done - you can cause all sorts of issues & I believe even deadlock your entire code. You should be using tokio sync primitives (e.g. tokio Mutex) which can yield to the reactor when it needs to block. Otherwise the thread that's running the future blocks forever waiting for that mutex and that reactor never does anything else which isn't how tokio is designed).

So the compiler is warning about 1 problem, but you also have to know to be careful to know not to call blocking functions in an async function.

replies(6): >>45041892 #>>45041938 #>>45041964 #>>45042014 #>>45042145 #>>45042479 #
sunshowers ◴[] No.45042145[source]
Using a Tokio mutex is even more of an antipattern :) come to my RustConf talk about async cancellation next week to find out why!
replies(1): >>45044123 #
vlovich123 ◴[] No.45044123[source]
Most people on this forum are not attending RustConf. It might be helpful to post at least the abstract of your idea.
replies(1): >>45044490 #
sunshowers ◴[] No.45044490[source]
The big thing is that futures are passive, so any future can be cancelled at any await point by dropping it or not polling it any more. So if you have a situation like this, which in my experience is a very common way to use mutexes:

  let guard = mutex.lock().await;
  // guard.data is Option<T>, Some to begin with
  let data = guard.data.take(); // guard.data is now None

  let new_data = process_data(data).await;
  guard.data = Some(new_data); // guard.data is Some again
Then you could cancel the future at the await point in between while the lock is held, and as a result guard.data will not be restored to Some.
replies(2): >>45045706 #>>45045804 #
neandrake ◴[] No.45045706{3}[source]
Same would be true for any resource that needs cleaned up, right? Referring to stop-polling-future as canceling is probably not good nomenclature. Typically canceling some work requires cleanup, if only to be graceful let alone properly releasing resources.
replies(1): >>45045790 #
1. sunshowers ◴[] No.45045790{4}[source]
Yes, this is true of any resource. But Tokio mutexes, being shared mutable state, are inherently likely to run into bugs in production.

In the Rust community, cancellation is pretty well-established nomenclature for this.

Hopefully the video of my talk will be up soon after RustConf, and I'll make a text version of it as well for people that prefer reading to watching.

replies(1): >>45045836 #
2. neandrake ◴[] No.45045836[source]
Thank you, I look forward to watching your presentation.