Merge branch 'master' into book-typos-and-small-edits

pull/62/head
Florian Gilcher 5 years ago committed by GitHub
commit 1fe8a28399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -99,7 +99,7 @@ Every call to `poll()` can result in one of these two cases:
2. The computation has not finished executing, it will return [`Poll::Pending`](https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending)
This allows us to externally check if a `Future` still has unfinished work, or is finally done and can give us the value. The most simple (but not efficient) way would be to just constantly poll futures in a loop. There are optimisations possible, and this is what a good runtime does for you.
Note that calling `poll` after case 1 happened may result in confusing behaviour. See the [futures-docs](https://doc.rust-lang.org/std/future/trait.Future.html) for details.
Note that calling `poll` again after case 1 happened may result in confusing behaviour. See the [futures-docs](https://doc.rust-lang.org/std/future/trait.Future.html) for details.
## Async
@ -118,7 +118,7 @@ async fn read_file(path: &str) -> Result<String, io::Error> {
Amazingly little difference, right? All we did is label the function `async` and insert 2 special commands: `.await`.
This `async` function sets up a deferred computation. When this function is called, instead of returning the computed `String`, it will produce a `Future<Output=String>`. (Or, more precisely, will generate a type for you that implements `Future<Output=String>`.)
This `async` function sets up a deferred computation. When this function is called, it will produce a `Future<Output=Result<String, io::Error>>` instead of immediately returning a `Result<String, io::Error>`. (Or, more precisely, generate a type for you that implements `Future<Output=Result<String, io::Error>>`.)
## What does `.await` do?
@ -137,3 +137,5 @@ A `Future` is any data type that does not represent a value, but the ability to
Next, we will introduce you to `tasks`, which we need to actually *run* Futures.
[^1]: Two parties reading while it is guaranteed that no one is writing is always safe.
[futures]: https://rust-lang.github.io/async-book/02_execution/02_future.html

@ -17,7 +17,7 @@ async fn read_file(path: &str) -> Result<String, io::Error> {
fn main() {
let reader_task = task::spawn(async {
let result = read_file("data.csv");
let result = read_file("data.csv").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)
@ -33,7 +33,7 @@ This asks the runtime baked into `async_std` to execute the code that reads a fi
```rust
async {
let result = read_file("data.csv");
let result = read_file("data.csv").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)

@ -2,6 +2,6 @@
### blocking
"blocked" generally refers to conditions that keep a task from doing its work. For example, it might need data to be sent by a client before continuing. When tasks becomes blocked, usually, other tasks are scheduled.
"blocked" generally refers to conditions that keep a task from doing its work. For example, it might need data to be sent by a client before continuing. When tasks become blocked, usually, other tasks are scheduled.
Sometimes you hear that you should never call "blocking functions" in an async context. What this refers to is functions that block the current thread and do not yield control back. This keeps the executor from using this thread to schedule another task.
Sometimes you hear that you should never call "blocking functions" in an async context. What this refers to is functions that block the current thread and do not yield control back. This keeps the executor from using this thread to schedule another task.

@ -2,7 +2,7 @@
![async-std logo](./images/horizontal_color.svg)
This book serves as high-level documentation for `async-std` and a way of learning async programming in Rust through it. As such, it focusses on the `async-std` API and the task model it gives you.
This book serves as high-level documentation for `async-std` and a way of learning async programming in Rust through it. As such, it focuses on the `async-std` API and the task model it gives you.
Please note that the Rust project provides its own book on asynchronous programming, called ["Asynchronous Programming in Rust"][async-book], which we highly recommend reading along with this book, as it provides a different, wider view on the topic.

@ -1,8 +1,8 @@
# Welcome to `async-std`
`async-std` along with its [supporting libraries][organization] is a library making your life in async programming easier. It provides provide fundamental implementations for downstream libraries and applications alike. The name reflects the approach of this library: it is a closely modeled to the Rust main standard library as possible, replacing all components by async counterparts.
`async-std`, along with its [supporting libraries][organization], is a library making your life in async programming easier. It provides fundamental implementations for downstream libraries and applications alike. The name reflects the approach of this library: it is as closely modeled to the Rust main standard library as possible, replacing all components by async counterparts.
`async-std` provides an interface to all important primitives: filesystem operations, network operations and concurrency basics like timers. It also exposes an `task` in a model similar to the `thread` module found in the Rust standard lib. But it does not only include io primitives, but also `async/await` compatible versions of primitives like `Mutex`. You can read more about `async-std` in [the overview chapter][overview-std].
`async-std` provides an interface to all important primitives: filesystem operations, network operations and concurrency basics like timers. It also exposes a `task` in a model similar to the `thread` module found in the Rust standard lib. But it does not only include I/O primitives, but also `async/await` compatible versions of primitives like `Mutex`. You can read more about `async-std` in [the overview chapter][overview-std].
[organization]: https://github.com/async-rs/async-std
[overview-std]: overview/async-std/

@ -35,6 +35,6 @@ Security fixes will be applied to _all_ minor branches of this library in all _s
## Credits
This policy is based on [burntsushis regex crate][regex-policy].
This policy is based on [BurntSushi's regex crate][regex-policy].
[regex-policy]: https://github.com/rust-lang/regex#minimum-rust-version-policy

@ -10,13 +10,13 @@ The future defined in the [futures-rs](https://docs.rs/futures-preview/0.3.0-alp
It is critical to understand the difference between `std::future::Future` and `futures::future::Future`, and the approach that `async-std` takes towards them. In itself, `std::future::Future` is not something you want to interact with as a user—except by calling `.await` on it. The inner workings of `std::future::Future` are mostly of interest to people implementing `Future`. Make no mistake—this is very useful! Most of the functionality that used to be defined on `Future` itself has been moved to an extension trait called [`FuturesExt`](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/future/trait.FutureExt.html). From this information, you might be able to infer that the `futures` library serves as an extension to the core Rust async features.
In the same tradition as `futures`, `async-std` re-exports the core `std::future::Future` type. You can get actively opt into the extensions provided by the `futures-preview` crate by adding it your `Cargo.toml` and importing `FuturesExt`.
In the same tradition as `futures`, `async-std` re-exports the core `std::future::Future` type. You can actively opt into the extensions provided by the `futures-preview` crate by adding it to your `Cargo.toml` and importing `FuturesExt`.
## Interfaces and Stability
`async-std` aims to be a stable and reliable library, at the level of the Rust standard library. This also means that we don't rely on the `futures` library for our interface. Yet, we appreciate that many users have come to like the conveniences that `futures-rs` brings. For that reason, `async-std` implements all `futures` traits for its types.
Luckily, the approach from above gives you full flexibility. If you care about stability a lot, you can just use `async-std` as is. If you prefer the `futures` library interfaces, you link those in.. Both uses are first class.
Luckily, the approach from above gives you full flexibility. If you care about stability a lot, you can just use `async-std` as is. If you prefer the `futures` library interfaces, you link those in. Both uses are first class.
## `async_std::future`

@ -21,8 +21,8 @@ type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>
1. `async_std` uses `std` types where appropriate.
We'll need `ToSocketAddrs` to specify address to listen on.
2. `prelude` re-exports some traits required to work with futures and streams
3. The `task` module roughtly corresponds to `std::thread` module, but tasks are much lighter weight.
2. `prelude` re-exports some traits required to work with futures and streams.
3. The `task` module roughly corresponds to the `std::thread` module, but tasks are much lighter weight.
A single thread can run many tasks.
4. For the socket type, we use `TcpListener` from `async_std`, which is just like `std::net::TcpListener`, but is non-blocking and uses `async` API.
5. We will skip implementing comprehensive error handling in this example.
@ -43,7 +43,7 @@ async fn server(addr: impl ToSocketAddrs) -> Result<()> { // 1
}
```
1. We mark `server` function as `async`, which allows us to use `.await` syntax inside.
1. We mark the `server` function as `async`, which allows us to use `.await` syntax inside.
2. `TcpListener::bind` call returns a future, which we `.await` to extract the `Result`, and then `?` to get a `TcpListener`.
Note how `.await` and `?` work nicely together.
This is exactly how `std::net::TcpListener` works, but with `.await` added.
@ -73,4 +73,4 @@ The crucial thing to realise that is in Rust, unlike other languages, calling an
Async functions only construct futures, which are inert state machines.
To start stepping through the future state-machine in an async function, you should use `.await`.
In a non-async function, a way to execute a future is to handle it to the executor.
In this case, we use `task::block_on` to execute future on the current thread and block until it's done.
In this case, we use `task::block_on` to execute a future on the current thread and block until it's done.

@ -1,7 +1,7 @@
## All Together
At this point, we only need to start broker to get a fully-functioning (in the happy case!) chat:
At this point, we only need to start the broker to get a fully-functioning (in the happy case!) chat:
```rust
#![feature(async_await)]
@ -129,7 +129,7 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
}
```
1. Inside the `server`, we create broker's channel and `task`.
1. Inside the `server`, we create the broker's channel and `task`.
2. Inside `client`, we need to wrap `TcpStream` into an `Arc`, to be able to share it with the `client_writer`.
3. On login, we notify the broker.
Note that we `.unwrap` on send: broker should outlive all the clients and if that's not the case the broker probably panicked, so we can escalate the panic as well.

@ -1,6 +1,6 @@
## Clean Shutdown
On of the problems of the current implementation is that it doesn't handle graceful shutdown.
One of the problems of the current implementation is that it doesn't handle graceful shutdown.
If we break from the accept loop for some reason, all in-flight tasks are just dropped on the floor.
A more correct shutdown sequence would be:
@ -10,7 +10,7 @@ A more correct shutdown sequence would be:
A clean shutdown in a channel based architecture is easy, although it can appear a magic trick at first.
In Rust, receiver side of a channel is closed as soon as all senders are dropped.
That is, as soon as producers exit and drop their senders, the rest of the system shutdowns naturally.
That is, as soon as producers exit and drop their senders, the rest of the system shuts down naturally.
In `async_std` this translates to two rules:
1. Make sure that channels form an acyclic graph.
@ -83,4 +83,4 @@ Notice what happens with all of the channels once we exit the accept loop:
3. It's crucial that, at this stage, we drop the `peers` map.
This drops writer's senders.
4. Now we can join all of the writers.
5. Finally, we join the broker, which also guarantees that all the writes have terminated.
5. Finally, we join the broker, which also guarantees that all the writes have terminated.

@ -1,13 +1,13 @@
## Connecting Readers and Writers
So how we make sure that messages read in `client` flow into the relevant `client_writer`?
So how do we make sure that messages read in `client` flow into the relevant `client_writer`?
We should somehow maintain an `peers: HashMap<String, Sender<String>>` map which allows a client to find destination channels.
However, this map would be a bit of shared mutable state, so we'll have to wrap an `RwLock` over it and answer tough questions of what should happen if the client joins at the same moment as it receives a message.
One trick to make reasoning about state simpler comes from the actor model.
We can create a dedicated broker tasks which owns the `peers` map and communicates with other tasks by channels.
By hiding `peers` inside such "actor" task, we remove the need for mutxes and also make serialization point explicit.
By hiding `peers` inside such an "actor" task, we remove the need for mutxes and also make serialization point explicit.
The order of events "Bob sends message to Alice" and "Alice joins" is determined by the order of the corresponding events in the broker's event queue.
```rust
@ -55,6 +55,6 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
1. Broker should handle two types of events: a message or an arrival of a new peer.
2. Internal state of the broker is a `HashMap`.
Note how we don't need a `Mutex` here and can confidently say, at each iteration of the broker's loop, what is the current set of peers
3. To handle a message we send it over a channel to each destination
3. To handle a message, we send it over a channel to each destination
4. To handle new peer, we first register it in the peer's map ...
5. ... and then spawn a dedicated task to actually write the messages to the socket.

@ -11,11 +11,11 @@ If the read side finishes we will notify the write side that it should stop as w
That is, we need to add an ability to signal shutdown for the writer task.
One way to approach this is a `shutdown: Receiver<()>` channel.
There's a more minimal solution however, which makes a clever use of RAII.
There's a more minimal solution however, which makes clever use of RAII.
Closing a channel is a synchronization event, so we don't need to send a shutdown message, we can just drop the sender.
This way, we statically guarantee that we issue shutdown exactly once, even if we early return via `?` or panic.
First, let's add shutdown channel to the `client`:
First, let's add a shutdown channel to the `client`:
```rust
#[derive(Debug)]
@ -51,10 +51,10 @@ async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
1. To enforce that no messages are send along the shutdown channel, we use an uninhabited type.
2. We pass the shutdown channel to the writer task
3. In the reader, we create an `_shutdown_sender` whose only purpose is to get dropped.
3. In the reader, we create a `_shutdown_sender` whose only purpose is to get dropped.
In the `client_writer`, we now need to chose between shutdown and message channels.
We use `select` macro for this purpose:
In the `client_writer`, we now need to choose between shutdown and message channels.
We use the `select` macro for this purpose:
```rust
use futures::select;
@ -83,12 +83,12 @@ async fn client_writer(
```
1. We add shutdown channel as an argument.
2. Because of `select`, we can't use a `white let` loop, so we desugar it further into a `loop`.
2. Because of `select`, we can't use a `while let` loop, so we desugar it further into a `loop`.
3. In the shutdown case we use `match void {}` as a statically-checked `unreachable!()`.
Another problem is that between the moment we detect disconnection in `client_writer` and the moment when we actually remove the peer from the `peers` map, new messages might be pushed into the peer's channel.
To not lose these messages completely, we'll return the messages channel back to broker.
This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and make the broker itself infailable.
To not lose these messages completely, we'll return the messages channel back to the broker.
This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infailable.
## Final Code
@ -280,7 +280,7 @@ where
2. The broker's main loop exits when the input events channel is exhausted (that is, when all readers exit).
3. Because broker itself holds a `disconnect_sender`, we know that the disconnections channel can't be fully drained in the main loop.
4. We send peer's name and pending messages to the disconnections channel in both the happy and the not-so-happy path.
Again, we can safely unwrap because broker outlives writers.
Again, we can safely unwrap because the broker outlives writers.
5. We drop `peers` map to close writers' messages channel and shut down the writers for sure.
It is not strictly necessary in the current setup, where the broker waits for readers' shutdown anyway.
However, if we add a server-initiated shutdown (for example, kbd:[ctrl+c] handling), this will be a way for the broker to shutdown the writers.

@ -3,7 +3,7 @@
Let's now implement the client for the chat.
Because the protocol is line-based, the implementation is pretty straightforward:
* Lines read from stdin should be send over the socket.
* Lines read from stdin should be sent over the socket.
* Lines read from the socket should be echoed to stdout.
Unlike the server, the client needs only limited concurrency, as it interacts with only a single user.
@ -67,6 +67,6 @@ async fn try_main(addr: impl ToSocketAddrs) -> Result<()> {
}
```
1. Here we split `TcpStream` into read and write halfs: there's `impl AsyncRead for &'_ TcpStream`, just like the one in std.
2. We crate a steam of lines for both the socket and stdin.
3. In the main select loop, we print the lines we receive from server and send the lines we read from the console.
1. Here we split `TcpStream` into read and write halves: there's `impl AsyncRead for &'_ TcpStream`, just like the one in std.
2. We create a stream of lines for both the socket and stdin.
3. In the main select loop, we print the lines we receive from the server and send the lines we read from the console.

@ -3,6 +3,7 @@
Nothing is as simple as a chat server, right? Not quite, chat servers
already expose you to all the fun of asynchronous programming: how
do you handle clients connecting concurrently. How do you handle them disconnecting?
How do you distribute the messages?
In this tutorial, we will show you how to write one in `async-std`.

@ -46,7 +46,7 @@ async fn client(stream: TcpStream) -> Result<()> {
1. We use `task::spawn` function to spawn an independent task for working with each client.
That is, after accepting the client the `server` loop immediately starts waiting for the next one.
This is the core benefit of event-driven architecture: we serve many number of clients concurrently, without spending many hardware threads.
This is the core benefit of event-driven architecture: we serve many clients concurrently, without spending many hardware threads.
2. Luckily, the "split byte stream into lines" functionality is already implemented.
`.lines()` call returns a stream of `String`'s.
@ -60,7 +60,7 @@ async fn client(stream: TcpStream) -> Result<()> {
## Managing Errors
One serious problem in the above solution is that, while we correctly propagate errors in the `client`, we just drop the error on the floor afterwards!
That is, `task::spawn` does not return error immediately (it can't, it needs to run the future to completion first), only after it is joined.
That is, `task::spawn` does not return an error immediately (it can't, it needs to run the future to completion first), only after it is joined.
We can "fix" it by waiting for the task to be joined, like this:
```rust

@ -33,4 +33,4 @@ async fn client_writer(
1. We will use channels from the `futures` crate.
2. For simplicity, we will use `unbounded` channels, and won't be discussing backpressure in this tutorial.
3. As `client` and `client_writer` share the same `TcpStream`, we need to put it into an `Arc`.
Note that because `client` only reads from and `client_writer` only writes to the stream, so we don't get a race here.
Note that because `client` only reads from the stream and `client_writer` only writes to the stream, we don't get a race here.

@ -3,7 +3,7 @@
## Specification
The chat uses a simple text protocol over TCP.
Protocol consists of utf-8 messages, separated by `\n`.
The protocol consists of utf-8 messages, separated by `\n`.
The client connects to the server and sends login as a first line.
After that, the client can send messages to other clients using the following syntax:

@ -33,7 +33,7 @@ cfg_if! {
///
/// [`std::io::BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
/// [`futures::io::AsyncBufRead`]:
/// https://docs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html
pub trait BufRead {
/// Reads all bytes into `buf` until the delimiter `byte` or EOF is reached.
///

@ -34,7 +34,7 @@ cfg_if! {
///
/// [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
/// [`futures::io::AsyncRead`]:
/// https://docs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html
pub trait Read {
/// Reads some bytes from the byte stream.
///

@ -31,7 +31,7 @@ cfg_if! {
///
/// [`std::io::Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html
/// [`futures::io::AsyncSeek`]:
/// https://docs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html
pub trait Seek {
/// Seeks to a new position in a byte stream.
///

@ -33,7 +33,7 @@ cfg_if! {
///
/// [`std::io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`futures::io::AsyncWrite`]:
/// https://docs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html
pub trait Write {
/// Writes some bytes into the byte stream.
///

@ -25,9 +25,9 @@ use crate::task::{Context, Poll};
/// [`connect`]: struct.TcpStream.html#method.connect
/// [accepting]: struct.TcpListener.html#method.accept
/// [listener]: struct.TcpListener.html
/// [`AsyncRead`]: https://docs/futures-preview/0.3.0-alpha.13/futures/io/trait.AsyncRead.html
/// [`AsyncWrite`]: https://docs/futures-preview/0.3.0-alpha.13/futures/io/trait.AsyncRead.html
/// [`futures::io`]: https://docs/futures-preview/0.3.0-alpha.13/futures/io
/// [`AsyncRead`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html
/// [`AsyncWrite`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html
/// [`futures::io`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/index.html
/// [`shutdown`]: struct.TcpStream.html#method.shutdown
/// [`std::net::TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html
///

@ -53,7 +53,7 @@ cfg_if! {
///
/// [`std::iter::Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
/// [`futures::stream::Stream`]:
/// https://docs/futures-preview/0.3.0-alpha.17/futures/stream/trait.Stream.html
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.Stream.html
pub trait Stream {
/// The type of items yielded by this stream.
type Item;

Loading…
Cancel
Save