Merge pull request #312 from reem/acceptor-pool
feat(server): Rewrite the accept loop into a custom thread pool.
This commit is contained in:
		| @@ -13,6 +13,7 @@ fn hello(_: Request, res: Response) { | |||||||
| } | } | ||||||
|  |  | ||||||
| fn main() { | fn main() { | ||||||
|     hyper::Server::http(Ipv4Addr(127, 0, 0, 1), 3000).listen(hello).unwrap(); |     let _listening = hyper::Server::http(Ipv4Addr(127, 0, 0, 1), 3000) | ||||||
|  |         .listen(hello).unwrap(); | ||||||
|     println!("Listening on http://127.0.0.1:3000"); |     println!("Listening on http://127.0.0.1:3000"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -51,7 +51,6 @@ fn echo(mut req: Request, mut res: Response) { | |||||||
|  |  | ||||||
| fn main() { | fn main() { | ||||||
|     let server = Server::http(Ipv4Addr(127, 0, 0, 1), 1337); |     let server = Server::http(Ipv4Addr(127, 0, 0, 1), 1337); | ||||||
|     let mut listening = server.listen(echo).unwrap(); |     let _guard = server.listen(echo).unwrap(); | ||||||
|     println!("Listening on http://127.0.0.1:1337"); |     println!("Listening on http://127.0.0.1:1337"); | ||||||
|     listening.await(); |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| #![feature(core, collections, hash, io, os, path, std_misc, | #![feature(core, collections, hash, io, os, path, std_misc, | ||||||
|            slicing_syntax, box_syntax)] |            slicing_syntax, box_syntax, unsafe_destructor)] | ||||||
| #![deny(missing_docs)] | #![deny(missing_docs)] | ||||||
| #![cfg_attr(test, deny(warnings))] | #![cfg_attr(test, deny(warnings))] | ||||||
| #![cfg_attr(test, feature(alloc, test))] | #![cfg_attr(test, feature(alloc, test))] | ||||||
| @@ -130,12 +130,16 @@ extern crate "rustc-serialize" as serialize; | |||||||
| extern crate time; | extern crate time; | ||||||
| extern crate url; | extern crate url; | ||||||
| extern crate openssl; | extern crate openssl; | ||||||
| #[macro_use] extern crate log; |  | ||||||
| #[cfg(test)] extern crate test; |  | ||||||
| extern crate "unsafe-any" as uany; | extern crate "unsafe-any" as uany; | ||||||
| extern crate cookie; | extern crate cookie; | ||||||
| extern crate unicase; | extern crate unicase; | ||||||
|  |  | ||||||
|  | #[macro_use] | ||||||
|  | extern crate log; | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | extern crate test; | ||||||
|  |  | ||||||
| pub use std::old_io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; | pub use std::old_io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; | ||||||
| pub use mimewrapper::mime; | pub use mimewrapper::mime; | ||||||
| pub use url::Url; | pub use url::Url; | ||||||
|   | |||||||
							
								
								
									
										95
									
								
								src/server/acceptor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/server/acceptor.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | use std::thread::{Thread, JoinGuard}; | ||||||
|  | use std::sync::Arc; | ||||||
|  | use std::sync::mpsc; | ||||||
|  | use net::NetworkAcceptor; | ||||||
|  |  | ||||||
|  | pub struct AcceptorPool<A: NetworkAcceptor> { | ||||||
|  |     acceptor: A | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<A: NetworkAcceptor> AcceptorPool<A> { | ||||||
|  |     /// Create a thread pool to manage the acceptor. | ||||||
|  |     pub fn new(acceptor: A) -> AcceptorPool<A> { | ||||||
|  |         AcceptorPool { acceptor: acceptor } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Runs the acceptor pool. Blocks until the acceptors are closed. | ||||||
|  |     /// | ||||||
|  |     /// ## Panics | ||||||
|  |     /// | ||||||
|  |     /// Panics if threads == 0. | ||||||
|  |     pub fn accept<F: Fn(A::Stream) + Send + Sync>(self, | ||||||
|  |                                                   work: F, | ||||||
|  |                                                   threads: usize) -> JoinGuard<'static, ()> { | ||||||
|  |         assert!(threads != 0, "Can't accept on 0 threads."); | ||||||
|  |  | ||||||
|  |         // Replace with &F when Send changes land. | ||||||
|  |         let work = Arc::new(work); | ||||||
|  |  | ||||||
|  |         let (super_tx, supervisor_rx) = mpsc::channel(); | ||||||
|  |  | ||||||
|  |         let spawn = | ||||||
|  |             move || spawn_with(super_tx.clone(), work.clone(), self.acceptor.clone()); | ||||||
|  |  | ||||||
|  |         // Go | ||||||
|  |         for _ in 0..threads { spawn() } | ||||||
|  |  | ||||||
|  |         // Spawn the supervisor | ||||||
|  |         Thread::scoped(move || for () in supervisor_rx.iter() { spawn() }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn spawn_with<A, F>(supervisor: mpsc::Sender<()>, work: Arc<F>, mut acceptor: A) | ||||||
|  | where A: NetworkAcceptor, | ||||||
|  |       F: Fn(<A as NetworkAcceptor>::Stream) + Send + Sync { | ||||||
|  |     use std::old_io::EndOfFile; | ||||||
|  |  | ||||||
|  |     Thread::spawn(move || { | ||||||
|  |         let sentinel = Sentinel::new(supervisor, ()); | ||||||
|  |  | ||||||
|  |         loop { | ||||||
|  |             match acceptor.accept() { | ||||||
|  |                 Ok(stream) => work(stream), | ||||||
|  |                 Err(ref e) if e.kind == EndOfFile => { | ||||||
|  |                     debug!("Server closed."); | ||||||
|  |                     sentinel.cancel(); | ||||||
|  |                     return; | ||||||
|  |                 }, | ||||||
|  |  | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Connection failed: {}", e); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct Sentinel<T: Send> { | ||||||
|  |     value: Option<T>, | ||||||
|  |     supervisor: mpsc::Sender<T>, | ||||||
|  |     active: bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T: Send> Sentinel<T> { | ||||||
|  |     fn new(channel: mpsc::Sender<T>, data: T) -> Sentinel<T> { | ||||||
|  |         Sentinel { | ||||||
|  |             value: Some(data), | ||||||
|  |             supervisor: channel, | ||||||
|  |             active: true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn cancel(mut self) { self.active = false; } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[unsafe_destructor] | ||||||
|  | impl<T: Send> Drop for Sentinel<T> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         // If we were cancelled, get out of here. | ||||||
|  |         if !self.active { return; } | ||||||
|  |  | ||||||
|  |         // Respawn ourselves | ||||||
|  |         let _ = self.supervisor.send(self.value.take().unwrap()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -1,10 +1,8 @@ | |||||||
| //! HTTP Server | //! HTTP Server | ||||||
| use std::old_io::{Listener, EndOfFile, BufferedReader, BufferedWriter}; | use std::old_io::{Listener, BufferedReader, BufferedWriter}; | ||||||
| use std::old_io::net::ip::{IpAddr, Port, SocketAddr}; | use std::old_io::net::ip::{IpAddr, Port, SocketAddr}; | ||||||
| use std::os; | use std::os; | ||||||
| use std::sync::{Arc, TaskPool}; | use std::thread::JoinGuard; | ||||||
| use std::thread::{Builder, JoinGuard}; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pub use self::request::Request; | pub use self::request::Request; | ||||||
| pub use self::response::Response; | pub use self::response::Response; | ||||||
| @@ -19,9 +17,13 @@ use net::{NetworkListener, NetworkStream, NetworkAcceptor, | |||||||
|           HttpAcceptor, HttpListener}; |           HttpAcceptor, HttpListener}; | ||||||
| use version::HttpVersion::{Http10, Http11}; | use version::HttpVersion::{Http10, Http11}; | ||||||
|  |  | ||||||
|  | use self::acceptor::AcceptorPool; | ||||||
|  |  | ||||||
| pub mod request; | pub mod request; | ||||||
| pub mod response; | pub mod response; | ||||||
|  |  | ||||||
|  | mod acceptor; | ||||||
|  |  | ||||||
| /// A server can listen on a TCP socket. | /// A server can listen on a TCP socket. | ||||||
| /// | /// | ||||||
| /// Once listening, it will create a `Request`/`Response` pair for each | /// Once listening, it will create a `Request`/`Response` pair for each | ||||||
| @@ -71,17 +73,27 @@ S: NetworkStream + Clone + Send> Server<L> { | |||||||
|         let acceptor = try!(self.listener.listen((self.ip, self.port))); |         let acceptor = try!(self.listener.listen((self.ip, self.port))); | ||||||
|         let socket = try!(acceptor.socket_name()); |         let socket = try!(acceptor.socket_name()); | ||||||
|  |  | ||||||
|         let mut captured = acceptor.clone(); |  | ||||||
|         let guard = Builder::new().name("hyper acceptor".to_string()).scoped(move || { |  | ||||||
|             let handler = Arc::new(handler); |  | ||||||
|         debug!("threads = {:?}", threads); |         debug!("threads = {:?}", threads); | ||||||
|             let pool = TaskPool::new(threads); |         let pool = AcceptorPool::new(acceptor.clone()); | ||||||
|             for conn in captured.incoming() { |         let work = move |stream| handle_connection(stream, &handler); | ||||||
|                 match conn { |  | ||||||
|                     Ok(mut stream) => { |         Ok(Listening { | ||||||
|  |             _guard: pool.accept(work, threads), | ||||||
|  |             socket: socket, | ||||||
|  |             acceptor: acceptor | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Binds to a socket and starts handling connections. | ||||||
|  |     pub fn listen<H: Handler>(self, handler: H) -> HttpResult<Listening<L::Acceptor>> { | ||||||
|  |         self.listen_threads(handler, os::num_cpus() * 5 / 4) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn handle_connection<S, H>(mut stream: S, handler: &H) | ||||||
|  | where S: NetworkStream + Clone, H: Handler { | ||||||
|     debug!("Incoming stream"); |     debug!("Incoming stream"); | ||||||
|                         let handler = handler.clone(); |  | ||||||
|                         pool.execute(move || { |  | ||||||
|     let addr = match stream.peer_name() { |     let addr = match stream.peer_name() { | ||||||
|         Ok(addr) => addr, |         Ok(addr) => addr, | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
| @@ -89,6 +101,7 @@ S: NetworkStream + Clone + Send> Server<L> { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let mut rdr = BufferedReader::new(stream.clone()); |     let mut rdr = BufferedReader::new(stream.clone()); | ||||||
|     let mut wrt = BufferedWriter::new(stream); |     let mut wrt = BufferedWriter::new(stream); | ||||||
|  |  | ||||||
| @@ -117,51 +130,17 @@ S: NetworkStream + Clone + Send> Server<L> { | |||||||
|         handler.handle(req, res); |         handler.handle(req, res); | ||||||
|         debug!("keep_alive = {:?}", keep_alive); |         debug!("keep_alive = {:?}", keep_alive); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|                         }); |  | ||||||
|                     }, |  | ||||||
|                     Err(ref e) if e.kind == EndOfFile => { |  | ||||||
|                         debug!("server closed"); |  | ||||||
|                         break; |  | ||||||
|                     }, |  | ||||||
|                     Err(e) => { |  | ||||||
|                         error!("Connection failed: {}", e); |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         Ok(Listening { |  | ||||||
|             acceptor: acceptor, |  | ||||||
|             guard: Some(guard), |  | ||||||
|             socket: socket, |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Binds to a socket and starts handling connections. |  | ||||||
|     pub fn listen<H: Handler>(self, handler: H) -> HttpResult<Listening<L::Acceptor>> { |  | ||||||
|         self.listen_threads(handler, os::num_cpus() * 5 / 4) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A listening server, which can later be closed. | /// A listening server, which can later be closed. | ||||||
| pub struct Listening<A = HttpAcceptor> { | pub struct Listening<A = HttpAcceptor> { | ||||||
|     acceptor: A, |     acceptor: A, | ||||||
|     guard: Option<JoinGuard<'static, ()>>, |     _guard: JoinGuard<'static, ()>, | ||||||
|     /// The socket addresses that the server is bound to. |     /// The socket addresses that the server is bound to. | ||||||
|     pub socket: SocketAddr, |     pub socket: SocketAddr, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<A: NetworkAcceptor> Listening<A> { | impl<A: NetworkAcceptor> Listening<A> { | ||||||
|     /// Causes the current thread to wait for this listening to complete. |  | ||||||
|     pub fn await(&mut self) { |  | ||||||
|         if let Some(guard) = self.guard.take() { |  | ||||||
|             let _ = guard.join(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Stop the server from listening to its socket address. |     /// Stop the server from listening to its socket address. | ||||||
|     pub fn close(&mut self) -> HttpResult<()> { |     pub fn close(&mut self) -> HttpResult<()> { | ||||||
|         debug!("closing server"); |         debug!("closing server"); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user