Грациозное завершение работы сервера
Серверные соединения hyper имеют возможность инициировать грациозное завершение работы. Часто возникает необходимость координировать грациозное завершение работы всех активных соединений. Эту задачу мы и рассмотрим в данном руководстве.
Грациозное завершение работы — это когда соединение перестает принимать новые запросы, позволяя при этом завершиться текущим выполняющимся запросам.
Для этого нам понадобится несколько компонентов:
- Сигнал для начала завершения работы.
- Цикл принятия (accept loop) для обработки вновь поступающих соединений.
- Наблюдатель (watcher) для координации завершения работы.
Определение сигнала завершения
Вы можете использовать любой механизм для сигнализации о начале грациозного завершения работы. Это может быть обработчик сигналов процесса, таймер, специальный HTTP-запрос или что-либо еще.
В этом руководстве мы будем использовать обработчик сигнала CTRL+C. В Tokio есть простая поддержка для его создания:
extern crate tokio; async fn shutdown_signal() { // Ждем сигнала CTRL+C tokio::signal::ctrl_c() .await .expect("failed to install CTRL+C signal handler"); } fn main() {}
Модификация цикла принятия соединений сервера
Нестабильно: Код, обсуждаемый в этом руководстве, находится в
hyper-util, который не так стабилен, как код вhyper. Он готов к использованию в production, но изменения могут происходить чаще.
Мы предполагаем, что у вас есть цикл принятия соединений для вашего сервера, аналогичный показанному в руководстве Hello World. Поэтому мы просто модифицируем его здесь:
extern crate hyper; extern crate http_body_util; extern crate hyper_util; extern crate tokio; mod no_run { use std::convert::Infallible; use std::net::SocketAddr; use http_body_util::Full; use hyper::body::Bytes; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::{Request, Response}; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; async fn shutdown_signal() {} async fn hello( _: Request<hyper::body::Incoming>, ) -> Result<Response<Full<Bytes>>, Infallible> { Ok(Response::new(Full::new(Bytes::from("Hello World!")))) } async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).await?; // Указываем настройки HTTP (http1, http2, auto — все работает) let mut http = http1::Builder::new(); // Наблюдатель для грациозного завершения let graceful = hyper_util::server::graceful::GracefulShutdown::new(); // Когда этот сигнал завершится, начинаем завершение работы let mut signal = std::pin::pin!(shutdown_signal()); // Наш цикл принятия соединений сервера loop { tokio::select! { Ok((stream, _addr)) = listener.accept() => { let io = TokioIo::new(stream); let conn = http.serve_connection(io, service_fn(hello)); // Наблюдаем за этим соединением let fut = graceful.watch(conn); tokio::spawn(async move { if let Err(e) = fut.await { eprintln!("Error serving connection: {:?}", e); } }); }, _ = &mut signal => { drop(listener); eprintln!("graceful shutdown signal received"); // Останавливаем цикл принятия break; } } } // Теперь начинаем завершение работы и ждем, пока все соединения завершатся // Опционально: запускаем таймаут, чтобы ограничить время ожидания. tokio::select! { _ = graceful.shutdown() => { eprintln!("all connections gracefully closed"); }, _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { eprintln!("timed out wait for all connections to close"); } } Ok(()) } } fn main() {}