3 ms·
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
by Spivak 1y ago
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?
- jvanderbot 1y agoYes Rust has ways of verifying single access to locks through the borrow checker and lifetimes.
- Spivak 1y agoWell yes but this seems more complicated. If the code was executed on a single thread it would be correct. The compiler somehow knows that await might move the work to another thread in which the code would still be correct if it weren't for the pesky undefined behavior. At all points it seems like it would be single access and yet it catches this.
- 0x1ceb00da 1y agoAn async function call returns a future which is an object holding the state of the async computation. Multithreaded runtimes like tokio require this future to be `Send`, which means it can be moved from one thread to another. The future generated by the compiler is not Send if there is a local variable crossing an await boundary that is not `Send`.
- ViewTrick1002 1y agoThat comes from the where the tokio task is spawned annotating the future with send. https://docs.rs/tokio/latest/tokio/task/fn.spawn.html https://docs.rs/tokio/latest/tokio/task/fn.spawn.html If you want to run everything on the same thread then localset enables that. See how the spawn function does not include the send bound. https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html
- veber-alex 1y agoThe compiler doesn't know anything about threads. The compiler knows the Future doesn't implement the Send trait because MutexGuard is not Send and it crosses await points. Then, tokio the aysnc runtime requires that futures that it runs are Send because it can move them to another thread. This is how Rust safety works. The internals of std, tokio and other low level libraries are unsafe but they expose interfaces that are impossible to misuse.
- vlovich123 1y agoPresumably 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.
- maplant 1y ago> using a normal std Mutex within an async environment is an antipattern and should not be done This is simply not true, and the tokio documentation says as much: "Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code." https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#wh...
- deleted 1y ago[deleted]
- vlovich123 1y agoI think that’s a dangerous recommendation and ignores the consequence of doing this within a single threaded reactor (your app will hang permanently if the lock is contended) or assumes you won’t have N threads trying to acquire this lock where N is the size of your thread pool which is entirely possible on a heavily accessed server if the core state that needs to be accessed by every request is locked with said mutex.
- synalx 1y agoMore the latter. The lock guard is not `Send`, so holding it across the await point makes the `impl Future` returned by the async function also not `Send`. Therefore it can't be passed to a scheduler which does work stealing, since that scheduler is typed to require futures to be `Send`.
- bkolobara 1y agoYes, if you use a scheduler that doesn't move work between threads, it will not require the task to be Send, and the example code would compile. Rust can use that type information and lifetimes to figure out when it's safe and when not.
- mrtracy 1y agoRust uses the traits “Send” and “Sync” to encode this information, there is a lot of special tooling in the compiler around these. A type is “Send” if it can be moved from one thread to another, it is “Sync” if it can be simultaneously accessed from multiple threads. These traits are automatically applied whenever the compiler knows it is safe to do so. In cases where automatic application is not possible, the developer can explicitly declare a type to have these traits, but doing so is unsafe (requires the ‘unsafe’ keyword and everything that entails). You can read more at rustinomicon, if you are interested: https://doc.rust-lang.org/nomicon/send-and-sync.html https://doc.rust-lang.org/nomicon/send-and-sync.html