Skip to content

Commit d3c6714

Browse files
committed
Seperate the tutorial in multiple parts
Fix some typos
1 parent d941bb8 commit d3c6714

18 files changed

+921
-957
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Build Status](https://travis-ci.org/async-rs/async-std.svg?branch=master)](https://travis-ci.org/stjepang/async-std)
44
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](
5-
https://github.com/async-std/async-std)
5+
https://github.com/async-rs/async-std)
66
[![Cargo](https://img.shields.io/crates/v/async-std.svg)](https://crates.io/crates/async-std)
77
[![Documentation](https://docs.rs/async-std/badge.svg)](https://docs.rs/async-std)
88
[![chat](https://img.shields.io/discord/598880689856970762.svg?logo=discord)](https://discord.gg/JvZeVNe)

docs/src/SUMMARY.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Summary
22

3-
- [Overview](./overview.md)
3+
- [Welcome to `async-std`!](./overview.md)
44
- [`async-std`](./overview/async-std.md)
55
- [`std::future` and `futures-rs`](./overview/std-and-library-futures.md)
66
- [Stability guarantees](./overview/stability-guarantees.md)
@@ -9,16 +9,18 @@
99
- [Tasks](./concepts/tasks.md)
1010
- [Async read/write](./concepts/async-read-write.md)
1111
- [Streams and Channels](./concepts/streams.md)
12-
- [Tutorials](./tutorials/index.md)
13-
- [Integrating std::thread](./tutorials/integrating-std-thread.md)
14-
- [Implementing a Chat Server](./tutorials/chat.md)
15-
- [Async Patterns](./patterns.md)
16-
- [Fork/Join](./patterns/fork-join.md)
17-
- [Accepting requests](./patterns/accepting-concurrent-requests.md)
18-
- [Proper Shutdown](./patterns/proper-shutdown.md)
19-
- [Background Tasks](./patterns/background-tasks.md)
20-
- [Testing](./patterns/testing.md)
21-
- [Collected Small Patterns](./patterns/small-patterns.md)
12+
- [Tutorial: Implementing a chat](./tutorial/index.md)
13+
- [Specification and Getting started](./tutorial/specification.md)
14+
- [Writing an Accept Loop](./tutorial/accept_loop.md)
15+
- [Receiving Messages](./tutorial/receiving_messages.md)
16+
- [Sending Messages](./tutorial/sending_messages.md)
17+
- [Connecting Readers and Writers](./tutorial/connecting_readers_and_writers.md)
18+
- [All Together](./tutorial/all_together.md)
19+
- [Clean Shutdown](./tutorial/clean_shutdown.md)
20+
- [Handling Disconnections](./tutorial/handling_disconnections.md)
21+
- [Implementing a Client](./tutorial/implementing_a_client.md)
22+
- [TODO: Async Patterns](./patterns.md)
23+
- [TODO: Collected Small Patterns](./patterns/small-patterns.md)
2224
- [Security practices](./security/index.md)
23-
- [Security disclosures and policy](./security/policy.md)
25+
- [Security Disclosures and Policy](./security/policy.md)
2426
- [Glossary](./glossary.md)

docs/src/overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Overview
1+
# Welcome to `async-std`
22

33
![async-std logo](./images/horizontal_color.svg)
44

55
`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.
66

77
`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].
88

9-
[organization]: https://github.com/async-std/async-std
9+
[organization]: https://github.com/async-rs/async-std
1010
[overview-std]: overview/async-std/

docs/src/security/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ In the case that you find a security-related bug in our library, please get in t
88

99
Patches improving the resilience of the library or the testing setup are happily accepted on our [github org][github].
1010

11-
[security-policies]: /security/policy
12-
[github]: https://github.com/async-std/
11+
[security-policy]: /security/policy
12+
[github]: https://github.com/async-rs

docs/src/tutorial/accept_loop.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
## Writing an Accept Loop
2+
3+
Let's implement the scaffold of the server: a loop that binds a TCP socket to an address and starts accepting connections.
4+
5+
6+
First of all, let's add required import boilerplate:
7+
8+
```rust
9+
#![feature(async_await)]
10+
11+
use std::net::ToSocketAddrs; // 1
12+
13+
use async_std::{
14+
prelude::*, // 2
15+
task, // 3
16+
net::TcpListener, // 4
17+
};
18+
19+
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 5
20+
```
21+
22+
1. `async_std` uses `std` types where appropriate.
23+
We'll need `ToSocketAddrs` to specify address to listen on.
24+
2. `prelude` re-exports some traits required to work with futures and streams
25+
3. The `task` module roughtly corresponds to `std::thread` module, but tasks are much lighter weight.
26+
A single thread can run many tasks.
27+
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.
28+
5. We will skip implementing comprehensive error handling in this example.
29+
To propagate the errors, we will use a boxed error trait object.
30+
Do you know that there's `From<&'_ str> for Box<dyn Error>` implementation in stdlib, which allows you to use strings with `?` operator?
31+
32+
33+
Now we can write the server's accept loop:
34+
35+
```rust
36+
async fn server(addr: impl ToSocketAddrs) -> Result<()> { // 1
37+
let listener = TcpListener::bind(addr).await?; // 2
38+
let mut incoming = listener.incoming();
39+
while let Some(stream) = incoming.next().await { // 3
40+
// TODO
41+
}
42+
Ok(())
43+
}
44+
```
45+
46+
1. We mark `server` function as `async`, which allows us to use `.await` syntax inside.
47+
2. `TcpListener::bind` call returns a future, which we `.await` to extract the `Result`, and then `?` to get a `TcpListener`.
48+
Note how `.await` and `?` work nicely together.
49+
This is exactly how `std::net::TcpListener` works, but with `.await` added.
50+
Mirroring API of `std` is an explicit design goal of `async_std`.
51+
3. Here, we would like to iterate incoming sockets, just how one would do in `std`:
52+
53+
```rust
54+
let listener: std::net::TcpListener = unimplemented!();
55+
for stream in listener.incoming() {
56+
57+
}
58+
```
59+
60+
Unfortunately this doesn't quite work with `async` yet, because there's no support for `async` for-loops in the language yet.
61+
For this reason we have to implement the loop manually, by using `while let Some(item) = iter.next().await` pattern.
62+
63+
Finally, let's add main:
64+
65+
```rust
66+
fn main() -> Result<()> {
67+
let fut = server("127.0.0.1:8080");
68+
task::block_on(fut)
69+
}
70+
```
71+
72+
The crucial thing to realise that is in Rust, unlike other languages, calling an async function does **not** run any code.
73+
Async functions only construct futures, which are inert state machines.
74+
To start stepping through the future state-machine in an async function, you should use `.await`.
75+
In a non-async function, a way to execute a future is to handle it to the executor.
76+
In this case, we use `task::block_on` to execute future on the current thread and block until it's done.

docs/src/tutorial/all_together.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
## All Together
3+
4+
At this point, we only need to start broker to get a fully-functioning (in the happy case!) chat:
5+
6+
```rust
7+
#![feature(async_await)]
8+
9+
use std::{
10+
net::ToSocketAddrs,
11+
sync::Arc,
12+
collections::hash_map::{HashMap, Entry},
13+
};
14+
15+
use futures::{
16+
channel::mpsc,
17+
SinkExt,
18+
};
19+
20+
use async_std::{
21+
io::BufReader,
22+
prelude::*,
23+
task,
24+
net::{TcpListener, TcpStream},
25+
};
26+
27+
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
28+
type Sender<T> = mpsc::UnboundedSender<T>;
29+
type Receiver<T> = mpsc::UnboundedReceiver<T>;
30+
31+
32+
fn main() -> Result<()> {
33+
task::block_on(server("127.0.0.1:8080"))
34+
}
35+
36+
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
37+
let listener = TcpListener::bind(addr).await?;
38+
39+
let (broker_sender, broker_receiver) = mpsc::unbounded(); // 1
40+
let _broker_handle = task::spawn(broker(broker_receiver));
41+
let mut incoming = listener.incoming();
42+
while let Some(stream) = incoming.next().await {
43+
let stream = stream?;
44+
println!("Accepting from: {}", stream.peer_addr()?);
45+
spawn_and_log_error(client(broker_sender.clone(), stream));
46+
}
47+
Ok(())
48+
}
49+
50+
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
51+
let stream = Arc::new(stream); // 2
52+
let reader = BufReader::new(&*stream);
53+
let mut lines = reader.lines();
54+
55+
let name = match lines.next().await {
56+
None => Err("peer disconnected immediately")?,
57+
Some(line) => line?,
58+
};
59+
broker.send(Event::NewPeer { name: name.clone(), stream: Arc::clone(&stream) }).await // 3
60+
.unwrap();
61+
62+
while let Some(line) = lines.next().await {
63+
let line = line?;
64+
let (dest, msg) = match line.find(':') {
65+
None => continue,
66+
Some(idx) => (&line[..idx], line[idx + 1 ..].trim()),
67+
};
68+
let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect();
69+
let msg: String = msg.trim().to_string();
70+
71+
broker.send(Event::Message { // 4
72+
from: name.clone(),
73+
to: dest,
74+
msg,
75+
}).await.unwrap();
76+
}
77+
Ok(())
78+
}
79+
80+
async fn client_writer(
81+
mut messages: Receiver<String>,
82+
stream: Arc<TcpStream>,
83+
) -> Result<()> {
84+
let mut stream = &*stream;
85+
while let Some(msg) = messages.next().await {
86+
stream.write_all(msg.as_bytes()).await?;
87+
}
88+
Ok(())
89+
}
90+
91+
#[derive(Debug)]
92+
enum Event {
93+
NewPeer {
94+
name: String,
95+
stream: Arc<TcpStream>,
96+
},
97+
Message {
98+
from: String,
99+
to: Vec<String>,
100+
msg: String,
101+
},
102+
}
103+
104+
async fn broker(mut events: Receiver<Event>) -> Result<()> {
105+
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
106+
107+
while let Some(event) = events.next().await {
108+
match event {
109+
Event::Message { from, to, msg } => {
110+
for addr in to {
111+
if let Some(peer) = peers.get_mut(&addr) {
112+
peer.send(format!("from {}: {}\n", from, msg)).await?
113+
}
114+
}
115+
}
116+
Event::NewPeer { name, stream} => {
117+
match peers.entry(name) {
118+
Entry::Occupied(..) => (),
119+
Entry::Vacant(entry) => {
120+
let (client_sender, client_receiver) = mpsc::unbounded();
121+
entry.insert(client_sender); // 4
122+
spawn_and_log_error(client_writer(client_receiver, stream)); // 5
123+
}
124+
}
125+
}
126+
}
127+
}
128+
Ok(())
129+
}
130+
```
131+
132+
1. Inside the `server`, we create broker's channel and `task`.
133+
2. Inside `client`, we need to wrap `TcpStream` into an `Arc`, to be able to share it with the `client_writer`.
134+
3. On login, we notify the broker.
135+
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.
136+
4. Similarly, we forward parsed messages to the broker, assuming that it is alive.

docs/src/tutorial/clean_shutdown.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
## Clean Shutdown
2+
3+
On of the problems of the current implementation is that it doesn't handle graceful shutdown.
4+
If we break from the accept loop for some reason, all in-flight tasks are just dropped on the floor.
5+
A more correct shutdown sequence would be:
6+
7+
1. Stop accepting new clients
8+
2. Deliver all pending messages
9+
3. Exit the process
10+
11+
A clean shutdown in a channel based architecture is easy, although it can appear a magic trick at first.
12+
In Rust, receiver side of a channel is closed as soon as all senders are dropped.
13+
That is, as soon as producers exit and drop their senders, the rest of the system shutdowns naturally.
14+
In `async_std` this translates to two rules:
15+
16+
1. Make sure that channels form an acyclic graph.
17+
2. Take care to wait, in the correct order, until intermediate layers of the system process pending messages.
18+
19+
In `a-chat`, we already have an unidirectional flow of messages: `reader -> broker -> writer`.
20+
However, we never wait for broker and writers, which might cause some messages to get dropped.
21+
Let's add waiting to the server:
22+
23+
```rust
24+
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
25+
let listener = TcpListener::bind(addr).await?;
26+
27+
let (broker_sender, broker_receiver) = mpsc::unbounded();
28+
let broker = task::spawn(broker(broker_receiver));
29+
let mut incoming = listener.incoming();
30+
while let Some(stream) = incoming.next().await {
31+
let stream = stream?;
32+
println!("Accepting from: {}", stream.peer_addr()?);
33+
spawn_and_log_error(client(broker_sender.clone(), stream));
34+
}
35+
drop(broker_sender); // 1
36+
broker.await?; // 5
37+
Ok(())
38+
}
39+
```
40+
41+
And to the broker:
42+
43+
```rust
44+
async fn broker(mut events: Receiver<Event>) -> Result<()> {
45+
let mut writers = Vec::new();
46+
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
47+
48+
while let Some(event) = events.next().await { // 2
49+
match event {
50+
Event::Message { from, to, msg } => {
51+
for addr in to {
52+
if let Some(peer) = peers.get_mut(&addr) {
53+
peer.send(format!("from {}: {}\n", from, msg)).await?
54+
}
55+
}
56+
}
57+
Event::NewPeer { name, stream} => {
58+
match peers.entry(name) {
59+
Entry::Occupied(..) => (),
60+
Entry::Vacant(entry) => {
61+
let (client_sender, client_receiver) = mpsc::unbounded();
62+
entry.insert(client_sender);
63+
let handle = spawn_and_log_error(client_writer(client_receiver, stream));
64+
writers.push(handle); // 4
65+
}
66+
}
67+
}
68+
}
69+
}
70+
drop(peers); // 3
71+
for writer in writers { // 4
72+
writer.await?;
73+
}
74+
Ok(())
75+
}
76+
```
77+
78+
Notice what happens with all of the channels once we exit the accept loop:
79+
80+
1. First, we drop the main broker's sender.
81+
That way when the readers are done, there's no sender for the broker's channel, and the chanel closes.
82+
2. Next, the broker exits `while let Some(event) = events.next().await` loop.
83+
3. It's crucial that, at this stage, we drop the `peers` map.
84+
This drops writer's senders.
85+
4. Now we can join all of the writers.
86+
5. Finally, we join the broker, which also guarantees that all the writes have terminated.

0 commit comments

Comments
 (0)