From 9681ce2b95ae7271c041f69b9fc48912259a7ea8 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sun, 21 Jul 2024 16:28:12 +0000 Subject: [PATCH 1/5] chore: make 1.38 an LTS (#6706) --- README.md | 1 + tokio/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a3d0e2be66..b04599b99bd 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,7 @@ releases are: * `1.32.x` - LTS release until September 2024. (MSRV 1.63) * `1.36.x` - LTS release until March 2025. (MSRV 1.63) + * `1.38.x` - LTS release until July 2025. (MSRV 1.63) Each LTS release will continue to receive backported fixes for at least a year. If you wish to use a fixed minor release in your project, we recommend that you diff --git a/tokio/README.md b/tokio/README.md index 6a3d0e2be66..b04599b99bd 100644 --- a/tokio/README.md +++ b/tokio/README.md @@ -217,6 +217,7 @@ releases are: * `1.32.x` - LTS release until September 2024. (MSRV 1.63) * `1.36.x` - LTS release until March 2025. (MSRV 1.63) + * `1.38.x` - LTS release until July 2025. (MSRV 1.63) Each LTS release will continue to receive backported fixes for at least a year. If you wish to use a fixed minor release in your project, we recommend that you From 4b174ce2c95fe1d1a217917db93fcc935e17e0da Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 31 Mar 2025 09:42:43 -0700 Subject: [PATCH 2/5] sync: fix cloning value when receiving from broadcast channel The broadcast channel does not require values to implement `Sync` yet it calls the `.clone()` method without synchronizing. This is unsound logic. This patch adds per-value synchronization on receive to handle this case. It is unlikely any usage of the broadcast channel is currently at risk of the unsoundeness issue as it requires accessing a `!Sync` type during `.clone()`, which would be very unusual when using the broadcast channel. --- tokio/src/sync/broadcast.rs | 55 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/tokio/src/sync/broadcast.rs b/tokio/src/sync/broadcast.rs index ba0a44fb8b9..10dfd0efb75 100644 --- a/tokio/src/sync/broadcast.rs +++ b/tokio/src/sync/broadcast.rs @@ -118,7 +118,7 @@ use crate::loom::cell::UnsafeCell; use crate::loom::sync::atomic::{AtomicBool, AtomicUsize}; -use crate::loom::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; +use crate::loom::sync::{Arc, Mutex, MutexGuard}; use crate::util::linked_list::{self, GuardedLinkedList, LinkedList}; use crate::util::WakeList; @@ -303,7 +303,7 @@ use self::error::{RecvError, SendError, TryRecvError}; /// Data shared between senders and receivers. struct Shared { /// slots in the channel. - buffer: Box<[RwLock>]>, + buffer: Box<[Mutex>]>, /// Mask a position -> index. mask: usize, @@ -347,7 +347,7 @@ struct Slot { /// /// The value is set by `send` when the write lock is held. When a reader /// drops, `rem` is decremented. When it hits zero, the value is dropped. - val: UnsafeCell>, + val: Option, } /// An entry in the wait queue. @@ -385,7 +385,7 @@ generate_addr_of_methods! { } struct RecvGuard<'a, T> { - slot: RwLockReadGuard<'a, Slot>, + slot: MutexGuard<'a, Slot>, } /// Receive a value future. @@ -394,11 +394,15 @@ struct Recv<'a, T> { receiver: &'a mut Receiver, /// Entry in the waiter `LinkedList`. - waiter: UnsafeCell, + waiter: WaiterCell, } -unsafe impl<'a, T: Send> Send for Recv<'a, T> {} -unsafe impl<'a, T: Send> Sync for Recv<'a, T> {} +// The wrapper around `UnsafeCell` isolates the unsafe impl `Send` and `Sync` +// from `Recv`. +struct WaiterCell(UnsafeCell); + +unsafe impl Send for WaiterCell {} +unsafe impl Sync for WaiterCell {} /// Max number of receivers. Reserve space to lock. const MAX_RECEIVERS: usize = usize::MAX >> 2; @@ -466,12 +470,6 @@ pub fn channel(capacity: usize) -> (Sender, Receiver) { (tx, rx) } -unsafe impl Send for Sender {} -unsafe impl Sync for Sender {} - -unsafe impl Send for Receiver {} -unsafe impl Sync for Receiver {} - impl Sender { /// Creates the sending-half of the [`broadcast`] channel. /// @@ -510,10 +508,10 @@ impl Sender { let mut buffer = Vec::with_capacity(capacity); for i in 0..capacity { - buffer.push(RwLock::new(Slot { + buffer.push(Mutex::new(Slot { rem: AtomicUsize::new(0), pos: (i as u64).wrapping_sub(capacity as u64), - val: UnsafeCell::new(None), + val: None, })); } @@ -599,7 +597,7 @@ impl Sender { tail.pos = tail.pos.wrapping_add(1); // Get the slot - let mut slot = self.shared.buffer[idx].write().unwrap(); + let mut slot = self.shared.buffer[idx].lock(); // Track the position slot.pos = pos; @@ -608,7 +606,7 @@ impl Sender { slot.rem.with_mut(|v| *v = rem); // Write the value - slot.val = UnsafeCell::new(Some(value)); + slot.val = Some(value); // Release the slot lock before notifying the receivers. drop(slot); @@ -695,7 +693,7 @@ impl Sender { while low < high { let mid = low + (high - low) / 2; let idx = base_idx.wrapping_add(mid) & self.shared.mask; - if self.shared.buffer[idx].read().unwrap().rem.load(SeqCst) == 0 { + if self.shared.buffer[idx].lock().rem.load(SeqCst) == 0 { low = mid + 1; } else { high = mid; @@ -737,7 +735,7 @@ impl Sender { let tail = self.shared.tail.lock(); let idx = (tail.pos.wrapping_sub(1) & self.shared.mask as u64) as usize; - self.shared.buffer[idx].read().unwrap().rem.load(SeqCst) == 0 + self.shared.buffer[idx].lock().rem.load(SeqCst) == 0 } /// Returns the number of active receivers. @@ -1057,7 +1055,7 @@ impl Receiver { let idx = (self.next & self.shared.mask as u64) as usize; // The slot holding the next value to read - let mut slot = self.shared.buffer[idx].read().unwrap(); + let mut slot = self.shared.buffer[idx].lock(); if slot.pos != self.next { // Release the `slot` lock before attempting to acquire the `tail` @@ -1074,7 +1072,7 @@ impl Receiver { let mut tail = self.shared.tail.lock(); // Acquire slot lock again - slot = self.shared.buffer[idx].read().unwrap(); + slot = self.shared.buffer[idx].lock(); // Make sure the position did not change. This could happen in the // unlikely event that the buffer is wrapped between dropping the @@ -1367,12 +1365,12 @@ impl<'a, T> Recv<'a, T> { fn new(receiver: &'a mut Receiver) -> Recv<'a, T> { Recv { receiver, - waiter: UnsafeCell::new(Waiter { + waiter: WaiterCell(UnsafeCell::new(Waiter { queued: AtomicBool::new(false), waker: None, pointers: linked_list::Pointers::new(), _p: PhantomPinned, - }), + })), } } @@ -1384,7 +1382,7 @@ impl<'a, T> Recv<'a, T> { is_unpin::<&mut Receiver>(); let me = self.get_unchecked_mut(); - (me.receiver, &me.waiter) + (me.receiver, &me.waiter.0) } } } @@ -1418,6 +1416,7 @@ impl<'a, T> Drop for Recv<'a, T> { // `Shared::notify_rx` before we drop the object. let queued = self .waiter + .0 .with(|ptr| unsafe { (*ptr).queued.load(Acquire) }); // If the waiter is queued, we need to unlink it from the waiters list. @@ -1432,6 +1431,7 @@ impl<'a, T> Drop for Recv<'a, T> { // `Relaxed` order suffices because we hold the tail lock. let queued = self .waiter + .0 .with_mut(|ptr| unsafe { (*ptr).queued.load(Relaxed) }); if queued { @@ -1440,7 +1440,7 @@ impl<'a, T> Drop for Recv<'a, T> { // safety: tail lock is held and the wait node is verified to be in // the list. unsafe { - self.waiter.with_mut(|ptr| { + self.waiter.0.with_mut(|ptr| { tail.waiters.remove((&mut *ptr).into()); }); } @@ -1486,7 +1486,7 @@ impl<'a, T> RecvGuard<'a, T> { where T: Clone, { - self.slot.val.with(|ptr| unsafe { (*ptr).clone() }) + self.slot.val.clone() } } @@ -1494,8 +1494,7 @@ impl<'a, T> Drop for RecvGuard<'a, T> { fn drop(&mut self) { // Decrement the remaining counter if 1 == self.slot.rem.fetch_sub(1, SeqCst) { - // Safety: Last receiver, drop the value - self.slot.val.with_mut(|ptr| unsafe { *ptr = None }); + self.slot.val = None; } } } From 7b6ccb515ff067151ed62db835f735e5653f8784 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Wed, 2 Apr 2025 14:25:45 -0700 Subject: [PATCH 3/5] chore: backport CI fixes --- .cirrus.yml | 2 +- .github/workflows/ci.yml | 40 +++++++++++++++++++++++--------- Cargo.toml | 13 +++++++++++ examples/Cargo.toml | 3 +++ tokio/Cargo.toml | 4 +++- tokio/src/runtime/tests/queue.rs | 13 ----------- 6 files changed, 49 insertions(+), 26 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index bdf3af74098..abf07ca4852 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,7 +1,7 @@ only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*') auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*' freebsd_instance: - image_family: freebsd-14-0 + image_family: freebsd-14-2 env: RUST_STABLE: stable RUST_NIGHTLY: nightly-2024-05-05 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76151902576..fb96e27c532 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -177,7 +177,7 @@ jobs: run: | set -euxo pipefail RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests - + test-integration-tests-per-feature: needs: basics name: Run integration tests for each feature @@ -455,10 +455,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Check semver + - name: Check `tokio` semver + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: ${{ env.rust_stable }} + package: tokio + release-type: minor + - name: Check semver for rest of the workspace + if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') }} uses: obi1kenobi/cargo-semver-checks-action@v2 with: rust-toolchain: ${{ env.rust_stable }} + exclude: tokio release-type: minor cross-check: @@ -689,7 +697,14 @@ jobs: toolchain: ${{ env.rust_min }} - uses: Swatinem/rust-cache@v2 - name: "check --workspace --all-features" - run: cargo check --workspace --all-features + run: | + if [[ "${{ github.event.pull_request.base.ref }}" =~ ^tokio-1\..* ]]; then + # Only check `tokio` crate as the PR is backporting to an earlier tokio release. + cargo check -p tokio --all-features + else + # Check all crates in the workspace + cargo check --workspace --all-features + fi env: RUSTFLAGS: "" # remove -Dwarnings @@ -927,10 +942,10 @@ jobs: targets: ${{ matrix.target }} # Install dependencies - - name: Install cargo-hack, wasmtime, and cargo-wasi + - name: Install cargo-hack, wasmtime uses: taiki-e/install-action@v2 with: - tool: cargo-hack,wasmtime,cargo-wasi + tool: cargo-hack,wasmtime - uses: Swatinem/rust-cache@v2 - name: WASI test tokio full @@ -956,9 +971,12 @@ jobs: - name: test tests-integration --features wasi-rt # TODO: this should become: `cargo hack wasi test --each-feature` - run: cargo wasi test --test rt_yield --features wasi-rt + run: cargo test --target ${{ matrix.target }} --test rt_yield --features wasi-rt if: matrix.target == 'wasm32-wasip1' working-directory: tests-integration + env: + CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --" + RUSTFLAGS: -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864 - name: test tests-integration --features wasi-threads-rt run: cargo test --target ${{ matrix.target }} --features wasi-threads-rt @@ -980,7 +998,7 @@ jobs: rust: # `check-external-types` requires a specific Rust nightly version. See # the README for details: https://github.com/awslabs/cargo-check-external-types - - nightly-2023-10-21 + - nightly-2024-06-30 steps: - uses: actions/checkout@v4 - name: Install Rust ${{ matrix.rust }} @@ -991,7 +1009,7 @@ jobs: - name: Install cargo-check-external-types uses: taiki-e/cache-cargo-install-action@v1 with: - tool: cargo-check-external-types@0.1.10 + tool: cargo-check-external-types@0.1.13 - name: check-external-types run: cargo check-external-types --all-features working-directory: tokio @@ -1051,11 +1069,11 @@ jobs: - name: Make sure dictionary words are sorted and unique run: | # `sed` removes the first line (number of words) and - # the last line (new line). - # + # the last line (new line). + # # `sort` makes sure everything in between is sorted # and contains no duplicates. - # + # # Since `sort` is sensitive to locale, we set it # using LC_ALL to en_US.UTF8 to be consistent in different # environments. diff --git a/Cargo.toml b/Cargo.toml index 2238deac71c..618b310e32c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,16 @@ members = [ [workspace.metadata.spellcheck] config = "spellcheck.toml" + +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(fuzzing)', + 'cfg(loom)', + 'cfg(mio_unsupported_force_poll_poll)', + 'cfg(tokio_allow_from_blocking_fd)', + 'cfg(tokio_internal_mt_counters)', + 'cfg(tokio_no_parking_lot)', + 'cfg(tokio_no_tuning_tests)', + 'cfg(tokio_taskdump)', + 'cfg(tokio_unstable)', +] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a244fccaca1..8d42ca3d8fa 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -94,3 +94,6 @@ path = "named-pipe-multi-client.rs" [[example]] name = "dump" path = "dump.rs" + +[lints] +workspace = true diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index 13a9a8c0579..bfcb55ed5f9 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -171,6 +171,8 @@ features = ["full", "test-util"] allowed_external_types = [ "bytes::buf::buf_impl::Buf", "bytes::buf::buf_mut::BufMut", - "tokio_macros::*", ] + +[lints] +workspace = true diff --git a/tokio/src/runtime/tests/queue.rs b/tokio/src/runtime/tests/queue.rs index f463355f0d3..9047f4ad7af 100644 --- a/tokio/src/runtime/tests/queue.rs +++ b/tokio/src/runtime/tests/queue.rs @@ -1,5 +1,4 @@ use crate::runtime::scheduler::multi_thread::{queue, Stats}; -use crate::runtime::task::{self, Schedule, Task}; use std::cell::RefCell; use std::thread; @@ -272,15 +271,3 @@ fn stress2() { assert_eq!(num_pop, NUM_TASKS); } } - -struct Runtime; - -impl Schedule for Runtime { - fn release(&self, _task: &Task) -> Option> { - None - } - - fn schedule(&self, _task: task::Notified) { - unreachable!(); - } -} From aa303bc2051f7c21b48bb7bfcafe8fd4f39afd21 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Wed, 2 Apr 2025 17:29:30 -0700 Subject: [PATCH 4/5] chore: prepare Tokio v1.38.2 release --- README.md | 2 +- tokio/CHANGELOG.md | 14 ++++++++++++++ tokio/Cargo.toml | 2 +- tokio/README.md | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b04599b99bd..b82b6bf345a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.38.1", features = ["full"] } +tokio = { version = "1.38.2", features = ["full"] } ``` Then, on your main.rs: diff --git a/tokio/CHANGELOG.md b/tokio/CHANGELOG.md index cfe70fa4d52..2e911098d55 100644 --- a/tokio/CHANGELOG.md +++ b/tokio/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.38.2 (April 2nd, 2025) + +This release fixes a soundness issue in the broadcast channel. The channel +accepts values that are `Send` but `!Sync`. Previously, the channel called +`clone()` on these values without synchronizing. This release fixes the channel +by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and +reporting the issue). + +### Fixed + +- sync: synchronize `clone()` call in broadcast channel ([#7232]) + +[#7232]: https://github.com/tokio-rs/tokio/pull/7232 + # 1.38.1 (July 16th, 2024) This release fixes the bug identified as ([#6682]), which caused timers not diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index bfcb55ed5f9..6a908042596 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -6,7 +6,7 @@ name = "tokio" # - README.md # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.38.1" +version = "1.38.2" edition = "2021" rust-version = "1.63" authors = ["Tokio Contributors "] diff --git a/tokio/README.md b/tokio/README.md index b04599b99bd..b82b6bf345a 100644 --- a/tokio/README.md +++ b/tokio/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.38.1", features = ["full"] } +tokio = { version = "1.38.2", features = ["full"] } ``` Then, on your main.rs: From a7b658c35bd40f6811e557aeb97cbb361b612c56 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 3 Apr 2025 10:44:26 -0700 Subject: [PATCH 5/5] chore: prepare Tokio v1.43.1 release --- README.md | 2 +- tokio/CHANGELOG.md | 14 ++++++++++++++ tokio/Cargo.toml | 2 +- tokio/README.md | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 16b0819fea3..1e7474c2810 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.43.0", features = ["full"] } +tokio = { version = "1.43.1", features = ["full"] } ``` Then, on your main.rs: diff --git a/tokio/CHANGELOG.md b/tokio/CHANGELOG.md index 2ba7e8a0cab..2155f14e8c9 100644 --- a/tokio/CHANGELOG.md +++ b/tokio/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.43.1 (April 2nd, 2025) + +This release fixes a soundness issue in the broadcast channel. The channel +accepts values that are `Send` but `!Sync`. Previously, the channel called +`clone()` on these values without synchronizing. This release fixes the channel +by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and +reporting the issue). + +### Fixed + +- sync: synchronize `clone()` call in broadcast channel ([#7232]) + +[#7232]: https://github.com/tokio-rs/tokio/pull/7232 + # 1.43.0 (Jan 8th, 2025) ### Added diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index 2b0c1127a71..19dfa51d3e0 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -6,7 +6,7 @@ name = "tokio" # - README.md # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.43.0" +version = "1.43.1" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] diff --git a/tokio/README.md b/tokio/README.md index 16b0819fea3..1e7474c2810 100644 --- a/tokio/README.md +++ b/tokio/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.43.0", features = ["full"] } +tokio = { version = "1.43.1", features = ["full"] } ``` Then, on your main.rs: