From b24d29f3021e59625d49ff5a40d54ef710841228 Mon Sep 17 00:00:00 2001 From: andres Date: Wed, 23 Oct 2024 18:57:30 -0500 Subject: [PATCH 1/2] chore: Waits for kill processes to exit --- src/client/worker.rs | 88 +++++++++++++++++++++++++--------------- src/error.rs | 2 +- src/server/mod.rs | 40 +++++++++--------- src/server/router/mod.rs | 23 +++++------ 4 files changed, 87 insertions(+), 66 deletions(-) diff --git a/src/client/worker.rs b/src/client/worker.rs index 91ffecf..7b36356 100644 --- a/src/client/worker.rs +++ b/src/client/worker.rs @@ -4,7 +4,13 @@ use crate::{ networking::get_available_sockets, server::FaucetServerConfig, }; -use std::{ffi::OsStr, net::SocketAddr, path::Path, sync::atomic::AtomicBool, time::Duration}; +use std::{ + ffi::OsStr, + net::SocketAddr, + path::Path, + sync::atomic::{AtomicBool, AtomicU32, Ordering}, + time::Duration, +}; use tokio::{process::Child, task::JoinHandle}; use tokio_stream::StreamExt; use tokio_util::codec::{FramedRead, LinesCodec}; @@ -221,56 +227,74 @@ async fn check_if_online(addr: SocketAddr) -> bool { const RECHECK_INTERVAL: Duration = Duration::from_millis(250); pub struct WorkerChild { - _handle: JoinHandle>, + handle: Option>>, stopper: tokio::sync::mpsc::Sender<()>, } impl WorkerChild { - pub fn kill(&self) { + pub async fn kill(&mut self) { let _ = self.stopper.try_send(()); + self.wait_until_done().await; + } + pub async fn wait_until_done(&mut self) { + if let Some(handle) = self.handle.take() { + let _ = handle.await; + } } } fn spawn_worker_task(config: WorkerConfig) -> WorkerChild { let (stopper, mut rx) = tokio::sync::mpsc::channel(1); let handle = tokio::spawn(async move { + let pid = AtomicU32::new(0); loop { - let mut child = config.spawn_process(config); - let pid = child.id().expect("Failed to get plumber worker PID"); - log::info!(target: "faucet", "Starting process {pid} for {target} on port {port}", port = config.addr.port(), target = config.target); - loop { - // Try to connect to the socket - let check_status = check_if_online(config.addr).await; - // If it's online, we can break out of the loop and start serving connections - if check_status { - log::info!(target: "faucet", "{target} is online and ready to serve connections", target = config.target); - config - .is_online - .store(check_status, std::sync::atomic::Ordering::SeqCst); - break; - } - // If it's not online but the child process has exited, we should break out of the loop - // and restart the process - if child.try_wait()?.is_some() { - break; + let child_manage_closure = || async { + let mut child = config.spawn_process(config); + pid.store( + child.id().expect("Failed to get plumber worker PID"), + Ordering::SeqCst, + ); + log::info!(target: "faucet", "Starting process {pid} for {target} on port {port}", port = config.addr.port(), target = config.target, pid = pid.load(Ordering::SeqCst)); + loop { + // Try to connect to the socket + let check_status = check_if_online(config.addr).await; + // If it's online, we can break out of the loop and start serving connections + if check_status { + log::info!(target: "faucet", "{target} is online and ready to serve connections", target = config.target); + config.is_online.store(check_status, Ordering::SeqCst); + break; + } + // If it's not online but the child process has exited, we should break out of the loop + // and restart the process + if child.try_wait()?.is_some() { + break; + } + + tokio::time::sleep(RECHECK_INTERVAL).await; } - - tokio::time::sleep(RECHECK_INTERVAL).await; - } + child.wait().await + }; tokio::select! { - _ = child.wait() => (), - _ = rx.recv() => return FaucetResult::Ok(()), + status = child_manage_closure() => { + config + .is_online + .store(false, std::sync::atomic::Ordering::SeqCst); + log::error!(target: "faucet", "{target}'s process ({}) exited with status {}", pid.load(Ordering::SeqCst), status?, target = config.target); + }, + _ = rx.recv() => break, + } + } + match pid.load(Ordering::SeqCst) { + 0 => (), // If PID is 0 that means the process has not even started + pid => { + log::info!(target: "faucet", "{target}'s process ({pid}) killed", target = config.target) } - let status = child.wait().await?; - config - .is_online - .store(false, std::sync::atomic::Ordering::SeqCst); - log::error!(target: "faucet", "{target}'s process ({}) exited with status {}", pid, status, target = config.target); } + FaucetResult::Ok(()) }); WorkerChild { - _handle: handle, + handle: Some(handle), stopper, } } diff --git a/src/error.rs b/src/error.rs index 66c5874..e015c36 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum FaucetError { InvalidHeaderValues(hyper::header::InvalidHeaderValue), Http(hyper::http::Error), MissingArgument(&'static str), - DuplicateRoute(&'static str), + DuplicateRoute(String), Utf8Coding, BufferCapacity(tokio_tungstenite::tungstenite::error::CapacityError), ProtocolViolation(tokio_tungstenite::tungstenite::error::ProtocolError), diff --git a/src/server/mod.rs b/src/server/mod.rs index fd0c54a..678ab51 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -34,16 +34,16 @@ fn determine_strategy(server_type: WorkerType, strategy: Option) -> St match server_type { WorkerType::Plumber => strategy.unwrap_or_else(|| { - log::info!(target: "faucet", "No load balancing strategy specified. Defaulting to round robin for plumber."); + log::debug!(target: "faucet", "No load balancing strategy specified. Defaulting to round robin for plumber."); Strategy::RoundRobin }), WorkerType::Shiny | WorkerType::QuartoShiny => match strategy { None => { - log::info!(target: "faucet", "No load balancing strategy specified. Defaulting to IP hash for shiny."); + log::debug!(target: "faucet", "No load balancing strategy specified. Defaulting to IP hash for shiny."); Strategy::IpHash }, Some(Strategy::RoundRobin) => { - log::info!(target: "faucet", "Round robin load balancing strategy specified for shiny, switching to IP hash."); + log::debug!(target: "faucet", "Round robin load balancing strategy specified for shiny, switching to IP hash."); Strategy::IpHash }, Some(Strategy::IpHash) => Strategy::IpHash, @@ -84,22 +84,22 @@ impl FaucetServerBuilder { self } pub fn strategy(mut self, strategy: Option) -> Self { - log::info!(target: "faucet", "Using load balancing strategy: {:?}", strategy); + log::debug!(target: "faucet", "Using load balancing strategy: {:?}", strategy); self.strategy = strategy; self } pub fn bind(mut self, bind: SocketAddr) -> Self { - log::info!(target: "faucet", "Will bind to: {}", bind); + log::debug!(target: "faucet", "Will bind to: {}", bind); self.bind = Some(bind); self } pub fn extractor(mut self, extractor: load_balancing::IpExtractor) -> Self { - log::info!(target: "faucet", "Using IP extractor: {:?}", extractor); + log::debug!(target: "faucet", "Using IP extractor: {:?}", extractor); self.extractor = Some(extractor); self } pub fn workers(mut self, n: usize) -> Self { - log::info!(target: "faucet", "Will spawn {} workers", n); + log::debug!(target: "faucet", "Will spawn {} workers", n); self.n_workers = match n.try_into() { Ok(n) => Some(n), Err(_) => { @@ -110,22 +110,22 @@ impl FaucetServerBuilder { self } pub fn server_type(mut self, server_type: WorkerType) -> Self { - log::info!(target: "faucet", "Using worker type: {:?}", server_type); + log::debug!(target: "faucet", "Using worker type: {:?}", server_type); self.server_type = Some(server_type); self } pub fn workdir(mut self, workdir: impl AsRef) -> Self { - log::info!(target: "faucet", "Using workdir: {:?}", workdir.as_ref()); + log::debug!(target: "faucet", "Using workdir: {:?}", workdir.as_ref()); self.workdir = Some(workdir.as_ref().into()); self } pub fn rscript(mut self, rscript: impl AsRef) -> Self { - log::info!(target: "faucet", "Using Rscript command: {:?}", rscript.as_ref()); + log::debug!(target: "faucet", "Using Rscript command: {:?}", rscript.as_ref()); self.rscript = Some(rscript.as_ref().into()); self } pub fn quarto(mut self, quarto: impl AsRef) -> Self { - log::info!(target: "faucet", "Using quarto command: {:?}", quarto.as_ref()); + log::debug!(target: "faucet", "Using quarto command: {:?}", quarto.as_ref()); self.quarto = Some(quarto.as_ref().into()); self } @@ -140,27 +140,27 @@ impl FaucetServerBuilder { let strategy = determine_strategy(server_type, self.strategy); let bind = self.bind; let n_workers = self.n_workers.unwrap_or_else(|| { - log::info!(target: "faucet", "No number of workers specified. Defaulting to the number of logical cores."); + log::debug!(target: "faucet", "No number of workers specified. Defaulting to the number of logical cores."); num_cpus::get().try_into().expect("num_cpus::get() returned 0") }); let workdir = self.workdir .map(|wd| leak!(wd, Path)) .unwrap_or_else(|| { - log::info!(target: "faucet", "No workdir specified. Defaulting to the current directory."); + log::debug!(target: "faucet", "No workdir specified. Defaulting to the current directory."); Path::new(".") }); let rscript = self.rscript.map(|wd| leak!(wd, OsStr)).unwrap_or_else(|| { - log::info!(target: "faucet", "No Rscript command specified. Defaulting to `Rscript`."); + log::debug!(target: "faucet", "No Rscript command specified. Defaulting to `Rscript`."); OsStr::new("Rscript") }); let extractor = self.extractor.unwrap_or_else(|| { - log::info!(target: "faucet", "No IP extractor specified. Defaulting to client address."); + log::debug!(target: "faucet", "No IP extractor specified. Defaulting to client address."); load_balancing::IpExtractor::ClientAddr }); let app_dir = self.app_dir.map(|app_dir| leak!(app_dir, str)); let qmd = self.qmd.map(|qmd| leak!(qmd, Path)); let quarto = self.quarto.map(|qmd| leak!(qmd, OsStr)).unwrap_or_else(|| { - log::info!(target: "faucet", "No quarto command specified. Defaulting to `quarto`."); + log::debug!(target: "faucet", "No quarto command specified. Defaulting to `quarto`."); OsStr::new("quarto") }); Ok(FaucetServerConfig { @@ -200,7 +200,7 @@ pub struct FaucetServerConfig { impl FaucetServerConfig { pub async fn run(self, shutdown: ShutdownSignal) -> FaucetResult<()> { - let workers = Workers::new(self, "").await?; + let mut workers = Workers::new(self, "").await?; let targets = workers.get_workers_config(); let load_balancer = LoadBalancer::new(self.strategy, self.extractor, &targets)?; let bind = self.bind.ok_or(FaucetError::MissingArgument("bind"))?; @@ -253,10 +253,10 @@ impl FaucetServerConfig { } // Kill child process - workers.workers.iter().for_each(|w| { + for w in &mut workers.workers { log::info!(target: w.config.target, "Killing child process"); - w.child.kill() - }); + w.child.kill().await; + } FaucetResult::Ok(()) } diff --git a/src/server/router/mod.rs b/src/server/router/mod.rs index c2f87d9..c9bbd03 100644 --- a/src/server/router/mod.rs +++ b/src/server/router/mod.rs @@ -47,7 +47,7 @@ pub struct RouterConfig { #[derive(Copy, Clone)] struct RouterService { - routes: &'static [&'static str], + routes: &'static [String], clients: &'static [FaucetServerService], } @@ -99,7 +99,7 @@ impl Service> for RouterService { ) -> Result { let mut client = None; for i in 0..self.routes.len() { - let route = self.routes[i]; + let route = &self.routes[i]; if let Some(new_uri) = strip_prefix(req.uri(), route) { client = Some(&self.clients[i]); *req.uri_mut() = new_uri; @@ -128,11 +128,10 @@ impl RouterConfig { let mut clients = Vec::with_capacity(self.route.len()); let mut routes_set = HashSet::with_capacity(self.route.len()); for route_conf in self.route.into_iter() { - let route: &'static str = route_conf.route.leak(); - if !routes_set.insert(route) { - return Err(FaucetError::DuplicateRoute(route)); + let route = route_conf.route.as_str(); + if !routes_set.insert(route.to_owned()) { + return Err(FaucetError::DuplicateRoute(route_conf.route)); } - routes.push(route); let (client, workers) = FaucetServerBuilder::new() .workdir(route_conf.config.workdir) .server_type(route_conf.config.server_type) @@ -146,6 +145,7 @@ impl RouterConfig { .build()? .extract_service(&format!("[{route}]::")) .await?; + routes.push(route_conf.route); all_workers.push(workers); clients.push(client); } @@ -165,7 +165,7 @@ impl RouterConfig { addr: SocketAddr, shutdown: ShutdownSignal, ) -> FaucetResult<()> { - let (service, all_workers) = self.into_service(rscript, quarto, ip_from).await?; + let (service, mut all_workers) = self.into_service(rscript, quarto, ip_from).await?; // Bind to the port and listen for incoming TCP connections let listener = TcpListener::bind(addr).await?; log::info!(target: "faucet", "Listening on http://{}", addr); @@ -208,12 +208,9 @@ impl RouterConfig { } // Kill child process - all_workers.iter().for_each(|workers| { - workers.workers.iter().for_each(|w| { - log::info!(target: w.config.target, "Killing child process"); - w.child.kill(); - }); - }); + for w in all_workers.iter_mut().flat_map(|ws| &mut ws.workers) { + w.child.kill().await; + } FaucetResult::Ok(()) } From 60030f4ff18021042ee04d83e209b4742387b996 Mon Sep 17 00:00:00 2001 From: andres Date: Wed, 23 Oct 2024 18:59:09 -0500 Subject: [PATCH 2/2] chore: Cargo update --- Cargo.lock | 97 +++++++++++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 604c57d..ba38f88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -131,9 +131,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -149,9 +149,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -323,24 +323,24 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -349,21 +349,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", @@ -406,9 +406,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" @@ -501,9 +501,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -563,9 +563,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "lock_api" @@ -634,21 +634,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parking_lot" @@ -685,12 +682,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -702,9 +693,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -800,18 +791,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", @@ -880,9 +871,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -891,18 +882,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", @@ -911,9 +902,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes",