740 lines
46 KiB
HTML
740 lines
46 KiB
HTML
|
<!DOCTYPE HTML>
|
|||
|
<html lang="en" class="sidebar-visible no-js light">
|
|||
|
<head>
|
|||
|
<!-- Book generated using mdBook -->
|
|||
|
<meta charset="UTF-8">
|
|||
|
<title>Graceful Shutdown and Cleanup - The Rust Programming Language</title>
|
|||
|
|
|||
|
|
|||
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|||
|
<meta name="description" content="">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|||
|
<meta name="theme-color" content="#ffffff" />
|
|||
|
|
|||
|
<link rel="shortcut icon" href="favicon.png">
|
|||
|
<link rel="stylesheet" href="css/variables.css">
|
|||
|
<link rel="stylesheet" href="css/general.css">
|
|||
|
<link rel="stylesheet" href="css/chrome.css">
|
|||
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|||
|
|
|||
|
<!-- Fonts -->
|
|||
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|||
|
<link href="googleFonts/css.css" rel="stylesheet" type="text/css">
|
|||
|
|
|||
|
<!-- Highlight.js Stylesheets -->
|
|||
|
<link rel="stylesheet" href="highlight.css">
|
|||
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|||
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|||
|
|
|||
|
<!-- Custom theme stylesheets -->
|
|||
|
|
|||
|
<link rel="stylesheet" href="ferris.css">
|
|||
|
|
|||
|
<link rel="stylesheet" href="theme/2018-edition.css">
|
|||
|
|
|||
|
|
|||
|
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<!-- Provide site root to javascript -->
|
|||
|
<script type="text/javascript">
|
|||
|
var path_to_root = "";
|
|||
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|||
|
<script type="text/javascript">
|
|||
|
try {
|
|||
|
var theme = localStorage.getItem('mdbook-theme');
|
|||
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|||
|
|
|||
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|||
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|||
|
}
|
|||
|
|
|||
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|||
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|||
|
}
|
|||
|
} catch (e) { }
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|||
|
<script type="text/javascript">
|
|||
|
var theme;
|
|||
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|||
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|||
|
var html = document.querySelector('html');
|
|||
|
html.classList.remove('no-js')
|
|||
|
html.classList.remove('light')
|
|||
|
html.classList.add(theme);
|
|||
|
html.classList.add('js');
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|||
|
<script type="text/javascript">
|
|||
|
var html = document.querySelector('html');
|
|||
|
var sidebar = 'hidden';
|
|||
|
if (document.body.clientWidth >= 1080) {
|
|||
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|||
|
sidebar = sidebar || 'visible';
|
|||
|
}
|
|||
|
html.classList.remove('sidebar-visible');
|
|||
|
html.classList.add("sidebar-" + sidebar);
|
|||
|
</script>
|
|||
|
|
|||
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|||
|
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
|||
|
<ol class="chapter"><li class="expanded affix "><a href="title-page.html">The Rust Programming Language</a></li><li class="expanded affix "><a href="foreword.html">Foreword</a></li><li class="expanded affix "><a href="ch00-00-introduction.html">Introduction</a></li><li class="expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> Getting Started</a></li><li><ol class="section"><li class="expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> Installation</a></li><li class="expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> Programming a Guessing Game</a></li><li class="expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> Common Programming Concepts</a></li><li><ol class="section"><li class="expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> Variables and Mutability</a></li><li class="expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> Data Types</a></li><li class="expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> Functions</a></li><li class="expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> Comments</a></li><li class="expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> Control Flow</a></li></ol></li><li class="expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> Understanding Ownership</a></li><li><ol class="section"><li class="expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> What is Ownership?</a></li><li class="expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> References and Borrowing</a></li><li class="expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> The Slice Type</a></li></ol></li><li class="expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> Using Structs to Structure Related Data</a></li><li><ol class="section"><li class="expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> Defining and Instantiating Structs</a></li><li class="expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> An Example Program Using Structs</a></li><li class="expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> Method Syntax</a></li></ol></li><li class="expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> Enums and Pattern Matching</a></li><li><ol class="section"><li class="expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> Defining an Enum</a></li><li class="expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> The match Control Flow Operator</a></li><li class="expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> Concise Control Flow with if let</a></li></ol></li><li class="expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> Managing Growing Projects with Packages, Crates, and Modules</a></li><li><ol class="section"><li class="expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> Packages and Crates</a></li><li class="expanded "><a href="ch07-02-defining-modules-to-control-scope-and-privacy.html"><strong aria-hidden="true">7.2.</strong> Defining Modules to Control Scope and Privacy</a></li><li class="expanded "><a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html"><
|
|||
|
</div>
|
|||
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|||
|
</nav>
|
|||
|
|
|||
|
<div id="page-wrapper" class="page-wrapper">
|
|||
|
|
|||
|
<div class="page">
|
|||
|
|
|||
|
<div id="menu-bar" class="menu-bar">
|
|||
|
<div id="menu-bar-sticky-container">
|
|||
|
<div class="left-buttons">
|
|||
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|||
|
<i class="fa fa-bars"></i>
|
|||
|
</button>
|
|||
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|||
|
<i class="fa fa-paint-brush"></i>
|
|||
|
</button>
|
|||
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|||
|
<i class="fa fa-search"></i>
|
|||
|
</button>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
<h1 class="menu-title">The Rust Programming Language</h1>
|
|||
|
|
|||
|
<div class="right-buttons">
|
|||
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|||
|
<i id="print-button" class="fa fa-print"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
<div id="search-wrapper" class="hidden">
|
|||
|
<form id="searchbar-outer" class="searchbar-outer">
|
|||
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|||
|
</form>
|
|||
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|||
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|||
|
<ul id="searchresults">
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|||
|
<script type="text/javascript">
|
|||
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|||
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|||
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|||
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|||
|
});
|
|||
|
</script>
|
|||
|
|
|||
|
<div id="content" class="content">
|
|||
|
<main>
|
|||
|
<h2><a class="header" href="#graceful-shutdown-and-cleanup" id="graceful-shutdown-and-cleanup">Graceful Shutdown and Cleanup</a></h2>
|
|||
|
<p>The code in Listing 20-21 is responding to requests asynchronously through the
|
|||
|
use of a thread pool, as we intended. We get some warnings about the <code>workers</code>,
|
|||
|
<code>id</code>, and <code>thread</code> 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.</p>
|
|||
|
<p>Now we’ll implement the <code>Drop</code> trait to call <code>join</code> on each of the threads in
|
|||
|
the pool so they can finish the requests they’re working on before closing.
|
|||
|
Then we’ll implement a way to tell the threads they should stop 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.</p>
|
|||
|
<h3><a class="header" href="#implementing-the-drop-trait-on-threadpool" id="implementing-the-drop-trait-on-threadpool">Implementing the <code>Drop</code> Trait on <code>ThreadPool</code></a></h3>
|
|||
|
<p>Let’s start with implementing <code>Drop</code> on our thread pool. When the pool is
|
|||
|
dropped, our threads should all join to make sure they finish their work.
|
|||
|
Listing 20-23 shows a first attempt at a <code>Drop</code> implementation; this code won’t
|
|||
|
quite work yet.</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore does_not_compile">impl Drop for ThreadPool {
|
|||
|
fn drop(&mut self) {
|
|||
|
for worker in &mut self.workers {
|
|||
|
println!("Shutting down worker {}", worker.id);
|
|||
|
|
|||
|
worker.thread.join().unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p><span class="caption">Listing 20-23: Joining each thread when the thread pool
|
|||
|
goes out of scope</span></p>
|
|||
|
<p>First, we loop through each of the thread pool <code>workers</code>. We use <code>&mut</code> for
|
|||
|
this because <code>self</code> is a mutable reference, and we also need to be able to
|
|||
|
mutate <code>worker</code>. For each worker, we print a message saying that this
|
|||
|
particular worker is shutting down, and then we call <code>join</code> on that worker’s
|
|||
|
thread. If the call to <code>join</code> fails, we use <code>unwrap</code> to make Rust panic and go
|
|||
|
into an ungraceful shutdown.</p>
|
|||
|
<p>Here is the error we get when we compile this code:</p>
|
|||
|
<pre><code class="language-text">error[E0507]: cannot move out of borrowed content
|
|||
|
--> src/lib.rs:65:13
|
|||
|
|
|
|||
|
65 | worker.thread.join().unwrap();
|
|||
|
| ^^^^^^ cannot move out of borrowed content
|
|||
|
</code></pre>
|
|||
|
<p>The error tells us we can’t call <code>join</code> because we only have a mutable borrow
|
|||
|
of each <code>worker</code> and <code>join</code> takes ownership of its argument. To solve this
|
|||
|
issue, we need to move the thread out of the <code>Worker</code> instance that owns
|
|||
|
<code>thread</code> so <code>join</code> can consume the thread. We did this in Listing 17-15: if
|
|||
|
<code>Worker</code> holds an <code>Option<thread::JoinHandle<()>></code> instead, we can call the
|
|||
|
<code>take</code> method on the <code>Option</code> to move the value out of the <code>Some</code> variant and
|
|||
|
leave a <code>None</code> variant in its place. In other words, a <code>Worker</code> that is running
|
|||
|
will have a <code>Some</code> variant in <code>thread</code>, and when we want to clean up a
|
|||
|
<code>Worker</code>, we’ll replace <code>Some</code> with <code>None</code> so the <code>Worker</code> doesn’t have a
|
|||
|
thread to run.</p>
|
|||
|
<p>So we know we want to update the definition of <code>Worker</code> like this:</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><pre class="playpen"><code class="language-rust">
|
|||
|
<span class="boring">#![allow(unused_variables)]
|
|||
|
</span><span class="boring">fn main() {
|
|||
|
</span><span class="boring">use std::thread;
|
|||
|
</span>struct Worker {
|
|||
|
id: usize,
|
|||
|
thread: Option<thread::JoinHandle<()>>,
|
|||
|
}
|
|||
|
<span class="boring">}
|
|||
|
</span></code></pre></pre>
|
|||
|
<p>Now let’s lean on the compiler to find the other places that need to change.
|
|||
|
Checking this code, we get two errors:</p>
|
|||
|
<pre><code class="language-text">error[E0599]: no method named `join` found for type
|
|||
|
`std::option::Option<std::thread::JoinHandle<()>>` in the current scope
|
|||
|
--> src/lib.rs:65:27
|
|||
|
|
|
|||
|
65 | worker.thread.join().unwrap();
|
|||
|
| ^^^^
|
|||
|
|
|||
|
error[E0308]: mismatched types
|
|||
|
--> src/lib.rs:89:13
|
|||
|
|
|
|||
|
89 | thread,
|
|||
|
| ^^^^^^
|
|||
|
| |
|
|||
|
| expected enum `std::option::Option`, found struct
|
|||
|
`std::thread::JoinHandle`
|
|||
|
| help: try using a variant of the expected type: `Some(thread)`
|
|||
|
|
|
|||
|
= note: expected type `std::option::Option<std::thread::JoinHandle<()>>`
|
|||
|
found type `std::thread::JoinHandle<_>`
|
|||
|
</code></pre>
|
|||
|
<p>Let’s address the second error, which points to the code at the end of
|
|||
|
<code>Worker::new</code>; we need to wrap the <code>thread</code> value in <code>Some</code> when we create a
|
|||
|
new <code>Worker</code>. Make the following changes to fix this error:</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">impl Worker {
|
|||
|
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
|||
|
// --snip--
|
|||
|
|
|||
|
Worker {
|
|||
|
id,
|
|||
|
thread: Some(thread),
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p>The first error is in our <code>Drop</code> implementation. We mentioned earlier that we
|
|||
|
intended to call <code>take</code> on the <code>Option</code> value to move <code>thread</code> out of <code>worker</code>.
|
|||
|
The following changes will do so:</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">impl Drop for ThreadPool {
|
|||
|
fn drop(&mut self) {
|
|||
|
for worker in &mut self.workers {
|
|||
|
println!("Shutting down worker {}", worker.id);
|
|||
|
|
|||
|
if let Some(thread) = worker.thread.take() {
|
|||
|
thread.join().unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p>As discussed in Chapter 17, the <code>take</code> method on <code>Option</code> takes the <code>Some</code>
|
|||
|
variant out and leaves <code>None</code> in its place. We’re using <code>if let</code> to destructure
|
|||
|
the <code>Some</code> and get the thread; then we call <code>join</code> on the thread. If a worker’s
|
|||
|
thread is already <code>None</code>, we know that worker has already had its thread
|
|||
|
cleaned up, so nothing happens in that case.</p>
|
|||
|
<h3><a class="header" href="#signaling-to-the-threads-to-stop-listening-for-jobs" id="signaling-to-the-threads-to-stop-listening-for-jobs">Signaling to the Threads to Stop Listening for Jobs</a></h3>
|
|||
|
<p>With all the changes we’ve made, our code compiles without any warnings. But
|
|||
|
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 <code>Worker</code> instances: at
|
|||
|
the moment, we call <code>join</code>, but that won’t shut down the threads because they
|
|||
|
<code>loop</code> forever looking for jobs. If we try to drop our <code>ThreadPool</code> with our
|
|||
|
current implementation of <code>drop</code>, the main thread will block forever waiting
|
|||
|
for the first thread to finish.</p>
|
|||
|
<p>To fix this problem, we’ll modify the threads so they listen for either a <code>Job</code>
|
|||
|
to run or a signal that they should stop listening and exit the infinite loop.
|
|||
|
Instead of <code>Job</code> instances, our channel will send one of these two enum
|
|||
|
variants.</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><pre class="playpen"><code class="language-rust">
|
|||
|
<span class="boring">#![allow(unused_variables)]
|
|||
|
</span><span class="boring">fn main() {
|
|||
|
</span><span class="boring">struct Job;
|
|||
|
</span>enum Message {
|
|||
|
NewJob(Job),
|
|||
|
Terminate,
|
|||
|
}
|
|||
|
<span class="boring">}
|
|||
|
</span></code></pre></pre>
|
|||
|
<p>This <code>Message</code> enum will either be a <code>NewJob</code> variant that holds the <code>Job</code> the
|
|||
|
thread should run, or it will be a <code>Terminate</code> variant that will cause the
|
|||
|
thread to exit its loop and stop.</p>
|
|||
|
<p>We need to adjust the channel to use values of type <code>Message</code> rather than type
|
|||
|
<code>Job</code>, as shown in Listing 20-24.</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">pub struct ThreadPool {
|
|||
|
workers: Vec<Worker>,
|
|||
|
sender: mpsc::Sender<Message>,
|
|||
|
}
|
|||
|
|
|||
|
// --snip--
|
|||
|
|
|||
|
impl ThreadPool {
|
|||
|
// --snip--
|
|||
|
|
|||
|
pub fn execute<F>(&self, f: F)
|
|||
|
where
|
|||
|
F: FnOnce() + Send + 'static
|
|||
|
{
|
|||
|
let job = Box::new(f);
|
|||
|
|
|||
|
self.sender.send(Message::NewJob(job)).unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// --snip--
|
|||
|
|
|||
|
impl Worker {
|
|||
|
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) ->
|
|||
|
Worker {
|
|||
|
|
|||
|
let thread = thread::spawn(move ||{
|
|||
|
loop {
|
|||
|
let message = receiver.lock().unwrap().recv().unwrap();
|
|||
|
|
|||
|
match message {
|
|||
|
Message::NewJob(job) => {
|
|||
|
println!("Worker {} got a job; executing.", id);
|
|||
|
|
|||
|
job.call_box();
|
|||
|
},
|
|||
|
Message::Terminate => {
|
|||
|
println!("Worker {} was told to terminate.", id);
|
|||
|
|
|||
|
break;
|
|||
|
},
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
Worker {
|
|||
|
id,
|
|||
|
thread: Some(thread),
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p><span class="caption">Listing 20-24: Sending and receiving <code>Message</code> values and
|
|||
|
exiting the loop if a <code>Worker</code> receives <code>Message::Terminate</code></span></p>
|
|||
|
<p>To incorporate the <code>Message</code> enum, we need to change <code>Job</code> to <code>Message</code> in two
|
|||
|
places: the definition of <code>ThreadPool</code> and the signature of <code>Worker::new</code>. The
|
|||
|
<code>execute</code> method of <code>ThreadPool</code> needs to send jobs wrapped in the
|
|||
|
<code>Message::NewJob</code> variant. Then, in <code>Worker::new</code> where a <code>Message</code> is received
|
|||
|
from the channel, the job will be processed if the <code>NewJob</code> variant is
|
|||
|
received, and the thread will break out of the loop if the <code>Terminate</code> variant
|
|||
|
is received.</p>
|
|||
|
<p>With these changes, the code will compile and continue to function in the same
|
|||
|
way as it did after Listing 20-21. But we’ll get a warning because we aren’t
|
|||
|
creating any messages of the <code>Terminate</code> variety. Let’s fix this warning by
|
|||
|
changing our <code>Drop</code> implementation to look like Listing 20-25.</p>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">impl Drop for ThreadPool {
|
|||
|
fn drop(&mut self) {
|
|||
|
println!("Sending terminate message to all workers.");
|
|||
|
|
|||
|
for _ in &mut self.workers {
|
|||
|
self.sender.send(Message::Terminate).unwrap();
|
|||
|
}
|
|||
|
|
|||
|
println!("Shutting down all workers.");
|
|||
|
|
|||
|
for worker in &mut self.workers {
|
|||
|
println!("Shutting down worker {}", worker.id);
|
|||
|
|
|||
|
if let Some(thread) = worker.thread.take() {
|
|||
|
thread.join().unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p><span class="caption">Listing 20-25: Sending <code>Message::Terminate</code> to the
|
|||
|
workers before calling <code>join</code> on each worker thread</span></p>
|
|||
|
<p>We’re now iterating over the workers twice: once to send one <code>Terminate</code>
|
|||
|
message for each worker and once to call <code>join</code> on each worker’s thread. If we
|
|||
|
tried to send a message and <code>join</code> immediately in the same loop, we couldn’t
|
|||
|
guarantee that the worker in the current iteration would be the one to get the
|
|||
|
message from the channel.</p>
|
|||
|
<p>To better understand why we need two separate loops, imagine a scenario with
|
|||
|
two workers. If we used a single loop to iterate through each worker, on the
|
|||
|
first iteration a terminate message would be sent down the channel and <code>join</code>
|
|||
|
called on the first worker’s thread. If that first worker was busy processing a
|
|||
|
request at that moment, the second worker would pick up the terminate message
|
|||
|
from the channel and shut down. We would be left waiting on the first worker to
|
|||
|
shut down, but it never would because the second thread picked up the terminate
|
|||
|
message. Deadlock!</p>
|
|||
|
<p>To prevent this scenario, we first put all of our <code>Terminate</code> messages on the
|
|||
|
channel in one loop; then we join on all the threads in another loop. Each
|
|||
|
worker will stop receiving requests on the channel once it gets a terminate
|
|||
|
message. So, we can be sure that if we send the same number of terminate
|
|||
|
messages as there are workers, each worker will receive a terminate message
|
|||
|
before <code>join</code> is called on its thread.</p>
|
|||
|
<p>To see this code in action, let’s modify <code>main</code> to accept only two requests
|
|||
|
before gracefully shutting down the server, as shown in Listing 20-26.</p>
|
|||
|
<p><span class="filename">Filename: src/bin/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">fn main() {
|
|||
|
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
|||
|
let pool = ThreadPool::new(4);
|
|||
|
|
|||
|
for stream in listener.incoming().take(2) {
|
|||
|
let stream = stream.unwrap();
|
|||
|
|
|||
|
pool.execute(|| {
|
|||
|
handle_connection(stream);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
println!("Shutting down.");
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p><span class="caption">Listing 20-26: Shut down the server after serving two
|
|||
|
requests by exiting the loop</span></p>
|
|||
|
<p>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
|
|||
|
in working order.</p>
|
|||
|
<p>The <code>take</code> method is defined in the <code>Iterator</code> trait and limits the iteration
|
|||
|
to the first two items at most. The <code>ThreadPool</code> will go out of scope at the
|
|||
|
end of <code>main</code>, and the <code>drop</code> implementation will run.</p>
|
|||
|
<p>Start the server with <code>cargo run</code>, and make three requests. The third request
|
|||
|
should error, and in your terminal you should see output similar to this:</p>
|
|||
|
<pre><code class="language-text">$ cargo run
|
|||
|
Compiling hello v0.1.0 (file:///projects/hello)
|
|||
|
Finished dev [unoptimized + debuginfo] target(s) in 1.0 secs
|
|||
|
Running `target/debug/hello`
|
|||
|
Worker 0 got a job; executing.
|
|||
|
Worker 3 got a job; executing.
|
|||
|
Shutting down.
|
|||
|
Sending terminate message to all workers.
|
|||
|
Shutting down all workers.
|
|||
|
Shutting down worker 0
|
|||
|
Worker 1 was told to terminate.
|
|||
|
Worker 2 was told to terminate.
|
|||
|
Worker 0 was told to terminate.
|
|||
|
Worker 3 was told to terminate.
|
|||
|
Shutting down worker 1
|
|||
|
Shutting down worker 2
|
|||
|
Shutting down worker 3
|
|||
|
</code></pre>
|
|||
|
<p>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, and then on the third request, the server stopped accepting
|
|||
|
connections. When the <code>ThreadPool</code> goes out of scope at the end of <code>main</code>, its
|
|||
|
<code>Drop</code> implementation kicks in, and the pool tells all workers to terminate.
|
|||
|
The workers each print a message when they see the terminate message, and then
|
|||
|
the thread pool calls <code>join</code> to shut down each worker thread.</p>
|
|||
|
<p>Notice one interesting aspect of this particular execution: the <code>ThreadPool</code>
|
|||
|
sent the terminate messages down the channel, and before any worker received
|
|||
|
the messages, we tried to join worker 0. Worker 0 had not yet received the
|
|||
|
terminate message, so the main thread blocked waiting for worker 0 to finish.
|
|||
|
In the meantime, each of the workers received the termination messages. When
|
|||
|
worker 0 finished, the main thread waited for the rest of the workers to
|
|||
|
finish. At that point, they had all received the termination message and were
|
|||
|
able to shut down.</p>
|
|||
|
<p>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.</p>
|
|||
|
<p>Here’s the full code for reference:</p>
|
|||
|
<p><span class="filename">Filename: src/bin/main.rs</span></p>
|
|||
|
<pre><code class="language-rust ignore">use hello::ThreadPool;
|
|||
|
|
|||
|
use std::io::prelude::*;
|
|||
|
use std::net::TcpListener;
|
|||
|
use std::net::TcpStream;
|
|||
|
use std::fs;
|
|||
|
use std::thread;
|
|||
|
use std::time::Duration;
|
|||
|
|
|||
|
fn main() {
|
|||
|
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
|
|||
|
let pool = ThreadPool::new(4);
|
|||
|
|
|||
|
for stream in listener.incoming().take(2) {
|
|||
|
let stream = stream.unwrap();
|
|||
|
|
|||
|
pool.execute(|| {
|
|||
|
handle_connection(stream);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
println!("Shutting down.");
|
|||
|
}
|
|||
|
|
|||
|
fn handle_connection(mut stream: TcpStream) {
|
|||
|
let mut buffer = [0; 512];
|
|||
|
stream.read(&mut buffer).unwrap();
|
|||
|
|
|||
|
let get = b"GET / HTTP/1.1\r\n";
|
|||
|
let sleep = b"GET /sleep HTTP/1.1\r\n";
|
|||
|
|
|||
|
let (status_line, filename) = if buffer.starts_with(get) {
|
|||
|
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
|
|||
|
} else if buffer.starts_with(sleep) {
|
|||
|
thread::sleep(Duration::from_secs(5));
|
|||
|
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
|
|||
|
} else {
|
|||
|
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
|
|||
|
};
|
|||
|
|
|||
|
let contents = fs::read_to_string(filename).unwrap();
|
|||
|
|
|||
|
let response = format!("{}{}", status_line, contents);
|
|||
|
|
|||
|
stream.write(response.as_bytes()).unwrap();
|
|||
|
stream.flush().unwrap();
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<p><span class="filename">Filename: src/lib.rs</span></p>
|
|||
|
<pre><pre class="playpen"><code class="language-rust">
|
|||
|
<span class="boring">#![allow(unused_variables)]
|
|||
|
</span><span class="boring">fn main() {
|
|||
|
</span>use std::thread;
|
|||
|
use std::sync::mpsc;
|
|||
|
use std::sync::Arc;
|
|||
|
use std::sync::Mutex;
|
|||
|
|
|||
|
enum Message {
|
|||
|
NewJob(Job),
|
|||
|
Terminate,
|
|||
|
}
|
|||
|
|
|||
|
pub struct ThreadPool {
|
|||
|
workers: Vec<Worker>,
|
|||
|
sender: mpsc::Sender<Message>,
|
|||
|
}
|
|||
|
|
|||
|
trait FnBox {
|
|||
|
fn call_box(self: Box<Self>);
|
|||
|
}
|
|||
|
|
|||
|
impl<F: FnOnce()> FnBox for F {
|
|||
|
fn call_box(self: Box<F>) {
|
|||
|
(*self)()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
type Job = Box<dyn FnBox + Send + 'static>;
|
|||
|
|
|||
|
impl ThreadPool {
|
|||
|
/// Create a new ThreadPool.
|
|||
|
///
|
|||
|
/// The size is the number of threads in the pool.
|
|||
|
///
|
|||
|
/// # Panics
|
|||
|
///
|
|||
|
/// The `new` function will panic if the size is zero.
|
|||
|
pub fn new(size: usize) -> ThreadPool {
|
|||
|
assert!(size > 0);
|
|||
|
|
|||
|
let (sender, receiver) = mpsc::channel();
|
|||
|
|
|||
|
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)));
|
|||
|
}
|
|||
|
|
|||
|
ThreadPool {
|
|||
|
workers,
|
|||
|
sender,
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
pub fn execute<F>(&self, f: F)
|
|||
|
where
|
|||
|
F: FnOnce() + Send + 'static
|
|||
|
{
|
|||
|
let job = Box::new(f);
|
|||
|
|
|||
|
self.sender.send(Message::NewJob(job)).unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
impl Drop for ThreadPool {
|
|||
|
fn drop(&mut self) {
|
|||
|
println!("Sending terminate message to all workers.");
|
|||
|
|
|||
|
for _ in &mut self.workers {
|
|||
|
self.sender.send(Message::Terminate).unwrap();
|
|||
|
}
|
|||
|
|
|||
|
println!("Shutting down all workers.");
|
|||
|
|
|||
|
for worker in &mut self.workers {
|
|||
|
println!("Shutting down worker {}", worker.id);
|
|||
|
|
|||
|
if let Some(thread) = worker.thread.take() {
|
|||
|
thread.join().unwrap();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
struct Worker {
|
|||
|
id: usize,
|
|||
|
thread: Option<thread::JoinHandle<()>>,
|
|||
|
}
|
|||
|
|
|||
|
impl Worker {
|
|||
|
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) ->
|
|||
|
Worker {
|
|||
|
|
|||
|
let thread = thread::spawn(move ||{
|
|||
|
loop {
|
|||
|
let message = receiver.lock().unwrap().recv().unwrap();
|
|||
|
|
|||
|
match message {
|
|||
|
Message::NewJob(job) => {
|
|||
|
println!("Worker {} got a job; executing.", id);
|
|||
|
|
|||
|
job.call_box();
|
|||
|
},
|
|||
|
Message::Terminate => {
|
|||
|
println!("Worker {} was told to terminate.", id);
|
|||
|
|
|||
|
break;
|
|||
|
},
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
Worker {
|
|||
|
id,
|
|||
|
thread: Some(thread),
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
<span class="boring">}
|
|||
|
</span></code></pre></pre>
|
|||
|
<p>We could do more here! If you want to continue enhancing this project, here are
|
|||
|
some ideas:</p>
|
|||
|
<ul>
|
|||
|
<li>Add more documentation to <code>ThreadPool</code> and its public methods.</li>
|
|||
|
<li>Add tests of the library’s functionality.</li>
|
|||
|
<li>Change calls to <code>unwrap</code> to more robust error handling.</li>
|
|||
|
<li>Use <code>ThreadPool</code> to perform some task other than serving web requests.</li>
|
|||
|
<li>Find a thread pool crate on <a href="https://crates.io/">crates.io</a> and implement a
|
|||
|
similar web server using the crate instead. Then compare its API and
|
|||
|
robustness to the thread pool we implemented.</li>
|
|||
|
</ul>
|
|||
|
<h2><a class="header" href="#summary" id="summary">Summary</a></h2>
|
|||
|
<p>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
|
|||
|
welcoming community of other Rustaceans who would love to help you with any
|
|||
|
challenges you encounter on your Rust journey.</p>
|
|||
|
|
|||
|
</main>
|
|||
|
|
|||
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|||
|
<!-- Mobile navigation buttons -->
|
|||
|
|
|||
|
<a rel="prev" href="ch20-02-multithreaded.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
<i class="fa fa-angle-left"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<a rel="next" href="appendix-00.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
<i class="fa fa-angle-right"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
|
|||
|
<div style="clear: both"></div>
|
|||
|
</nav>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|||
|
|
|||
|
<a href="ch20-02-multithreaded.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
<i class="fa fa-angle-left"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<a href="appendix-00.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
<i class="fa fa-angle-right"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
</nav>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<script type="text/javascript">
|
|||
|
window.playpen_copyable = true;
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
|
|||
|
|
|||
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
|
|||
|
<!-- Custom JS scripts -->
|
|||
|
|
|||
|
<script type="text/javascript" src="ferris.js"></script>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
</body>
|
|||
|
</html>
|