Skip to content

Add Runtimes & Tasks #522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@
- [Async](async.md)
- [async/await](async/async-await.md)
- [Async Blocks](async/async-blocks.md)
- [Futures](async/futures.md)
- [Futures](async/futures.md)
- [Runtimes](async/runtimes.md)
- [Tasks](async/tasks.md)
- [Exercises](exercises/day-4/async.md)

# Final Words
Expand Down
3 changes: 0 additions & 3 deletions src/async/futures.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ own types. For example, the `JoinHandle` returned from `tokio::spawn` implements
The `.await` keyword, applied to a Future, causes the current async function or
block to pause until that Future is ready, and then evaluates to its output.

An important difference from other languages is that a Future is inert: it does
not do anything until it is polled.

<details>

* Run the example and look at the error message. `_: () = ..` is a common
Expand Down
29 changes: 29 additions & 0 deletions src/async/runtimes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Runtimes and Tasks

A *runtime* provides support for performing operations asynchronously (a
*reactor*) and is responsible for executing futures (an *executor*). Rust does not have a
"built-in" runtime, but several options are available:

* [Tokio](https://tokio.rs/) - performant, with a well-developed ecosystem of
functionality like [Hyper](https://hyper.rs/) for HTTP or
[Tonic](https://github.com/hyperium/tonic) for gRPC.
* [async-std](https://async.rs/) - aims to be a "std for async", and includes a
basic runtime in `async::task`.
* [smol](https://docs.rs/smol/latest/smol/) - simple and lightweight

Several larger applications have their own runtimes. For example,
[Fuchsia](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/lib/fuchsia-async/src/lib.rs)
already has one.

<details>

* Note that of the listed runtimes, only Tokio is supported in the Rust
playground. The playground also does not permit any I/O, so most interesting
async things can't run in the playground.

* Futures are "inert" in that they do not do anything (not even start an I/O
operation) unless there is an executor polling them. This differs from JS
Promises, for example, which will run to completion even if they are never
used.

</details>
60 changes: 60 additions & 0 deletions src/async/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Tasks

Runtimes have the concept of a "Task", similar to a thread but much
less resource-intensive.

A Task has a single top-level Future which the executor polls to make progress.
That future may have one or more nested futures that its `poll` method polls,
corresponding loosely to a call stack. Concurrency is possible within a task by
polling multiple child futures, such as racing a timer and an I/O operation.

```rust,editable,compile_fail
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:6142").await?;
println!("listening on port 6142");

loop {
let (mut socket, addr) = listener.accept().await?;

println!("connection from {addr:?}");

tokio::spawn(async move {
if let Err(e) = socket.write_all(b"Who are you?\n").await {
println!("socket error: {e:?}");
return;
}

let mut buf = vec![0; 1024];
let reply = match socket.read(&mut buf).await {
Ok(n) => {
let name = std::str::from_utf8(&buf[..n]).unwrap().trim();
format!("Thanks for dialing in, {name}!\n")
}
Err(e) => {
println!("socket error: {e:?}");
return;
}
};

if let Err(e) = socket.write_all(reply.as_bytes()).await {
println!("socket error: {e:?}");
}
});
}
}
```

<details>

Copy this example into your prepared `src/main.rs` and run it from there.

* Ask students to visualize what the state of the example server would be with a
few connected clients. What tasks exist? What are their Futures?

* Refactor the async block into a function, and improve the error handling using `?`.

</details>
13 changes: 13 additions & 0 deletions src/running-the-course/day-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,18 @@ Ensure that `adb sync` works with your emulator or real device and pre-build
all Android examples using `src/android/build_all.sh`. Read the script to see
the commands it runs and make sure they work when you run them by hand.

## Async

If you chose Async for Day 4 afternoon, you will need a fresh crate set up and
the dependencies downloaded and ready to go. You can then copy/paste the
examples into `src/main.rs` to experiment with them.

```shell
cargo init day4
cd day4
cargo add tokio --features full
cargo run
```

[1]: https://source.android.com/docs/setup/download/downloading
[2]: https://github.com/google/comprehensive-rust