book
book copied to clipboard
ch20-03 suggest that implementing drop trait will gracefuly shut down the ThreadPool when CTRL-C in effect.
ch20-03 suggest that implementing drop trait will gracefuly shut down the ThreadPool when CTRL-C in effect. Which is not the case for Linux (tested on ubuntu 18.04). The default action for SIGINT is termination of the process by kernel (so my understanding is that the drop's are not being run at all).
I find it to be a bit misleading.
This seems like a good point. What about extending this section to point out this limitation and then show how to add a signal handler (say, using the signal_hook crate) to finish it up? It seems like this could help bring the final implementation to a more satisfying, complete place -- because it seems a little artificial to leave it where graceful shutdown only occurs by the server shutting itself down after 2 requests.
I'd be happy to take a stab at this, if it's a good idea?
I'd be happy to take a stab at this, if it's a good idea?
This would be too large of a change for us to be able to accept a PR for. Thank you for the offer though!
(I don't know if this is the way I want to go or not, but I wanted to make sure that you didn't waste your time.)
No worries, makes sense!
Just to add an additional data point, I am currently working my way through the book and just encountered this issue on Linux.
I find the approach of just skipping over this unsatisfying.
Especially since actually setting up a system handler in a way that causes drop to run and not have threads block indefinitely, appears to be non-trivial.
I get that this issue may affect only a fraction of the readers. However, something like an aside section with a skip link might still be appropriate here. Especially so since blkerby already offered to write something up in this regard.
I just finished The Book and gave this a try. I'll share my snippet here, probably not an ideal implementation, but it works with book's solution. I hope it helps someone:
use std::sync::mpsc;
use std::time::Duration;
use std::{fs, thread};
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use signal_hook::consts::SIGINT;
use signal_hook::iterator::Signals;
use web_server::ThreadPool;
/// Tutorial from
/// https://doc.rust-lang.org/book/ch20-03-graceful-shutdown-and-cleanup.html
///
/// This is the same implementation from The Book, with the addition
/// of a ctrl+c handler to gracefully stop the server.
///
/// Test triggering 12 parallel requests with
/// seq 1 12 | xargs -I $ -P12 curl localhost:7878
///
/// If you hit ctr+c during the processing, you'll notice the server
/// will finish handling all 12 requests before handling ctrl+c msg,
/// because all requests entered main mpsc channel before ctrl+c.
///
/// That's similar to what you'd expect in cloud, where you usually
/// stop routing requests to your app pod at k8s ingress level first,
/// wait your pod to handle already routed requests, and then it
/// finishes itself.
///
/// If you want the worker threads to finish their current jobs and
/// then stop accepting new requests if ctrl+c was already hit, you
/// can e.g. replace mpsc::channel() by mpsc::sync_channel(0) here
/// and in ThreadPool, but then you'd lose requests already routed
/// to your app (they'll fail with 'connection reset by peer').
fn main() {
let (tcp_sender, receiver) = mpsc::channel();
let signal_sender = tcp_sender.clone();
start_tcp_thread(tcp_sender, "127.0.0.1:7878");
start_signal_thread(signal_sender);
let pool = ThreadPool::new(4);
for message in receiver.iter() {
match message {
Message::Handle(stream) => {
pool.execute(|| handle_connection(stream));
},
Message::Terminate => {
break;
},
}
}
println!("Main thread completed.");
}
enum Message {
Handle(TcpStream),
Terminate,
}
fn start_tcp_thread(sender: mpsc::Sender<Message>, addr: &'static str) {
thread::spawn(move || {
let listener = TcpListener::bind(addr).unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
sender.send(Message::Handle(stream)).unwrap();
}
});
}
fn start_signal_thread(sender: mpsc::Sender<Message>) {
thread::spawn(move || {
let mut signals = Signals::new(&[SIGINT]).unwrap();
for _ in signals.forever() {
sender.send(Message::Terminate).unwrap();
break;
}
});
}
Oh, and thank you for The Book! I'm learning a lot from it.
Here is my code, I think it's better and clear. https://github.com/ssrlive/webserver-trpl/commit/6d1aaf530d308abb4eb3ba65834d193cb143ff2b
I encountered same problem. In my version of code on MacOS the drop is not getting run at all when I press ctrl c