summaryrefslogtreecommitdiffstats
path: root/src/doc/book/nostarch/chapter20.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/doc/book/nostarch/chapter20.md')
-rw-r--r--src/doc/book/nostarch/chapter20.md731
1 files changed, 389 insertions, 342 deletions
diff --git a/src/doc/book/nostarch/chapter20.md b/src/doc/book/nostarch/chapter20.md
index e692e2f1d..9d4e22cd7 100644
--- a/src/doc/book/nostarch/chapter20.md
+++ b/src/doc/book/nostarch/chapter20.md
@@ -16,21 +16,19 @@ lessons.
For our final project, we’ll make a web server that says “hello” and looks like
Figure 20-1 in a web browser.
-!hello from rust at *img/trpl20-01.png*
-
Figure 20-1: Our final shared project
Here is our plan for building the web server:
1. Learn a bit about TCP and HTTP.
-2. Listen for TCP connections on a socket.
-3. Parse a small number of HTTP requests.
-4. Create a proper HTTP response.
-5. Improve the throughput of our server with a thread pool.
+1. Listen for TCP connections on a socket.
+1. Parse a small number of HTTP requests.
+1. Create a proper HTTP response.
+1. Improve the throughput of our server with a thread pool.
Before we get started, we should mention one detail: the method we’ll use won’t
be the best way to build a web server with Rust. Community members have
-published a number of production-ready crates available at *https://crates.io/*
+published a number of production-ready crates available at *https://crates.io*
that provide more complete web server and thread pool implementations than
we’ll build. However, our intention in this chapter is to help you learn, not
to take the easy route. Because Rust is a systems programming language, we can
@@ -81,12 +79,12 @@ Filename: src/main.rs
use std::net::TcpListener;
fn main() {
- [1] let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
+ 1 let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
- [2] for stream in listener.incoming() {
- [3] let stream = stream.unwrap();
+ 2 for stream in listener.incoming() {
+ 3 let stream = stream.unwrap();
- [4] println!("Connection established!");
+ 4 println!("Connection established!");
}
}
```
@@ -99,7 +97,7 @@ Using `TcpListener`, we can listen for TCP connections at the address
address representing your computer (this is the same on every computer and
doesn’t represent the authors’ computer specifically), and `7878` is the port.
We’ve chosen this port for two reasons: HTTP isn’t normally accepted on this
-port so our server is unlikely to conflict with any other web server you might
+port, so our server is unlikely to conflict with any other web server you might
have running on your machine, and 7878 is *rust* typed on a telephone.
The `bind` function in this scenario works like the `new` function in that it
@@ -109,7 +107,7 @@ to a port.”
The `bind` function returns a `Result<T, E>`, which indicates that it’s
possible for binding to fail. For example, connecting to port 80 requires
-administrator privileges (nonadministrators can listen only on ports higher
+administrator privileges (non-administrators can listen only on ports higher
than 1023), so if we tried to connect to port 80 without being an
administrator, binding wouldn’t work. Binding also wouldn’t work, for example,
if we ran two instances of our program and so had two programs listening to the
@@ -141,7 +139,7 @@ open connections are closed.
Let’s try running this code! Invoke `cargo run` in the terminal and then load
*127.0.0.1:7878* in a web browser. The browser should show an error message
-like “Connection reset,” because the server isn’t currently sending back any
+like “Connection reset” because the server isn’t currently sending back any
data. But when you look at your terminal, you should see several messages that
were printed when the browser connected to the server!
@@ -152,7 +150,7 @@ Connection established!
Connection established!
```
-Sometimes, you’ll see multiple messages printed for one browser request; the
+Sometimes you’ll see multiple messages printed for one browser request; the
reason might be that the browser is making a request for the page as well as a
request for other resources, like the *favicon.ico* icon that appears in the
browser tab.
@@ -164,10 +162,10 @@ part of the `drop` implementation. Browsers sometimes deal with closed
connections by retrying, because the problem might be temporary. The important
factor is that we’ve successfully gotten a handle to a TCP connection!
-Remember to stop the program by pressing <span class="keystroke">ctrl-c
-when you’re done running a particular version of the code. Then restart the
-program by invoking the `cargo run` command after you’ve made each set of code
-changes to make sure you’re running the newest code.
+Remember to stop the program by pressing ctrl-C when you’re done running a
+particular version of the code. Then restart the program by invoking the `cargo
+run` command after you’ve made each set of code changes to make sure you’re
+running the newest code.
### Reading the Request
@@ -181,7 +179,7 @@ look like Listing 20-2.
Filename: src/main.rs
```
-[1] use std::{
+1 use std::{
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
@@ -192,19 +190,19 @@ fn main() {
for stream in listener.incoming() {
let stream = stream.unwrap();
- [2] handle_connection(stream);
+ 2 handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
- [3] let buf_reader = BufReader::new(&mut stream);
- [4] let http_request: Vec<_> = buf_reader
- [5] .lines()
- [6] .map(|result| result.unwrap())
- [7] .take_while(|line| !line.is_empty())
+ 3 let buf_reader = BufReader::new(&mut stream);
+ 4 let http_request: Vec<_> = buf_reader
+ 5 .lines()
+ 6 .map(|result| result.unwrap())
+ 7 .take_while(|line| !line.is_empty())
.collect();
- [8] println!("Request: {:#?}", http_request);
+ 8 println!("Request: {:#?}", http_request);
}
```
@@ -251,8 +249,11 @@ $ cargo run
Request: [
"GET / HTTP/1.1",
"Host: 127.0.0.1:7878",
- "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0",
- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
+ "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0)
+Gecko/20100101 Firefox/99.0",
+ "Accept:
+text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*
+;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"DNT: 1",
@@ -292,17 +293,17 @@ being used, such as `GET` or `POST`, which describes how the client is making
this request. Our client used a `GET` request, which means it is asking for
information.
-The next part of the request line is */*, which indicates the *Uniform Resource
-Identifier* *(URI)* the client is requesting: a URI is almost, but not quite,
-the same as a *Uniform Resource Locator* *(URL)*. The difference between URIs
+The next part of the request line is */*, which indicates the *uniform resource
+identifier* *(URI)* the client is requesting: a URI is almost, but not quite,
+the same as a *uniform resource locator* *(URL)*. The difference between URIs
and URLs isn’t important for our purposes in this chapter, but the HTTP spec
-uses the term URI, so we can just mentally substitute URL for URI here.
+uses the term *URI*, so we can just mentally substitute *URL* for *URI* here.
The last part is the HTTP version the client uses, and then the request line
-ends in a *CRLF sequence*. (CRLF stands for *carriage return* and *line feed*,
+ends in a CRLF sequence. (CRLF stands for *carriage return* and *line feed*,
which are terms from the typewriter days!) The CRLF sequence can also be
written as `\r\n`, where `\r` is a carriage return and `\n` is a line feed. The
-CRLF sequence separates the request line from the rest of the request data.
+*CRLF sequence* separates the request line from the rest of the request data.
Note that when the CRLF is printed, we see a new line start rather than `\r\n`.
Looking at the request line data we received from running our program so far,
@@ -334,8 +335,8 @@ a reason phrase that provides a text description of the status code. After the
CRLF sequence are any headers, another CRLF sequence, and the body of the
response.
-Here is an example response that uses HTTP version 1.1, has a status code of
-200, an OK reason phrase, no headers, and no body:
+Here is an example response that uses HTTP version 1.1, and has a status code
+of 200, an OK reason phrase, no headers, and no body:
```
HTTP/1.1 200 OK\r\n\r\n
@@ -358,9 +359,9 @@ fn handle_connection(mut stream: TcpStream) {
.take_while(|line| !line.is_empty())
.collect();
- [1] let response = "HTTP/1.1 200 OK\r\n\r\n";
+ 1 let response = "HTTP/1.1 200 OK\r\n\r\n";
- [2] stream.write_all(response.as_bytes()[3]).unwrap();
+ 2 stream.write_all(response.3 as_bytes()).unwrap();
}
```
@@ -376,7 +377,7 @@ a real application you would add error handling here.
With these changes, let’s run our code and make a request. We’re no longer
printing any data to the terminal, so we won’t see any output other than the
output from Cargo. When you load *127.0.0.1:7878* in a web browser, you should
-get a blank page instead of an error. You’ve just hand-coded receiving an HTTP
+get a blank page instead of an error. You’ve just handcoded receiving an HTTP
request and sending a response!
### Returning Real HTML
@@ -413,11 +414,11 @@ Filename: src/main.rs
```
use std::{
- [1] fs,
+ 1 fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
-// --snip--
+--snip--
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
@@ -431,8 +432,11 @@ fn handle_connection(mut stream: TcpStream) {
let contents = fs::read_to_string("hello.html").unwrap();
let length = contents.len();
- [2] let response =
- format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
+ 2 let response = format!(
+ "{status_line}\r\n\
+ Content-Length: {length}\r\n\r\n\
+ {contents}"
+ );
stream.write_all(response.as_bytes()).unwrap();
}
@@ -442,8 +446,8 @@ Listing 20-5: Sending the contents of *hello.html* as the body of the response
We’ve added `fs` to the `use` statement to bring the standard library’s
filesystem module into scope [1]. The code for reading the contents of a file
-to a string should look familiar; we used it in Chapter 12 when we read the
-contents of a file for our I/O project in Listing 12-4.
+to a string should look familiar; we used it when we read the contents of a
+file for our I/O project in Listing 12-4.
Next, we use `format!` to add the file’s contents as the body of the success
response [2]. To ensure a valid HTTP response, we add the `Content-Length`
@@ -465,7 +469,7 @@ request to */*.
Right now, our web server will return the HTML in the file no matter what the
client requested. Let’s add functionality to check that the browser is
-requesting */* before returning the HTML file and return an error if the
+requesting */* before returning the HTML file, and return an error if the
browser requests anything else. For this we need to modify `handle_connection`,
as shown in Listing 20-6. This new code checks the content of the request
received against what we know a request for */* looks like and adds `if` and
@@ -474,23 +478,29 @@ received against what we know a request for */* looks like and adds `if` and
Filename: src/main.rs
```
-// --snip--
+--snip--
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
- [1] let request_line = buf_reader.lines().next().unwrap().unwrap();
+ 1 let request_line = buf_reader
+ .lines()
+ .next()
+ .unwrap()
+ .unwrap();
- [2] if request_line == "GET / HTTP/1.1" {
+ 2 if request_line == "GET / HTTP/1.1" {
let status_line = "HTTP/1.1 200 OK";
let contents = fs::read_to_string("hello.html").unwrap();
let length = contents.len();
let response = format!(
- "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
+ "{status_line}\r\n\
+ Content-Length: {length}\r\n\r\n\
+ {contents}"
);
stream.write_all(response.as_bytes()).unwrap();
- [3] } else {
+ 3 } else {
// some other request
}
}
@@ -526,18 +536,20 @@ indicating the response to the end user.
Filename: src/main.rs
```
- // --snip--
- } else {
- [1] let status_line = "HTTP/1.1 404 NOT FOUND";
- [2] let contents = fs::read_to_string("404.html").unwrap();
- let length = contents.len();
+--snip--
+} else {
+ 1 let status_line = "HTTP/1.1 404 NOT FOUND";
+ 2 let contents = fs::read_to_string("404.html").unwrap();
+ let length = contents.len();
- let response = format!(
- "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
- );
+ let response = format!(
+ "{status_line}\r\n\
+ Content-Length: {length}\r\n\r\n
+ {contents}"
+ );
- stream.write_all(response.as_bytes()).unwrap();
- }
+ stream.write_all(response.as_bytes()).unwrap();
+}
```
Listing 20-7: Responding with status code 404 and an error page if anything
@@ -546,8 +558,8 @@ other than */* was requested
Here, our response has a status line with status code 404 and the reason phrase
`NOT FOUND` [1]. The body of the response will be the HTML in the file
*404.html* [1]. You’ll need to create a *404.html* file next to *hello.html*
-for the error page; again feel free to use any HTML you want or use the example
-HTML in Listing 20-8.
+for the error page; again feel free to use any HTML you want, or use the
+example HTML in Listing 20-8.
Filename: 404.html
@@ -573,34 +585,38 @@ return the contents of *hello.html*, and any other request, like
### A Touch of Refactoring
-At the moment the `if` and `else` blocks have a lot of repetition: they’re both
-reading files and writing the contents of the files to the stream. The only
-differences are the status line and the filename. Let’s make the code more
+At the moment, the `if` and `else` blocks have a lot of repetition: they’re
+both reading files and writing the contents of the files to the stream. The
+only differences are the status line and the filename. Let’s make the code more
concise by pulling out those differences into separate `if` and `else` lines
that will assign the values of the status line and the filename to variables;
we can then use those variables unconditionally in the code to read the file
-and write the response. Listing 20-9 shows the resulting code after replacing
+and write the response. Listing 20-9 shows the resultant code after replacing
the large `if` and `else` blocks.
Filename: src/main.rs
```
-// --snip--
+--snip--
fn handle_connection(mut stream: TcpStream) {
- // --snip--
+ --snip--
- let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
- ("HTTP/1.1 200 OK", "hello.html")
- } else {
- ("HTTP/1.1 404 NOT FOUND", "404.html")
- };
+ let (status_line, filename) =
+ if request_line == "GET / HTTP/1.1" {
+ ("HTTP/1.1 200 OK", "hello.html")
+ } else {
+ ("HTTP/1.1 404 NOT FOUND", "404.html")
+ };
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
- let response =
- format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
+ let response = format!(
+ "{status_line}\r\n\
+ Content-Length: {length}\r\n\r\n\
+ {contents}"
+ );
stream.write_all(response.as_bytes()).unwrap();
}
@@ -638,14 +654,14 @@ server received more and more requests, this serial execution would be less and
less optimal. If the server receives a request that takes a long time to
process, subsequent requests will have to wait until the long request is
finished, even if the new requests can be processed quickly. We’ll need to fix
-this, but first, we’ll look at the problem in action.
+this, but first we’ll look at the problem in action.
-### Simulating a Slow Request in the Current Server Implementation
+### Simulating a Slow Request
We’ll look at how a slow-processing request can affect other requests made to
our current server implementation. Listing 20-10 implements handling a request
to */sleep* with a simulated slow response that will cause the server to sleep
-for 5 seconds before responding.
+for five seconds before responding.
Filename: src/main.rs
@@ -657,44 +673,44 @@ use std::{
thread,
time::Duration,
};
-// --snip--
+--snip--
fn handle_connection(mut stream: TcpStream) {
- // --snip--
+ --snip--
- let (status_line, filename) = [1] match &request_line[..] {
- [2] "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
- [3] "GET /sleep HTTP/1.1" => {
+ let (status_line, filename) = 1 match &request_line[..] {
+ 2 "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
+ 3 "GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "hello.html")
}
- [4] _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
+ 4 _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
};
- // --snip--
+ --snip--
}
```
-Listing 20-10: Simulating a slow request by sleeping for 5 seconds
+Listing 20-10: Simulating a slow request by sleeping for five seconds
We switched from `if` to `match` now that we have three cases [1]. We need to
-explicitly match on a slice of `request_line` to pattern match against the
+explicitly match on a slice of `request_line` to pattern-match against the
string literal values; `match` doesn’t do automatic referencing and
-dereferencing like the equality method does.
+dereferencing, like the equality method does.
The first arm [2] is the same as the `if` block from Listing 20-9. The second
arm [3] matches a request to */sleep*. When that request is received, the
-server will sleep for 5 seconds before rendering the successful HTML page. The
-third arm [4] is the same as the `else` block from Listing 20-9.
+server will sleep for five seconds before rendering the successful HTML page.
+The third arm [4] is the same as the `else` block from Listing 20-9.
You can see how primitive our server is: real libraries would handle the
recognition of multiple requests in a much less verbose way!
Start the server using `cargo run`. Then open two browser windows: one for
-*http://127.0.0.1:7878/* and the other for *http://127.0.0.1:7878/sleep*. If
-you enter the */* URI a few times, as before, you’ll see it respond quickly.
-But if you enter */sleep* and then load */*, you’ll see that */* waits until
-`sleep` has slept for its full 5 seconds before loading.
+*http://127.0.0.1:7878* and the other for *http://127.0.0.1:7878/sleep*. If you
+enter the */* URI a few times, as before, you’ll see it respond quickly. But if
+you enter */sleep* and then load */*, you’ll see that */* waits until `sleep`
+has slept for its full five seconds before loading.
There are multiple techniques we could use to avoid requests backing up behind
a slow request; the one we’ll implement is a thread pool.
@@ -711,33 +727,28 @@ a new task. A thread pool allows you to process connections concurrently,
increasing the throughput of your server.
We’ll limit the number of threads in the pool to a small number to protect us
-from Denial of Service (DoS) attacks; if we had our program create a new thread
-for each request as it came in, someone making 10 million requests to our
-server could create havoc by using up all our server’s resources and grinding
-the processing of requests to a halt.
+from DoS attacks; if we had our program create a new thread for each request as
+it came in, someone making 10 million requests to our server could create havoc
+by using up all our server’s resources and grinding the processing of requests
+to a halt.
Rather than spawning unlimited threads, then, we’ll have a fixed number of
threads waiting in the pool. Requests that come in are sent to the pool for
processing. The pool will maintain a queue of incoming requests. Each of the
threads in the pool will pop off a request from this queue, handle the request,
and then ask the queue for another request. With this design, we can process up
-to `N` requests concurrently, where `N` is the number of threads. If each
-thread is responding to a long-running request, subsequent requests can still
-back up in the queue, but we’ve increased the number of long-running requests
-we can handle before reaching that point.
+to N requests concurrently, where N is the number of threads. If each thread is
+responding to a long-running request, subsequent requests can still back up in
+the queue, but we’ve increased the number of long-running requests we can
+handle before reaching that point.
This technique is just one of many ways to improve the throughput of a web
-server. Other options you might explore are the *fork/join model*, the
-*single-threaded async I/O model*, or the *multi-threaded async I/O model*. If
+server. Other options you might explore are the fork/join model, the
+single-threaded async I/O model, and the multithreaded async I/O model. If
you’re interested in this topic, you can read more about other solutions and
try to implement them; with a low-level language like Rust, all of these
options are possible.
-<!-- A more modern approach would probably use tokio, which could be a
-multi-threaded async I/O model. /JT -->
-<!-- I've added "multi-theraded async I/O model", I don't want to get into
-particular async crates though /Carol -->
-
Before we begin implementing a thread pool, let’s talk about what using the
pool should look like. When you’re trying to design code, writing the client
interface first can help guide your design. Write the API of the code so it’s
@@ -757,8 +768,7 @@ First, let’s explore how our code might look if it did create a new thread for
every connection. As mentioned earlier, this isn’t our final plan due to the
problems with potentially spawning an unlimited number of threads, but it is a
starting point to get a working multithreaded server first. Then we’ll add the
-thread pool as an improvement, and contrasting the two solutions will be
-easier.
+thread pool as an improvement, and contrasting the two solutions will be easier.
Listing 20-11 shows the changes to make to `main` to spawn a new thread to
handle each stream within the `for` loop.
@@ -790,9 +800,9 @@ new threads without any limit.
#### Creating a Finite Number of Threads
-We want our thread pool to work in a similar, familiar way so switching from
-threads to a thread pool doesn’t require large changes to the code that uses
-our API. Listing 20-12 shows the hypothetical interface for a `ThreadPool`
+We want our thread pool to work in a similar, familiar way so that switching
+from threads to a thread pool doesn’t require large changes to the code that
+uses our API. Listing 20-12 shows the hypothetical interface for a `ThreadPool`
struct we want to use instead of `thread::spawn`.
Filename: src/main.rs
@@ -800,12 +810,12 @@ Filename: src/main.rs
```
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
- [1] let pool = ThreadPool::new(4);
+ 1 let pool = ThreadPool::new(4);
for stream in listener.incoming() {
let stream = stream.unwrap();
- [2] pool.execute(|| {
+ 2 pool.execute(|| {
handle_connection(stream);
});
}
@@ -819,9 +829,9 @@ of threads, in this case four [1]. Then, in the `for` loop, `pool.execute` has
a similar interface as `thread::spawn` in that it takes a closure the pool
should run for each stream [2]. We need to implement `pool.execute` so it takes
the closure and gives it to a thread in the pool to run. This code won’t yet
-compile, but we’ll try so the compiler can guide us in how to fix it.
+compile, but we’ll try so that the compiler can guide us in how to fix it.
-#### Building `ThreadPool` Using Compiler Driven Development
+#### Building ThreadPool Using Compiler-Driven Development
Make the changes in Listing 20-12 to *src/main.rs*, and then let’s use the
compiler errors from `cargo check` to drive our development. Here is the first
@@ -831,21 +841,21 @@ error we get:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0433]: failed to resolve: use of undeclared type `ThreadPool`
- --> src/main.rs:10:16
+ --> src/main.rs:11:16
|
-10 | let pool = ThreadPool::new(4);
+11 | let pool = ThreadPool::new(4);
| ^^^^^^^^^^ use of undeclared type `ThreadPool`
```
Great! This error tells us we need a `ThreadPool` type or module, so we’ll
build one now. Our `ThreadPool` implementation will be independent of the kind
-of work our web server is doing. So, let’s switch the `hello` crate from a
+of work our web server is doing. So let’s switch the `hello` crate from a
binary crate to a library crate to hold our `ThreadPool` implementation. After
we change to a library crate, we could also use the separate thread pool
library for any work we want to do using a thread pool, not just for serving
web requests.
-Create a *src/lib.rs* that contains the following, which is the simplest
+Create a *src/lib.rs* file that contains the following, which is the simplest
definition of a `ThreadPool` struct that we can have for now:
Filename: src/lib.rs
@@ -854,7 +864,7 @@ Filename: src/lib.rs
pub struct ThreadPool;
```
-Then edit *main.rs* file to bring `ThreadPool` into scope from the library
+Then edit the *main.rs* file to bring `ThreadPool` into scope from the library
crate by adding the following code to the top of *src/main.rs*:
Filename: src/main.rs
@@ -869,11 +879,13 @@ we need to address:
```
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no function or associated item named `new` found for struct `ThreadPool` in the current scope
- --> src/bin/main.rs:11:28
+error[E0599]: no function or associated item named `new` found for struct
+`ThreadPool` in the current scope
+ --> src/main.rs:12:28
|
-11 | let pool = ThreadPool::new(4);
- | ^^^ function or associated item not found in `ThreadPool`
+12 | let pool = ThreadPool::new(4);
+ | ^^^ function or associated item not found in
+`ThreadPool`
```
This error indicates that next we need to create an associated function named
@@ -894,37 +906,37 @@ impl ThreadPool {
}
```
-We chose `usize` as the type of the `size` parameter, because we know that a
+We chose `usize` as the type of the `size` parameter because we know that a
negative number of threads doesn’t make any sense. We also know we’ll use this
-4 as the number of elements in a collection of threads, which is what the
-`usize` type is for, as discussed in the “Integer Types” section of Chapter 3.
+`4` as the number of elements in a collection of threads, which is what the
+`usize` type is for, as discussed in “Integer Types” on page XX.
Let’s check the code again:
```
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no method named `execute` found for struct `ThreadPool` in the current scope
- --> src/bin/main.rs:16:14
+error[E0599]: no method named `execute` found for struct `ThreadPool` in the
+current scope
+ --> src/main.rs:17:14
|
-16 | pool.execute(|| {
+17 | pool.execute(|| {
| ^^^^^^^ method not found in `ThreadPool`
```
Now the error occurs because we don’t have an `execute` method on `ThreadPool`.
-Recall from the “Creating a Finite Number of Threads” section that we decided
+Recall from “Creating a Finite Number of Threads” on page XX that we decided
our thread pool should have an interface similar to `thread::spawn`. In
addition, we’ll implement the `execute` function so it takes the closure it’s
given and gives it to an idle thread in the pool to run.
We’ll define the `execute` method on `ThreadPool` to take a closure as a
-parameter. Recall from the “Moving Captured Values Out of the Closure and the
-`Fn` Traits” section in Chapter 13 that we can take closures as parameters with
-three different traits: `Fn`, `FnMut`, and `FnOnce`. We need to decide which
-kind of closure to use here. We know we’ll end up doing something similar to
-the standard library `thread::spawn` implementation, so we can look at what
-bounds the signature of `thread::spawn` has on its parameter. The documentation
-shows us the following:
+parameter. Recall from “Moving Captured Values Out of Closures and the Fn
+Traits” on page XX that we can take closures as parameters with three different
+traits: `Fn`, `FnMut`, and `FnOnce`. We need to decide which kind of closure to
+use here. We know we’ll end up doing something similar to the standard library
+`thread::spawn` implementation, so we can look at what bounds the signature of
+`thread::spawn` has on its parameter. The documentation shows us the following:
```
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
@@ -952,10 +964,10 @@ Filename: src/lib.rs
```
impl ThreadPool {
- // --snip--
+ --snip--
pub fn execute<F>(&self, f: F)
where
- F: FnOnce() [1] + Send + 'static,
+ F: FnOnce() 1 + Send + 'static,
{
}
}
@@ -967,7 +979,7 @@ function definitions, the return type can be omitted from the signature, but
even if we have no parameters, we still need the parentheses.
Again, this is the simplest implementation of the `execute` method: it does
-nothing, but we’re trying only to make our code compile. Let’s check it again:
+nothing, but we’re only trying to make our code compile. Let’s check it again:
```
$ cargo check
@@ -981,18 +993,18 @@ the chapter. Our library isn’t actually calling the closure passed to `execute
yet!
> Note: A saying you might hear about languages with strict compilers, such as
-> Haskell and Rust, is “if the code compiles, it works.” But this saying is not
-> universally true. Our project compiles, but it does absolutely nothing! If we
-> were building a real, complete project, this would be a good time to start
-> writing unit tests to check that the code compiles *and* has the behavior we
-> want.
+Haskell and Rust, is “if the code compiles, it works.” But this saying is not
+universally true. Our project compiles, but it does absolutely nothing! If we
+were building a real, complete project, this would be a good time to start
+writing unit tests to check that the code compiles *and* has the behavior we
+want.
-#### Validating the Number of Threads in `new`
+#### Validating the Number of Threads in new
We aren’t doing anything with the parameters to `new` and `execute`. Let’s
implement the bodies of these functions with the behavior we want. To start,
let’s think about `new`. Earlier we chose an unsigned type for the `size`
-parameter, because a pool with a negative number of threads makes no sense.
+parameter because a pool with a negative number of threads makes no sense.
However, a pool with zero threads also makes no sense, yet zero is a perfectly
valid `usize`. We’ll add code to check that `size` is greater than zero before
we return a `ThreadPool` instance and have the program panic if it receives a
@@ -1006,16 +1018,16 @@ impl ThreadPool {
///
/// The size is the number of threads in the pool.
///
- [1] /// # Panics
+ 1 /// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
- [2] assert!(size > 0);
+ 2 assert!(size > 0);
ThreadPool
}
- // --snip--
+ --snip--
}
```
@@ -1035,14 +1047,10 @@ you’re feeling ambitious, try to write a function named `build` with the
following signature to compare with the `new` function:
```
-pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
+pub fn build(
+ size: usize
+) -> Result<ThreadPool, PoolCreationError> {
```
-<!-- Similar nit here to a comment I made a few chapters ago: fallible
-constructors are awkward to use. We may want to discourage their use. A modern
-approach might use a builder pattern to set the number of threads, and use a
-default number of threads that's non-zero. /JT -->
-<!-- I've changed the function name to be `build` which nicely matches the
-changes JT suggested for chapter 12. /Carol -->
#### Creating Space to Store the Threads
@@ -1073,18 +1081,18 @@ returned a `ThreadPool` instance containing them.
Filename: src/lib.rs
```
-[1] use std::thread;
+1 use std::thread;
pub struct ThreadPool {
- [2] threads: Vec<thread::JoinHandle<()>>,
+ 2 threads: Vec<thread::JoinHandle<()>>,
}
impl ThreadPool {
- // --snip--
+ --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
- [3] let mut threads = Vec::with_capacity(size);
+ 3 let mut threads = Vec::with_capacity(size);
for _ in 0..size {
// create some threads and store them in the vector
@@ -1092,26 +1100,26 @@ impl ThreadPool {
ThreadPool { threads }
}
- // --snip--
+ --snip--
}
```
Listing 20-14: Creating a vector for `ThreadPool` to hold the threads
-We’ve brought `std::thread` into scope in the library crate [1], because we’re
+We’ve brought `std::thread` into scope in the library crate [1] because we’re
using `thread::JoinHandle` as the type of the items in the vector in
`ThreadPool` [2].
Once a valid size is received, our `ThreadPool` creates a new vector that can
hold `size` items [3]. The `with_capacity` function performs the same task as
-`Vec::new` but with an important difference: it preallocates space in the
+`Vec::new` but with an important difference: it pre-allocates space in the
vector. Because we know we need to store `size` elements in the vector, doing
this allocation up front is slightly more efficient than using `Vec::new`,
which resizes itself as elements are inserted.
When you run `cargo check` again, it should succeed.
-#### A `Worker` Struct Responsible for Sending Code from the `ThreadPool` to a Thread
+#### Sending Code from the ThreadPool to a Thread
We left a comment in the `for` loop in Listing 20-14 regarding the creation of
threads. Here, we’ll look at how we actually create threads. The standard
@@ -1125,31 +1133,31 @@ implement it manually.
We’ll implement this behavior by introducing a new data structure between the
`ThreadPool` and the threads that will manage this new behavior. We’ll call
this data structure *Worker*, which is a common term in pooling
-implementations. The Worker picks up code that needs to be run and runs the
-code in the Worker’s thread.
+implementations. The `Worker` picks up code that needs to be run and runs the
+code in its thread.
-Think of people working in the kitchen at a restaurant: the
-workers wait until orders come in from customers, and then they’re responsible
-for taking those orders and filling them.
+Think of people working in the kitchen at a restaurant: the workers wait until
+orders come in from customers, and then they’re responsible for taking those
+orders and filling them.
Instead of storing a vector of `JoinHandle<()>` instances in the thread pool,
we’ll store instances of the `Worker` struct. Each `Worker` will store a single
`JoinHandle<()>` instance. Then we’ll implement a method on `Worker` that will
take a closure of code to run and send it to the already running thread for
-execution. We’ll also give each worker an `id` so we can distinguish between
-the different workers in the pool when logging or debugging.
+execution. We’ll also give each `Worker` an `id` so we can distinguish between
+the different instances of `Worker` in the pool when logging or debugging.
Here is the new process that will happen when we create a `ThreadPool`. We’ll
implement the code that sends the closure to the thread after we have `Worker`
set up in this way:
1. Define a `Worker` struct that holds an `id` and a `JoinHandle<()>`.
-2. Change `ThreadPool` to hold a vector of `Worker` instances.
-3. Define a `Worker::new` function that takes an `id` number and returns a
- `Worker` instance that holds the `id` and a thread spawned with an empty
- closure.
-4. In `ThreadPool::new`, use the `for` loop counter to generate an `id`, create
- a new `Worker` with that `id`, and store the worker in the vector.
+1. Change `ThreadPool` to hold a vector of `Worker` instances.
+1. Define a `Worker::new` function that takes an `id` number and returns a
+`Worker` instance that holds the `id` and a thread spawned with an empty
+closure.
+1. In `ThreadPool::new`, use the `for` loop counter to generate an `id`, create
+a new `Worker` with that `id`, and store the `Worker` in the vector.
If you’re up for a challenge, try implementing these changes on your own before
looking at the code in Listing 20-15.
@@ -1162,49 +1170,39 @@ Filename: src/lib.rs
use std::thread;
pub struct ThreadPool {
- [1] workers: Vec<Worker>,
+ 1 workers: Vec<Worker>,
}
impl ThreadPool {
- // --snip--
+ --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let mut workers = Vec::with_capacity(size);
- [2] for id in 0..size {
- [3] workers.push(Worker::new(id));
+ 2 for id in 0..size {
+ 3 workers.push(Worker::new(id));
}
ThreadPool { workers }
}
- // --snip--
+ --snip--
}
-[4] struct Worker {
+4 struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
- [5] fn new(id: usize) -> Worker {
- [6] let thread = thread::spawn(|| {});
+ 5 fn new(id: usize) -> Worker {
+ 6 let thread = thread::spawn(|| {});
- Worker { [7] id, [8] thread }
+ Worker { 7 id, 8 thread }
}
}
```
-<!-- Spawning a thread in the constructor isn't safe to do as the spawn
-of the thread may fail. You can use
-https://doc.rust-lang.org/std/thread/struct.Builder.html#method.spawn
-to be better protected against running out of resources. This should
-probably not live in the constructor, but instead in some helper function
-that can return a Result. /JT -->
-<!-- I've added a note in a few paragraphs. I think this behavior is perfectly
-fine for this example so I'm not going to change the code, but it is something
-readers should know. /Carol -->
-
Listing 20-15: Modifying `ThreadPool` to hold `Worker` instances instead of
holding threads directly
@@ -1221,11 +1219,11 @@ so we make the `Worker` struct [4] and its `new` function [5] private. The
empty closure [6].
> Note: If the operating system can’t create a thread because there aren’t
-> enough system resources, `thread::spawn` will panic. That will cause our
-> whole server to panic, even though the creation of some threads might
-> succeed. For simplicity’s sake, this behavior is fine, but in a production
-> thread pool implementation, you’d likely want to use `std::thread::Builder`
-> and its `spawn` method that returns `Result` instead.
+enough system resources, `thread::spawn` will panic. That will cause our whole
+server to panic, even though the creation of some threads might succeed. For
+simplicity’s sake, this behavior is fine, but in a production thread pool
+implementation, you’d likely want to use `std::thread::Builder` and its `spawn`
+method that returns `Result` instead.
This code will compile and will store the number of `Worker` instances we
specified as an argument to `ThreadPool::new`. But we’re *still* not processing
@@ -1247,13 +1245,13 @@ as the queue of jobs, and `execute` will send a job from the `ThreadPool` to
the `Worker` instances, which will send the job to its thread. Here is the plan:
1. The `ThreadPool` will create a channel and hold on to the sender.
-2. Each `Worker` will hold on to the receiver.
-3. We’ll create a new `Job` struct that will hold the closures we want to send
- down the channel.
-4. The `execute` method will send the job it wants to execute through the
- sender.
-5. In its thread, the `Worker` will loop over its receiver and execute the
- closures of any jobs it receives.
+1. Each `Worker` will hold on to the receiver.
+1. We’ll create a new `Job` struct that will hold the closures we want to send
+down the channel.
+1. The `execute` method will send the job it wants to execute through the
+sender.
+1. In its thread, the `Worker` will loop over its receiver and execute the
+closures of any jobs it receives.
Let’s start by creating a channel in `ThreadPool::new` and holding the sender
in the `ThreadPool` instance, as shown in Listing 20-16. The `Job` struct
@@ -1273,11 +1271,11 @@ pub struct ThreadPool {
struct Job;
impl ThreadPool {
- // --snip--
+ --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
- [1] let (sender, receiver) = mpsc::channel();
+ 1 let (sender, receiver) = mpsc::channel();
let mut workers = Vec::with_capacity(size);
@@ -1285,9 +1283,9 @@ impl ThreadPool {
workers.push(Worker::new(id));
}
- ThreadPool { workers, [2] sender }
+ ThreadPool { workers, 2 sender }
}
- // --snip--
+ --snip--
}
```
@@ -1297,16 +1295,16 @@ transmits `Job` instances
In `ThreadPool::new`, we create our new channel [1] and have the pool hold the
sender [2]. This will successfully compile.
-Let’s try passing a receiver of the channel into each worker as the thread pool
-creates the channel. We know we want to use the receiver in the thread that the
-workers spawn, so we’ll reference the `receiver` parameter in the closure. The
-code in Listing 20-17 won’t quite compile yet.
+Let’s try passing a receiver of the channel into each `Worker` as the thread
+pool creates the channel. We know we want to use the receiver in the thread
+that the `Worker` instances spawn, so we’ll reference the `receiver` parameter
+in the closure. The code in Listing 20-17 won’t quite compile yet.
Filename: src/lib.rs
```
impl ThreadPool {
- // --snip--
+ --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
@@ -1315,20 +1313,20 @@ impl ThreadPool {
let mut workers = Vec::with_capacity(size);
for id in 0..size {
- [1] workers.push(Worker::new(id, receiver));
+ 1 workers.push(Worker::new(id, receiver));
}
ThreadPool { workers, sender }
}
- // --snip--
+ --snip--
}
-// --snip--
+--snip--
impl Worker {
fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {
let thread = thread::spawn(|| {
- [2] receiver;
+ 2 receiver;
});
Worker { id, thread }
@@ -1336,7 +1334,7 @@ impl Worker {
}
```
-Listing 20-17: Passing the receiver to the workers
+Listing 20-17: Passing the receiver to each `Worker`
We’ve made some small and straightforward changes: we pass the receiver into
`Worker::new` [1], and then we use it inside the closure [2].
@@ -1347,13 +1345,15 @@ When we try to check this code, we get this error:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0382]: use of moved value: `receiver`
- --> src/lib.rs:27:42
+ --> src/lib.rs:26:42
|
-22 | let (sender, receiver) = mpsc::channel();
- | -------- move occurs because `receiver` has type `std::sync::mpsc::Receiver<Job>`, which does not implement the `Copy` trait
+21 | let (sender, receiver) = mpsc::channel();
+ | -------- move occurs because `receiver` has type
+`std::sync::mpsc::Receiver<Job>`, which does not implement the `Copy` trait
...
-27 | workers.push(Worker::new(id, receiver));
- | ^^^^^^^^ value moved here, in previous iteration of loop
+26 | workers.push(Worker::new(id, receiver));
+ | ^^^^^^^^ value moved here, in
+previous iteration of loop
```
The code is trying to pass `receiver` to multiple `Worker` instances. This
@@ -1361,7 +1361,8 @@ won’t work, as you’ll recall from Chapter 16: the channel implementation tha
Rust provides is multiple *producer*, single *consumer*. This means we can’t
just clone the consuming end of the channel to fix this code. We also don’t
want to send a message multiple times to multiple consumers; we want one list
-of messages with multiple workers such that each message gets processed once.
+of messages with multiple `Worker` instances such that each message gets
+processed once.
Additionally, taking a job off the channel queue involves mutating the
`receiver`, so the threads need a safe way to share and modify `receiver`;
@@ -1369,9 +1370,10 @@ otherwise, we might get race conditions (as covered in Chapter 16).
Recall the thread-safe smart pointers discussed in Chapter 16: to share
ownership across multiple threads and allow the threads to mutate the value, we
-need to use `Arc<Mutex<T>>`. The `Arc` type will let multiple workers own the
-receiver, and `Mutex` will ensure that only one worker gets a job from the
-receiver at a time. Listing 20-18 shows the changes we need to make.
+need to use `Arc<Mutex<T>>`. The `Arc` type will let multiple `Worker`
+instances own the receiver, and `Mutex` will ensure that only one `Worker` gets
+a job from the receiver at a time. Listing 20-18 shows the changes we need to
+make.
Filename: src/lib.rs
@@ -1380,75 +1382,81 @@ use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
-// --snip--
+--snip--
impl ThreadPool {
- // --snip--
+ --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
- [1] let receiver = Arc::new(Mutex::new(receiver));
+ 1 let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
- workers.push(Worker::new(id, Arc::clone(&receiver)[2]));
+ workers.push(
+ Worker::new(id, Arc::clone(& 2 receiver))
+ );
}
ThreadPool { workers, sender }
}
- // --snip--
+ --snip--
}
-// --snip--
+--snip--
impl Worker {
- fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
- // --snip--
+ fn new(
+ id: usize,
+ receiver: Arc<Mutex<mpsc::Receiver<Job>>>,
+ ) -> Worker {
+ --snip--
}
}
```
-Listing 20-18: Sharing the receiver among the workers using `Arc` and `Mutex`
+Listing 20-18: Sharing the receiver among the `Worker` instances using `Arc`
+and `Mutex`
In `ThreadPool::new`, we put the receiver in an `Arc` and a `Mutex` [1]. For
-each new worker, we clone the `Arc` to bump the reference count so the workers
-can share ownership of the receiver [2].
+each new `Worker`, we clone the `Arc` to bump the reference count so the
+`Worker` instances can share ownership of the receiver [2].
With these changes, the code compiles! We’re getting there!
-#### Implementing the `execute` Method
+#### Implementing the execute Method
Let’s finally implement the `execute` method on `ThreadPool`. We’ll also change
`Job` from a struct to a type alias for a trait object that holds the type of
-closure that `execute` receives. As discussed in the “Creating Type Synonyms
-with Type Aliases” section of Chapter 19, type aliases allow us to make long
-types shorter for ease of use. Look at Listing 20-19.
+closure that `execute` receives. As discussed in “Creating Type Synonyms with
+Type Aliases” on page XX, type aliases allow us to make long types shorter for
+ease of use. Look at Listing 20-19.
Filename: src/lib.rs
```
-// --snip--
+--snip--
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
- // --snip--
+ --snip--
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
- [1] let job = Box::new(f);
+ 1 let job = Box::new(f);
- [2] self.sender.send(job).unwrap();
+ 2 self.sender.send(job).unwrap();
}
}
-// --snip--
+--snip--
```
Listing 20-19: Creating a `Job` type alias for a `Box` that holds each closure
@@ -1463,7 +1471,7 @@ executing: our threads continue executing as long as the pool exists. The
reason we use `unwrap` is that we know the failure case won’t happen, but the
compiler doesn’t know that.
-But we’re not quite done yet! In the worker, our closure being passed to
+But we’re not quite done yet! In the `Worker`, our closure being passed to
`thread::spawn` still only *references* the receiving end of the channel.
Instead, we need the closure to loop forever, asking the receiving end of the
channel for a job and running the job when it gets one. Let’s make the change
@@ -1472,12 +1480,19 @@ shown in Listing 20-20 to `Worker::new`.
Filename: src/lib.rs
```
-// --snip--
+--snip--
impl Worker {
- fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
+ fn new(
+ id: usize,
+ receiver: Arc<Mutex<mpsc::Receiver<Job>>>,
+ ) -> Worker {
let thread = thread::spawn(move || loop {
- let job = receiver.lock()[1].unwrap()[2].recv()[3].unwrap()[4];
+ let job = receiver
+ 1 .lock()
+ 2 .unwrap()
+ 3 .recv()
+ 4 .unwrap();
println!("Worker {id} got a job; executing.");
@@ -1489,7 +1504,8 @@ impl Worker {
}
```
-Listing 20-20: Receiving and executing the jobs in the worker’s thread
+Listing 20-20: Receiving and executing the jobs in the `Worker` instance’s
+thread
Here, we first call `lock` on the `receiver` to acquire the mutex [1], and then
we call `unwrap` to panic on any errors [2]. Acquiring a lock might fail if the
@@ -1555,21 +1571,24 @@ overloaded if the server receives a lot of requests. If we make a request to
*/sleep*, the server will be able to serve other requests by having another
thread run them.
-> Note: if you open */sleep* in multiple browser windows simultaneously, they
-> might load one at a time in 5 second intervals. Some web browsers execute
-> multiple instances of the same request sequentially for caching reasons. This
-> limitation is not caused by our web server.
+> Note: If you open */sleep* in multiple browser windows simultaneously, they
+might load one at a time in five-second intervals. Some web browsers execute
+multiple instances of the same request sequentially for caching reasons. This
+limitation is not caused by our web server.
After learning about the `while let` loop in Chapter 18, you might be wondering
-why we didn’t write the worker thread code as shown in Listing 20-21.
+why we didn’t write the `Worker` thread code as shown in Listing 20-21.
Filename: src/lib.rs
```
-// --snip--
+--snip--
impl Worker {
- fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
+ fn new(
+ id: usize,
+ receiver: Arc<Mutex<mpsc::Receiver<Job>>>,
+ ) -> Worker {
let thread = thread::spawn(move || {
while let Ok(job) = receiver.lock().unwrap().recv() {
println!("Worker {id} got a job; executing.");
@@ -1598,21 +1617,20 @@ longer than intended if we aren’t mindful of the lifetime of the
The code in Listing 20-20 that uses `let job =
receiver.lock().unwrap().recv().unwrap();` works because with `let`, any
-temporary values used in the expression on the right hand side of the equals
+temporary values used in the expression on the right-hand side of the equal
sign are immediately dropped when the `let` statement ends. However, `while
let` (and `if let` and `match`) does not drop temporary values until the end of
the associated block. In Listing 20-21, the lock remains held for the duration
-of the call to `job()`, meaning other workers cannot receive jobs.
+of the call to `job()`, meaning other `Worker` instances cannot receive jobs.
## Graceful Shutdown and Cleanup
The code in Listing 20-20 is responding to requests asynchronously through the
use of a thread pool, as we intended. We get some warnings about the `workers`,
`id`, and `thread` fields that we’re not using in a direct way that reminds us
-we’re not cleaning up anything. When we use the less elegant <span
-class="keystroke">ctrl-c</span> method to halt the main thread, all other
-threads are stopped immediately as well, even if they’re in the middle of
-serving a request.
+we’re not cleaning up anything. When we use the less elegant ctrl-C method to
+halt the main thread, all other threads are stopped immediately as well, even
+if they’re in the middle of serving a request.
Next, then, we’ll implement the `Drop` trait to call `join` on each of the
threads in the pool so they can finish the requests they’re working on before
@@ -1621,7 +1639,7 @@ accepting new requests and shut down. To see this code in action, we’ll modify
our server to accept only two requests before gracefully shutting down its
thread pool.
-### Implementing the `Drop` Trait on `ThreadPool`
+### Implementing the Drop Trait on ThreadPool
Let’s start with implementing `Drop` on our thread pool. When the pool is
dropped, our threads should all join to make sure they finish their work.
@@ -1633,10 +1651,10 @@ Filename: src/lib.rs
```
impl Drop for ThreadPool {
fn drop(&mut self) {
- [1] for worker in &mut self.workers {
- [2] println!("Shutting down worker {}", worker.id);
+ 1 for worker in &mut self.workers {
+ 2 println!("Shutting down worker {}", worker.id);
- [3] worker.thread.join().unwrap();
+ 3 worker.thread.join().unwrap();
}
}
}
@@ -1644,21 +1662,29 @@ impl Drop for ThreadPool {
Listing 20-22: Joining each thread when the thread pool goes out of scope
-First, we loop through each of the thread pool `workers` [1]. We use `&mut` for
+First we loop through each of the thread pool `workers` [1]. We use `&mut` for
this because `self` is a mutable reference, and we also need to be able to
-mutate `worker`. For each worker, we print a message saying that this
-particular worker is shutting down [2], and then we call `join` on that
-worker’s thread [3]. If the call to `join` fails, we use `unwrap` to make Rust
-panic and go into an ungraceful shutdown.
+mutate `worker`. For each `worker`, we print a message saying that this
+particular `Worker` instance is shutting down [2], and then we call `join` on
+that `Worker` instance’s thread [3]. If the call to `join` fails, we use
+`unwrap` to make Rust panic and go into an ungraceful shutdown.
Here is the error we get when we compile this code:
```
-error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference
- --> src/lib.rs:52:13
- |
-52 | worker.thread.join().unwrap();
- | ^^^^^^^^^^^^^ move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
+error[E0507]: cannot move out of `worker.thread` which is behind a mutable
+reference
+ --> src/lib.rs:52:13
+ |
+52 | worker.thread.join().unwrap();
+ | ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this
+method call
+ | |
+ | move occurs because `worker.thread` has type
+`JoinHandle<()>`, which does not implement the `Copy` trait
+ |
+note: this function takes ownership of the receiver `self`, which moves
+`worker.thread`
```
The error tells us we can’t call `join` because we only have a mutable borrow
@@ -1687,24 +1713,27 @@ Now let’s lean on the compiler to find the other places that need to change.
Checking this code, we get two errors:
```
-error[E0599]: no method named `join` found for enum `Option` in the current scope
+error[E0599]: no method named `join` found for enum `Option` in the current
+scope
--> src/lib.rs:52:27
|
52 | worker.thread.join().unwrap();
- | ^^^^ method not found in `Option<JoinHandle<()>>`
+ | ^^^^ method not found in
+`Option<JoinHandle<()>>`
error[E0308]: mismatched types
--> src/lib.rs:72:22
|
72 | Worker { id, thread }
- | ^^^^^^ expected enum `Option`, found struct `JoinHandle`
+ | ^^^^^^ expected enum `Option`, found struct
+`JoinHandle`
|
= note: expected enum `Option<JoinHandle<()>>`
found struct `JoinHandle<_>`
help: try wrapping the expression in `Some`
|
-72 | Worker { id, Some(thread) }
- | +++++ +
+72 | Worker { id, thread: Some(thread) }
+ | +++++++++++++ +
```
Let’s address the second error, which points to the code at the end of
@@ -1715,8 +1744,11 @@ Filename: src/lib.rs
```
impl Worker {
- fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
- // --snip--
+ fn new(
+ id: usize,
+ receiver: Arc<Mutex<mpsc::Receiver<Job>>>,
+ ) -> Worker {
+ --snip--
Worker {
id,
@@ -1738,8 +1770,8 @@ impl Drop for ThreadPool {
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
- [1] if let Some(thread) = worker.thread.take() {
- [2] thread.join().unwrap();
+ 1 if let Some(thread) = worker.thread.take() {
+ 2 thread.join().unwrap();
}
}
}
@@ -1749,27 +1781,27 @@ impl Drop for ThreadPool {
As discussed in Chapter 17, the `take` method on `Option` takes the `Some`
variant out and leaves `None` in its place. We’re using `if let` to destructure
the `Some` and get the thread [1]; then we call `join` on the thread [2]. If a
-worker’s thread is already `None`, we know that worker has already had its
-thread cleaned up, so nothing happens in that case.
+`Worker` instance’s thread is already `None`, we know that `Worker` has already
+had its thread cleaned up, so nothing happens in that case.
### Signaling to the Threads to Stop Listening for Jobs
With all the changes we’ve made, our code compiles without any warnings.
-However, the bad news is this code doesn’t function the way we want it to yet.
-The key is the logic in the closures run by the threads of the `Worker`
-instances: at the moment, we call `join`, but that won’t shut down the threads
+However, the bad news is that this code doesn’t function the way we want it to
+yet. The key is the logic in the closures run by the threads of the `Worker`
+instances: at the moment, we call `join`, but that won’t shut down the threads,
because they `loop` forever looking for jobs. If we try to drop our
`ThreadPool` with our current implementation of `drop`, the main thread will
-block forever waiting for the first thread to finish.
+block forever, waiting for the first thread to finish.
-To fix this problem, we’ll need a change in the the `ThreadPool` `drop`
+To fix this problem, we’ll need a change in the `ThreadPool` `drop`
implementation and then a change in the `Worker` loop.
-First, we’ll change the `ThreadPool` `drop` implementation to explicitly drop
+First we’ll change the `ThreadPool` `drop` implementation to explicitly drop
the `sender` before waiting for the threads to finish. Listing 20-23 shows the
changes to `ThreadPool` to explicitly drop `sender`. We use the same `Option`
and `take` technique as we did with the thread to be able to move `sender` out
-of `ThreadPool`:
+of `ThreadPool`.
Filename: src/lib.rs
@@ -1778,10 +1810,10 @@ pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
-// --snip--
+--snip--
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
- // --snip--
+ --snip--
ThreadPool {
workers,
@@ -1795,13 +1827,17 @@ impl ThreadPool {
{
let job = Box::new(f);
- self.sender.as_ref().unwrap().send(job).unwrap();
+ self.sender
+ .as_ref()
+ .unwrap()
+ .send(job)
+ .unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
- [1] drop(self.sender.take());
+ 1 drop(self.sender.take());
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
@@ -1814,28 +1850,37 @@ impl Drop for ThreadPool {
}
```
-Listing 20-23: Explicitly drop `sender` before joining the worker threads
+Listing 20-23: Explicitly dropping `sender` before joining the `Worker` threads
Dropping `sender` [1] closes the channel, which indicates no more messages will
-be sent. When that happens, all the calls to `recv` that the workers do in the
-infinite loop will return an error. In Listing 20-24, we change the `Worker`
-loop to gracefully exit the loop in that case, which means the threads will
-finish when the `ThreadPool` `drop` implementation calls `join` on them.
+be sent. When that happens, all the calls to `recv` that the `Worker` instances
+do in the infinite loop will return an error. In Listing 20-24, we change the
+`Worker` loop to gracefully exit the loop in that case, which means the threads
+will finish when the `ThreadPool` `drop` implementation calls `join` on them.
Filename: src/lib.rs
```
impl Worker {
- fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
+ fn new(
+ id: usize,
+ receiver: Arc<Mutex<mpsc::Receiver<Job>>>,
+ ) -> Worker {
let thread = thread::spawn(move || loop {
- match receiver.lock().unwrap().recv() {
+ let message = receiver.lock().unwrap().recv();
+
+ match message {
Ok(job) => {
- println!("Worker {id} got a job; executing.");
+ println!(
+ "Worker {id} got a job; executing."
+ );
job();
}
Err(_) => {
- println!("Worker {id} disconnected; shutting down.");
+ println!(
+ "Worker {id} shutting down."
+ );
break;
}
}
@@ -1849,7 +1894,7 @@ impl Worker {
}
```
-Listing 20-24: Explicitly break out of the loop when `recv` returns an error
+Listing 20-24: Explicitly breaking out of the loop when `recv` returns an error
To see this code in action, let’s modify `main` to accept only two requests
before gracefully shutting down the server, as shown in Listing 20-25.
@@ -1873,8 +1918,8 @@ fn main() {
}
```
-Listing 20-25: Shut down the server after serving two requests by exiting the
-loop
+Listing 20-25: Shutting down the server after serving two requests by exiting
+the loop
You wouldn’t want a real-world web server to shut down after serving only two
requests. This code just demonstrates that the graceful shutdown and cleanup is
@@ -1905,26 +1950,27 @@ Shutting down worker 2
Shutting down worker 3
```
-You might see a different ordering of workers and messages printed. We can see
-how this code works from the messages: workers 0 and 3 got the first two
-requests. The server stopped accepting connections after the second connection,
-and the `Drop` implementation on `ThreadPool` starts executing before worker 3
-even starts its job. Dropping the `sender` disconnects all the workers and
-tells them to shut down. The workers each print a message when they disconnect,
-and then the thread pool calls `join` to wait for each worker thread to finish.
+You might see a different ordering of `Worker` IDs and messages printed. We can
+see how this code works from the messages: `Worker` instances 0 and 3 got the
+first two requests. The server stopped accepting connections after the second
+connection, and the `Drop` implementation on `ThreadPool` starts executing
+before `Worker` 3 even starts its job. Dropping the `sender` disconnects all
+the `Worker` instances and tells them to shut down. The `Worker` instances each
+print a message when they disconnect, and then the thread pool calls `join` to
+wait for each `Worker` thread to finish.
Notice one interesting aspect of this particular execution: the `ThreadPool`
-dropped the `sender`, and before any worker received an error, we tried to join
-worker 0. Worker 0 had not yet gotten an error from `recv`, so the main thread
-blocked waiting for worker 0 to finish. In the meantime, worker 3 received a
-job and then all threads received an error. When worker 0 finished, the main
-thread waited for the rest of the workers to finish. At that point, they had
-all exited their loops and stopped.
+dropped the `sender`, and before any `Worker` received an error, we tried to
+join `Worker` 0. `Worker` 0 had not yet gotten an error from `recv`, so the
+main thread blocked, waiting for `Worker` 0 to finish. In the meantime,
+`Worker` 3 received a job and then all threads received an error. When `Worker`
+0 finished, the main thread waited for the rest of the `Worker` instances to
+finish. At that point, they had all exited their loops and stopped.
Congrats! We’ve now completed our project; we have a basic web server that uses
a thread pool to respond asynchronously. We’re able to perform a graceful
shutdown of the server, which cleans up all the threads in the pool. See
-*https://www.nostarch.com/Rust2021/* to download the full code for this chapter
+*https://www.nostarch.com/Rust2021* to download the full code for this chapter
for reference.
We could do more here! If you want to continue enhancing this project, here are
@@ -1934,14 +1980,15 @@ some ideas:
* Add tests of the library’s functionality.
* Change calls to `unwrap` to more robust error handling.
* Use `ThreadPool` to perform some task other than serving web requests.
-* Find a thread pool crate on *https://crates.io/* and implement a similar web
- server using the crate instead. Then compare its API and robustness to the
- thread pool we implemented.
+* Find a thread pool crate on *https://crates.io* and implement a similar web
+server using the crate instead. Then compare its API and robustness to the
+thread pool we implemented.
## Summary
Well done! You’ve made it to the end of the book! We want to thank you for
joining us on this tour of Rust. You’re now ready to implement your own Rust
-projects and help with other peoples’ projects. Keep in mind that there is a
+projects and help with other people’s projects. Keep in mind that there is a
welcoming community of other Rustaceans who would love to help you with any
challenges you encounter on your Rust journey.
+