| { | ||
| "git": { | ||
| "sha1": "98df02d7a4a638b3bc76a01f41966dc83c275103" | ||
| "sha1": "64834ec7018de92fadf00d053b565263913439c1" | ||
| }, | ||
| "path_in_vcs": "tokio" | ||
| } |
+56
-50
@@ -112,5 +112,5 @@ # This file is automatically @generated by Cargo. | ||
| name = "bitflags" | ||
| version = "2.11.0" | ||
| version = "2.11.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" | ||
| checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" | ||
@@ -137,5 +137,5 @@ [[package]] | ||
| name = "cc" | ||
| version = "1.2.59" | ||
| version = "1.2.61" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" | ||
| checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" | ||
| dependencies = [ | ||
@@ -406,5 +406,5 @@ "find-msvc-tools", | ||
| name = "hashbrown" | ||
| version = "0.16.1" | ||
| version = "0.17.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" | ||
| checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" | ||
@@ -425,8 +425,8 @@ [[package]] | ||
| name = "indexmap" | ||
| version = "2.13.1" | ||
| version = "2.14.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" | ||
| checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" | ||
| dependencies = [ | ||
| "equivalent", | ||
| "hashbrown 0.16.1", | ||
| "hashbrown 0.17.0", | ||
| "serde", | ||
@@ -438,5 +438,5 @@ "serde_core", | ||
| name = "io-uring" | ||
| version = "0.7.11" | ||
| version = "0.7.12" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" | ||
| checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62" | ||
| dependencies = [ | ||
@@ -456,5 +456,5 @@ "bitflags", | ||
| name = "js-sys" | ||
| version = "0.3.94" | ||
| version = "0.3.97" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" | ||
| checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" | ||
| dependencies = [ | ||
@@ -481,5 +481,5 @@ "cfg-if", | ||
| name = "libc" | ||
| version = "0.2.184" | ||
| version = "0.2.186" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" | ||
| checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" | ||
@@ -838,5 +838,5 @@ [[package]] | ||
| name = "rand" | ||
| version = "0.9.2" | ||
| version = "0.9.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" | ||
| checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" | ||
| dependencies = [ | ||
@@ -1096,13 +1096,4 @@ "rand_chacha", | ||
| name = "tokio" | ||
| version = "1.51.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" | ||
| version = "1.51.2" | ||
| dependencies = [ | ||
| "pin-project-lite", | ||
| ] | ||
| [[package]] | ||
| name = "tokio" | ||
| version = "1.51.1" | ||
| dependencies = [ | ||
| "async-stream", | ||
@@ -1140,2 +1131,11 @@ "backtrace", | ||
| [[package]] | ||
| name = "tokio" | ||
| version = "1.52.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" | ||
| dependencies = [ | ||
| "pin-project-lite", | ||
| ] | ||
| [[package]] | ||
| name = "tokio-macros" | ||
@@ -1159,3 +1159,3 @@ version = "2.7.0" | ||
| "pin-project-lite", | ||
| "tokio 1.51.0", | ||
| "tokio 1.52.1", | ||
| ] | ||
@@ -1170,3 +1170,3 @@ | ||
| "futures-core", | ||
| "tokio 1.51.0", | ||
| "tokio 1.52.1", | ||
| "tokio-stream", | ||
@@ -1186,3 +1186,3 @@ ] | ||
| "pin-project-lite", | ||
| "tokio 1.51.0", | ||
| "tokio 1.52.1", | ||
| ] | ||
@@ -1312,7 +1312,7 @@ | ||
| name = "wasip2" | ||
| version = "1.0.2+wasi-0.2.9" | ||
| version = "1.0.3+wasi-0.2.9" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" | ||
| checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" | ||
| dependencies = [ | ||
| "wit-bindgen", | ||
| "wit-bindgen 0.57.1", | ||
| ] | ||
@@ -1326,3 +1326,3 @@ | ||
| dependencies = [ | ||
| "wit-bindgen", | ||
| "wit-bindgen 0.51.0", | ||
| ] | ||
@@ -1332,5 +1332,5 @@ | ||
| name = "wasm-bindgen" | ||
| version = "0.2.117" | ||
| version = "0.2.120" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" | ||
| checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" | ||
| dependencies = [ | ||
@@ -1346,5 +1346,5 @@ "cfg-if", | ||
| name = "wasm-bindgen-futures" | ||
| version = "0.4.67" | ||
| version = "0.4.70" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" | ||
| checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" | ||
| dependencies = [ | ||
@@ -1357,5 +1357,5 @@ "js-sys", | ||
| name = "wasm-bindgen-macro" | ||
| version = "0.2.117" | ||
| version = "0.2.120" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" | ||
| checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" | ||
| dependencies = [ | ||
@@ -1368,5 +1368,5 @@ "quote", | ||
| name = "wasm-bindgen-macro-support" | ||
| version = "0.2.117" | ||
| version = "0.2.120" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" | ||
| checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" | ||
| dependencies = [ | ||
@@ -1382,5 +1382,5 @@ "bumpalo", | ||
| name = "wasm-bindgen-shared" | ||
| version = "0.2.117" | ||
| version = "0.2.120" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" | ||
| checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" | ||
| dependencies = [ | ||
@@ -1392,5 +1392,5 @@ "unicode-ident", | ||
| name = "wasm-bindgen-test" | ||
| version = "0.3.67" | ||
| version = "0.3.70" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "941c102b3f0c15b6d72a53205e09e6646aafcf2991e18412cc331dbac1806bc0" | ||
| checksum = "29826f9d9ecaa314c480d376b276d1c790e6cb6a4681fab8532da69cbabf977d" | ||
| dependencies = [ | ||
@@ -1415,5 +1415,5 @@ "async-trait", | ||
| name = "wasm-bindgen-test-macro" | ||
| version = "0.3.67" | ||
| version = "0.3.70" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a26bd6570f39bb1440fd8f01b63461faaf2a3f6078a508e4e54efa99363108d2" | ||
| checksum = "c610311887f9e6599a546d278d12d69dfd3a3e92639b2129e4b11ad6cf1961d6" | ||
| dependencies = [ | ||
@@ -1427,5 +1427,5 @@ "proc-macro2", | ||
| name = "wasm-bindgen-test-shared" | ||
| version = "0.2.117" | ||
| version = "0.2.120" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1c29582b14d5bf030b02fa232b9b57faf2afc322d2c61964dd80bad02bf76207" | ||
| checksum = "60238e5b4b1b295701d6f9a66d2a126fe19990348f5fb9dae3b623a370119d94" | ||
@@ -1509,2 +1509,8 @@ [[package]] | ||
| [[package]] | ||
| name = "wit-bindgen" | ||
| version = "0.57.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" | ||
| [[package]] | ||
| name = "wit-bindgen-core" | ||
@@ -1511,0 +1517,0 @@ version = "0.51.0" |
+1
-1
@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "tokio" | ||
| version = "1.51.1" | ||
| version = "1.51.2" | ||
| authors = ["Tokio Contributors <team@tokio.rs>"] | ||
@@ -19,0 +19,0 @@ build = false |
+1
-1
@@ -63,3 +63,3 @@ *[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)* | ||
| [dependencies] | ||
| tokio = { version = "1.51.1", features = ["full"] } | ||
| tokio = { version = "1.51.2", features = ["full"] } | ||
| ``` | ||
@@ -66,0 +66,0 @@ Then, on your main.rs: |
@@ -37,9 +37,7 @@ #![cfg_attr( | ||
| /// store the last scheduled task. This can improve certain usage patterns, | ||
| /// especially message passing between tasks. | ||
| /// especially message passing between tasks. However, this LIFO slot is not | ||
| /// currently stealable. | ||
| /// | ||
| /// In Tokio versions before 1.51, tasks in the LIFO slot could not be | ||
| /// stolen, which could cause issues in applications with long poll times. | ||
| /// As a stop-gap, this unstable option lets users disable the LIFO task. | ||
| /// Now that the LIFO slot is stealable, we may remove this option in a | ||
| /// future version. | ||
| /// Eventually, the LIFO slot **will** become stealable, however as a | ||
| /// stop-gap, this unstable option lets users disable the LIFO task. | ||
| pub(crate) disable_lifo_slot: bool, | ||
@@ -46,0 +44,0 @@ |
@@ -370,4 +370,4 @@ //! The Tokio runtime. | ||
| //! disabled using the [`disable_lifo_slot`] setting. The lifo slot is separate | ||
| //! from the local queue, and is stolen from by other worker threads only when | ||
| //! a worker's local queue has been drained. | ||
| //! from the local queue, so other worker threads cannot steal the task in the | ||
| //! lifo slot. | ||
| //! | ||
@@ -374,0 +374,0 @@ //! When a task is woken from a thread that is not a worker thread, then the |
@@ -55,9 +55,2 @@ //! Run-queue structures to support a work-stealing scheduler | ||
| /// When a task is scheduled from a worker, it is stored in this slot. The | ||
| /// worker will check this slot for a task **before** checking the run | ||
| /// queue. This effectively results in the **last** scheduled task to be run | ||
| /// next (LIFO). This is an optimization for improving locality which | ||
| /// benefits message passing patterns and helps to reduce latency. | ||
| lifo: task::AtomicNotified<T>, | ||
| /// Elements | ||
@@ -103,3 +96,2 @@ buffer: Box<[UnsafeCell<MaybeUninit<task::Notified<T>>>; LOCAL_QUEUE_CAPACITY]>, | ||
| tail: AtomicUnsignedShort::new(0), | ||
| lifo: task::AtomicNotified::empty(), | ||
| buffer: make_fixed_size(buffer.into_boxed_slice()), | ||
@@ -121,6 +113,5 @@ }); | ||
| let (_, head) = unpack(self.inner.head.load(Acquire)); | ||
| let lifo = self.inner.lifo.is_some() as usize; | ||
| // safety: this is the **only** thread that updates this cell. | ||
| let tail = unsafe { self.inner.tail.unsync_load() }; | ||
| len(head, tail) + lifo | ||
| len(head, tail) | ||
| } | ||
@@ -403,15 +394,2 @@ | ||
| } | ||
| /// Pushes a task to the LIFO slot, returning the task previously in the | ||
| /// LIFO slot (if there was one). | ||
| pub(crate) fn push_lifo(&self, task: task::Notified<T>) -> Option<task::Notified<T>> { | ||
| self.inner.lifo.swap(Some(task)) | ||
| } | ||
| /// Pops the task currently held in the LIFO slot, if there is one; | ||
| /// otherwise, returns `None`. | ||
| pub(crate) fn pop_lifo(&self) -> Option<task::Notified<T>> { | ||
| // LIFO-suction! | ||
| self.inner.lifo.take() | ||
| } | ||
| } | ||
@@ -424,4 +402,3 @@ | ||
| let tail = self.0.tail.load(Acquire); | ||
| let lifo = self.0.lifo.is_some() as usize; | ||
| len(head, tail) + lifo | ||
| len(head, tail) | ||
| } | ||
@@ -461,10 +438,4 @@ | ||
| if n == 0 { | ||
| // If no tasks were stolen, let's see if there's one in the LIFO | ||
| // slot. | ||
| let lifo = self.0.lifo.take(); | ||
| if lifo.is_some() { | ||
| dst_stats.incr_steal_count(1); | ||
| dst_stats.incr_steal_operations(); | ||
| } | ||
| return lifo; | ||
| // No tasks were stolen | ||
| return None; | ||
| } | ||
@@ -607,3 +578,2 @@ | ||
| assert!(self.pop().is_none(), "queue not empty"); | ||
| assert!(self.pop_lifo().is_none(), "LIFO slot not empty"); | ||
| } | ||
@@ -610,0 +580,0 @@ } |
@@ -115,2 +115,9 @@ //! A scheduler is initialized with a fixed number of workers. Each worker is | ||
| /// When a task is scheduled from a worker, it is stored in this slot. The | ||
| /// worker will check this slot for a task **before** checking the run | ||
| /// queue. This effectively results in the **last** scheduled task to be run | ||
| /// next (LIFO). This is an optimization for improving locality which | ||
| /// benefits message passing patterns and helps to reduce latency. | ||
| lifo_slot: Option<Notified>, | ||
| /// When `true`, locally scheduled tasks go to the LIFO slot. When `false`, | ||
@@ -277,2 +284,3 @@ /// they go to the back of the `run_queue`. | ||
| tick: 0, | ||
| lifo_slot: None, | ||
| lifo_enabled: !config.disable_lifo_slot, | ||
@@ -437,3 +445,3 @@ run_queue, | ||
| // stolen, so we move the task out of the lifo_slot to the run_queue. | ||
| if let Some(task) = core.run_queue.pop_lifo() { | ||
| if let Some(task) = core.lifo_slot.take() { | ||
| core.run_queue | ||
@@ -668,3 +676,3 @@ .push_back_or_overflow(task, &*cx.worker.handle, &mut core.stats); | ||
| // Check for a task in the LIFO slot | ||
| let task = match core.run_queue.pop_lifo() { | ||
| let task = match core.lifo_slot.take() { | ||
| Some(task) => task, | ||
@@ -1078,3 +1086,3 @@ None => { | ||
| fn next_local_task(&mut self) -> Option<Notified> { | ||
| self.run_queue.pop_lifo().or_else(|| self.run_queue.pop()) | ||
| self.lifo_slot.take().or_else(|| self.run_queue.pop()) | ||
| } | ||
@@ -1135,3 +1143,3 @@ | ||
| fn has_tasks(&self) -> bool { | ||
| self.run_queue.has_tasks() | ||
| self.lifo_slot.is_some() || self.run_queue.has_tasks() | ||
| } | ||
@@ -1145,3 +1153,3 @@ | ||
| } | ||
| self.run_queue.len() > 1 | ||
| self.lifo_slot.is_some() as usize + self.run_queue.len() > 1 | ||
| } | ||
@@ -1308,13 +1316,19 @@ | ||
| // flexibility and the task may go to the front of the queue. | ||
| if is_yield || !core.lifo_enabled { | ||
| let should_notify = if is_yield || !core.lifo_enabled { | ||
| core.run_queue | ||
| .push_back_or_overflow(task, self, &mut core.stats); | ||
| true | ||
| } else { | ||
| // Push to the LIFO slot | ||
| if let Some(prev) = core.run_queue.push_lifo(task) { | ||
| // There was a previous task in the LIFO slot which needs | ||
| // to be pushed to the back of the run queue. | ||
| let prev = core.lifo_slot.take(); | ||
| let ret = prev.is_some(); | ||
| if let Some(prev) = prev { | ||
| core.run_queue | ||
| .push_back_or_overflow(prev, self, &mut core.stats); | ||
| } | ||
| core.lifo_slot = Some(task); | ||
| ret | ||
| }; | ||
@@ -1325,3 +1339,3 @@ | ||
| // batches, the notification is delayed until the park is complete. | ||
| if core.park.is_some() { | ||
| if should_notify && core.park.is_some() { | ||
| self.notify_parked_local(); | ||
@@ -1328,0 +1342,0 @@ } |
@@ -212,7 +212,2 @@ //! The task module. | ||
| #[cfg(feature = "rt-multi-thread")] | ||
| mod atomic_notified; | ||
| #[cfg(feature = "rt-multi-thread")] | ||
| pub(crate) use self::atomic_notified::AtomicNotified; | ||
| mod waker; | ||
@@ -219,0 +214,0 @@ |
@@ -65,62 +65,3 @@ use crate::runtime::scheduler::multi_thread::{queue, Stats}; | ||
| // Like `basic`, but with tasks in the LIFO slot. | ||
| #[test] | ||
| fn basic_lifo() { | ||
| loom::model(|| { | ||
| let (steal, mut local) = queue::local(); | ||
| let inject = RefCell::new(vec![]); | ||
| let mut stats = new_stats(); | ||
| let th = thread::spawn(move || { | ||
| let mut stats = new_stats(); | ||
| let (_, mut local) = queue::local(); | ||
| let mut n = 0; | ||
| for _ in 0..3 { | ||
| if steal.steal_into(&mut local, &mut stats).is_some() { | ||
| n += 1; | ||
| } | ||
| while local.pop().is_some() { | ||
| n += 1; | ||
| } | ||
| } | ||
| n | ||
| }); | ||
| let mut n = 0; | ||
| for _ in 0..2 { | ||
| for _ in 0..2 { | ||
| let (task, _) = unowned(async {}); | ||
| if let Some(prev) = local.push_lifo(task) { | ||
| local.push_back_or_overflow(prev, &inject, &mut stats); | ||
| } | ||
| } | ||
| if local.pop_lifo().or_else(|| local.pop()).is_some() { | ||
| n += 1; | ||
| } | ||
| // Push another task | ||
| let (task, _) = unowned(async {}); | ||
| if let Some(prev) = local.push_lifo(task) { | ||
| local.push_back_or_overflow(prev, &inject, &mut stats); | ||
| } | ||
| while local.pop_lifo().or_else(|| local.pop()).is_some() { | ||
| n += 1; | ||
| } | ||
| } | ||
| n += inject.borrow_mut().drain(..).count(); | ||
| n += th.join().unwrap(); | ||
| assert_eq!(6, n); | ||
| }); | ||
| } | ||
| #[test] | ||
| fn steal_overflow() { | ||
@@ -179,22 +120,12 @@ loom::model(|| { | ||
| loom::model(|| { | ||
| let (steal, mut local) = queue::local(); | ||
| let inject = RefCell::new(vec![]); | ||
| fn steal_tasks(steal: queue::Steal<NoopSchedule>) -> usize { | ||
| let mut stats = new_stats(); | ||
| let (_, mut local) = queue::local(); | ||
| // Push work | ||
| for _ in 0..NUM_TASKS { | ||
| let (task, _) = unowned(async {}); | ||
| local.push_back_or_overflow(task, &inject, &mut stats); | ||
| if steal.steal_into(&mut local, &mut stats).is_none() { | ||
| return 0; | ||
| } | ||
| let th1 = { | ||
| let steal = steal.clone(); | ||
| thread::spawn(move || steal_tasks(steal)) | ||
| }; | ||
| let mut n = 1; | ||
| let th2 = thread::spawn(move || steal_tasks(steal)); | ||
| let mut n = 0; | ||
| while local.pop().is_some() { | ||
@@ -204,16 +135,5 @@ n += 1; | ||
| n += inject.borrow_mut().drain(..).count(); | ||
| n | ||
| } | ||
| n += th1.join().unwrap(); | ||
| n += th2.join().unwrap(); | ||
| assert_eq!(n, NUM_TASKS); | ||
| }); | ||
| } | ||
| // Like `multi_stealer`, but with tasks in the LIFO slot. | ||
| #[test] | ||
| fn multi_stealer_lifo() { | ||
| const NUM_TASKS: usize = 5; | ||
| loom::model(|| { | ||
@@ -224,12 +144,6 @@ let (steal, mut local) = queue::local(); | ||
| // Push work into the LIFO slot. | ||
| // Push work | ||
| for _ in 0..NUM_TASKS { | ||
| let (task, _) = unowned(async {}); | ||
| // Push the new task into the LIFO slot, as though it's being | ||
| // notified locally. | ||
| if let Some(prev) = local.push_lifo(task) { | ||
| // If a task was already in the LIFO slot, stick the previous | ||
| // LIFO task into the queue. | ||
| local.push_back_or_overflow(prev, &inject, &mut stats); | ||
| } | ||
| local.push_back_or_overflow(task, &inject, &mut stats); | ||
| } | ||
@@ -246,3 +160,3 @@ | ||
| while local.pop_lifo().or_else(|| local.pop()).is_some() { | ||
| while local.pop().is_some() { | ||
| n += 1; | ||
@@ -260,19 +174,2 @@ } | ||
| fn steal_tasks(steal: queue::Steal<NoopSchedule>) -> usize { | ||
| let mut stats = new_stats(); | ||
| let (_, mut local) = queue::local(); | ||
| if steal.steal_into(&mut local, &mut stats).is_none() { | ||
| return 0; | ||
| } | ||
| let mut n = 1; | ||
| while local.pop().is_some() { | ||
| n += 1; | ||
| } | ||
| n | ||
| } | ||
| #[test] | ||
@@ -279,0 +176,0 @@ fn chained_steal() { |
+0
-106
@@ -695,109 +695,3 @@ #![warn(rust_2018_idioms)] | ||
| // Tests that when a task is notified by another task and is placed in the LIFO | ||
| // slot, and then the notifying task blocks the runtime, the notified task will | ||
| // be stolen by another worker thread. | ||
| // | ||
| // Integration test for: https://github.com/tokio-rs/tokio/issues/4941 | ||
| #[test] | ||
| fn lifo_stealable() { | ||
| use std::time::Duration; | ||
| // This test constructs a scenario where a task (the "blocker task") | ||
| // notifies another task (the "victim task") and then blocks that worker | ||
| // thread indefinitely. The victim task is placed in the worker's LIFO | ||
| // slot, and will only run to completion if another worker steals it from | ||
| // the LIFO slot, as the current worker remains blocked running the blocker | ||
| // task. | ||
| // | ||
| // To make the blocker task block its worker thread without yielding, we use | ||
| // a `std::sync` blocking channel, so that we can eventually unblock it when | ||
| // the test completes. | ||
| let (block_thread_tx, block_thread_rx) = mpsc::channel::<()>(); | ||
| // We use this channel to wait until the victim task has started running. If | ||
| // we just spawned the victim task and then immediately blocked the worker | ||
| // thread, it would be in the global inject queue, rather than in the | ||
| // worker's LIFO slot. | ||
| let (task_started_tx, task_started_rx) = tokio::sync::oneshot::channel(); | ||
| // Finally, this channel is used by the blocker task to wake up the victim | ||
| // task, so that it is placed in the worker's LIFO slot. | ||
| let (notify_tx, notify_rx) = tokio::sync::oneshot::channel(); | ||
| let rt = runtime::Builder::new_multi_thread() | ||
| // Make sure there are enough workers that one can be parked running the | ||
| // I/O driver and another can be parked running the timer wheel and | ||
| // there's still at least one worker free to steal the blocked task. | ||
| .worker_threads(4) | ||
| .enable_time() | ||
| .build() | ||
| .unwrap(); | ||
| rt.block_on(async { | ||
| // Keep the runtime busy so that the workers that might steal the | ||
| // blocked task don't all park themselves forever. | ||
| // | ||
| // Since this task will always be woken by whichever worker is holding | ||
| // the time driver, rather than a worker that's executing tasks, it | ||
| // shouldn't ever kick the victim task out of its worker's LIFO slot. | ||
| let churn = tokio::spawn(async move { | ||
| loop { | ||
| tokio::time::sleep(Duration::from_millis(4)).await; | ||
| } | ||
| }); | ||
| let victim_task_joined = tokio::spawn(async move { | ||
| println!("[victim] task started"); | ||
| task_started_tx.send(()).unwrap(); | ||
| println!("[victim] task waiting for wakeup..."); | ||
| notify_rx.await.unwrap(); | ||
| println!("[victim] task running after wakeup"); | ||
| }); | ||
| // Wait for the victim task to have been polled once and have yielded | ||
| // before we spawn the task that will notify it. This ensures that it | ||
| // will be placed in the LIFO slot of the same worker thread as the | ||
| // blocker task, rather than on the global injector queue. | ||
| task_started_rx.await.unwrap(); | ||
| println!("[main] victim slot task start acked!"); | ||
| // Now, spawn a task that will notify the victim task before going | ||
| // blocking forever. | ||
| tokio::spawn(async move { | ||
| println!("[blocker] sending wakeup"); | ||
| notify_tx.send(()).unwrap(); | ||
| println!("[blocker] blocking the worker thread..."); | ||
| // Block the worker thread indefinitely by waiting for a message on | ||
| // a blocking channel. Since we just notified the victim task, it | ||
| // went into the current worker thread's LIFO slot, and will only | ||
| // be able to complete if another worker thread successfully steals | ||
| // it from the LIFO slot. | ||
| // | ||
| // Using a channel rather than e.g. `loop {}` allows us to terminate | ||
| // the task cleanly when the test finishes. | ||
| let _ = block_thread_rx.recv(); | ||
| println!("[blocker] done"); | ||
| }); | ||
| println!("[main] blocker task spawned"); | ||
| // Wait for the victim task to join. If it does, then it has been stolen | ||
| // by another worker thread successfully. | ||
| // | ||
| // The 30-second timeout is chosen arbitrarily: its purpose is to ensure | ||
| // that the failure mode for this test is a panic, rather than hanging | ||
| // indefinitely. 30 seconds should be plenty of time for the task to be | ||
| // stolen, if it's going to work. | ||
| let result = tokio::time::timeout(Duration::from_secs(30), victim_task_joined).await; | ||
| println!("[main] result: {result:?}"); | ||
| // Before possibly panicking, make sure that we wake up the blocker task | ||
| // so that it doesn't stop the runtime from shutting down. | ||
| block_thread_tx.send(()).unwrap(); | ||
| churn.abort(); | ||
| result | ||
| .expect("task in LIFO slot should complete within 30 seconds") | ||
| .expect("task in LIFO slot should not panic"); | ||
| }) | ||
| } | ||
| #[test] | ||
| /// Deferred tasks should be woken before starting the [`tokio::task::block_in_place`] | ||
@@ -804,0 +698,0 @@ // https://github.com/tokio-rs/tokio/issues/7877 |
@@ -677,9 +677,5 @@ #![warn(rust_2018_idioms)] | ||
| // Bump the next-run spawn | ||
| let nop = tokio::spawn(async {}); | ||
| tokio::spawn(async {}); | ||
| // Wait until we're sure the other worker is blocked. | ||
| rx1.recv().unwrap(); | ||
| // Make sure the no-op task has terminated so that it doesn't end up | ||
| // in the LIFO slot and throw off our counts. | ||
| let _ = nop.await; | ||
@@ -686,0 +682,0 @@ // Spawn some tasks |
| use crate::loom::sync::atomic::AtomicPtr; | ||
| use crate::runtime::task::{Header, Notified, RawTask}; | ||
| use std::marker::PhantomData; | ||
| use std::ptr; | ||
| use std::ptr::NonNull; | ||
| use std::sync::atomic::Ordering::SeqCst; | ||
| /// An atomic cell which can contain a pointer to a [`Notified`] task. | ||
| /// | ||
| /// This is similar to the `crate::util::AtomicCell` type, but specialized to | ||
| /// hold a task pointer --- this type "remembers" the task's scheduler generic | ||
| /// when a task is stored in the cell, so that the pointer can be turned back | ||
| /// into a [`Notified`] task with the correct generic type when it is retrieved. | ||
| pub(crate) struct AtomicNotified<S: 'static> { | ||
| task: AtomicPtr<Header>, | ||
| _scheduler: PhantomData<S>, | ||
| } | ||
| impl<S: 'static> AtomicNotified<S> { | ||
| pub(crate) fn empty() -> Self { | ||
| Self { | ||
| task: AtomicPtr::new(ptr::null_mut()), | ||
| _scheduler: PhantomData, | ||
| } | ||
| } | ||
| pub(crate) fn swap(&self, task: Option<Notified<S>>) -> Option<Notified<S>> { | ||
| let new = task | ||
| .map(|t| t.into_raw().header_ptr().as_ptr()) | ||
| .unwrap_or_else(ptr::null_mut); | ||
| let old = self.task.swap(new, SeqCst); | ||
| NonNull::new(old).map(|ptr| unsafe { | ||
| // Safety: since we only allow tasks with the same scheduler type to | ||
| // be placed in this cell, we know that the pointed task's scheduler | ||
| // type matches the type parameter S. | ||
| Notified::from_raw(RawTask::from_raw(ptr)) | ||
| }) | ||
| } | ||
| pub(crate) fn take(&self) -> Option<Notified<S>> { | ||
| self.swap(None) | ||
| } | ||
| pub(crate) fn is_some(&self) -> bool { | ||
| !self.task.load(SeqCst).is_null() | ||
| } | ||
| } | ||
| unsafe impl<S: Send> Send for AtomicNotified<S> {} | ||
| unsafe impl<S: Send> Sync for AtomicNotified<S> {} | ||
| impl<S> Drop for AtomicNotified<S> { | ||
| fn drop(&mut self) { | ||
| // Ensure the task reference is dropped if this cell is dropped. | ||
| let _ = self.take(); | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display