initial commit

This commit is contained in:
2025-02-19 10:34:15 +05:30
commit b9cb4c290a
355 changed files with 18626 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
# Don't block the runtime
Let's circle back to yield points.\
Unlike threads, **Rust tasks cannot be preempted**.
`tokio` cannot, on its own, decide to pause a task and run another one in its place.
The control goes back to the executor **exclusively** when the task yields—i.e.
when `Future::poll` returns `Poll::Pending` or, in the case of `async fn`, when
you `.await` a future.
This exposes the runtime to a risk: if a task never yields, the runtime will never
be able to run another task. This is called **blocking the runtime**.
## What is blocking?
How long is too long? How much time can a task spend without yielding before it
becomes a problem?
It depends on the runtime, the application, the number of in-flight tasks, and
many other factors. But, as a general rule of thumb, try to spend less than 100
microseconds between yield points.
## Consequences
Blocking the runtime can lead to:
- **Deadlocks**: if the task that's not yielding is waiting for another task to
complete, and that task is waiting for the first one to yield, you have a deadlock.
No progress can be made, unless the runtime is able to schedule the other task on
a different thread.
- **Starvation**: other tasks might not be able to run, or might run after a long
delay, which can lead to poor performances (e.g. high tail latencies).
## Blocking is not always obvious
Some types of operations should generally be avoided in async code, like:
- Synchronous I/O. You can't predict how long it will take, and it's likely to be
longer than 100 microseconds.
- Expensive CPU-bound computations.
The latter category is not always obvious though. For example, sorting a vector with
a few elements is not a problem; that evaluation changes if the vector has billions
of entries.
## How to avoid blocking
OK, so how do you avoid blocking the runtime assuming you _must_ perform an operation
that qualifies or risks qualifying as blocking?\
You need to move the work to a different thread. You don't want to use the so-called
runtime threads, the ones used by `tokio` to run tasks.
`tokio` provides a dedicated threadpool for this purpose, called the **blocking pool**.
You can spawn a synchronous operation on the blocking pool using the
`tokio::task::spawn_blocking` function. `spawn_blocking` returns a future that resolves
to the result of the operation when it completes.
```rust
use tokio::task;
fn expensive_computation() -> u64 {
// [...]
}
async fn run() {
let handle = task::spawn_blocking(expensive_computation);
// Do other stuff in the meantime
let result = handle.await.unwrap();
}
```
The blocking pool is long-lived. `spawn_blocking` should be faster
than creating a new thread directly via `std::thread::spawn`
because the cost of thread initialization is amortized over multiple calls.
## Further reading
- Check out [Alice Ryhl's blog post](https://ryhl.io/blog/async-what-is-blocking/)
on the topic.