Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ jobs:
fail-fast: false
matrix:
include:
- rust: '1.61'
- rust: '1.63'
os: ubuntu-latest
- rust: '1.61'
- rust: '1.63'
os: windows-latest
- rust: stable
os: ubuntu-latest
Expand Down Expand Up @@ -136,10 +136,10 @@ jobs:
if ! git diff --exit-code; then
git add .
git commit -m "Update no_atomic.rs"
echo "::set-output name=success::false"
echo 'success=false' >>"${GITHUB_OUTPUT}"
fi
if: github.repository_owner == 'crossbeam-rs' && github.event_name == 'schedule'
- uses: peter-evans/create-pull-request@v5
- uses: peter-evans/create-pull-request@v7
with:
title: Update no_atomic.rs
body: |
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(crossbeam_loom)',
'cfg(crossbeam_sanitize)',
] }
[workspace.lints.clippy]
# Suppress buggy or noisy clippy lints
declare_interior_mutable_const = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7665
lint_groups_priority = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/12920
47 changes: 13 additions & 34 deletions ci/no_atomic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,24 @@ cd "$(dirname "$0")"/..
# Usage:
# ./ci/no_atomic.sh

file="no_atomic.rs"
file=no_atomic.rs

no_atomic=()
for target_spec in $(RUSTC_BOOTSTRAP=1 rustc +stable -Z unstable-options --print all-target-specs-json | jq -c '. | to_entries | .[]'); do
target=$(jq <<<"${target_spec}" -r '.key')
target_spec=$(jq <<<"${target_spec}" -c '.value')
res=$(jq <<<"${target_spec}" -r 'select(."atomic-cas" == false)')
[[ -z "${res}" ]] || no_atomic_cas+=("${target}")
max_atomic_width=$(jq <<<"${target_spec}" -r '."max-atomic-width"')
min_atomic_width=$(jq <<<"${target_spec}" -r '."min-atomic-width"')
case "${max_atomic_width}" in
# `"max-atomic-width" == 0` means that atomic is not supported at all.
# We do not have a cfg for targets with {8,16}-bit atomic only, so
# for now we treat them the same as targets that do not support atomic.
0) no_atomic+=("${target}") ;;
# It is not clear exactly what `"max-atomic-width" == null` means, but they
# actually seem to have the same max-atomic-width as the target-pointer-width.
# The targets currently included in this group are "mipsel-sony-psp",
# "thumbv4t-none-eabi", "thumbv6m-none-eabi", all of which are
# `"target-pointer-width" == "32"`, so assuming them `"max-atomic-width" == 32`
# for now.
null | 8 | 16 | 32 | 64 | 128) ;;
*) exit 1 ;;
esac
case "${min_atomic_width}" in
8 | null) ;;
*) no_atomic+=("${target}") ;;
esac
done
# `"max-atomic-width" == 0` means that atomic is not supported at all.
# We do not have a cfg for targets with {8,16}-bit atomic only, so
# for now we treat them the same as targets that do not support atomic.
# It is not clear exactly what `"max-atomic-width" == null` means, but they
# actually seem to have the same max-atomic-width as the target-pointer-width.
# The targets currently included in this group are "mipsel-sony-psp",
# "thumbv4t-none-eabi", "thumbv6m-none-eabi", all of which are
# `"target-pointer-width" == "32"`, so assuming them `"max-atomic-width" == 32`
# for now.
no_atomic=$(RUSTC_BOOTSTRAP=1 rustc +stable -Z unstable-options --print all-target-specs-json | jq -r '. | to_entries[] | select((.value."max-atomic-width" == 0) or (.value."min-atomic-width" and .value."min-atomic-width" != 8)) | " \"" + .key + "\","')

cat >"${file}" <<EOF
// This file is @generated by $(basename "$0").
// This file is @generated by ${0##*/}.
// It is not intended for manual editing.

const NO_ATOMIC: &[&str] = &[
EOF
for target in "${no_atomic[@]}"; do
echo " \"${target}\"," >>"${file}"
done
cat >>"${file}" <<EOF
${no_atomic}
];
EOF
7 changes: 7 additions & 0 deletions crossbeam-channel/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Version 0.5.14

- Fix stack overflow when sending large value to unbounded channel. (#1146, #1147)
- Add `Select::new_biased` function. (#1150)
- Remove inefficient spinning. (#1154)
- Suppress buggy `clippy::zero_repeat_side_effects` lint in macro generated code. (#1123)

# Version 0.5.13

- Add `select_biased!` macro. (#1040)
Expand Down
2 changes: 1 addition & 1 deletion crossbeam-channel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name = "crossbeam-channel"
# - Update CHANGELOG.md
# - Update README.md (when increasing major or minor version)
# - Run './tools/publish.sh crossbeam-channel <version>'
version = "0.5.13"
version = "0.5.14"
edition = "2021"
rust-version = "1.60"
license = "MIT OR Apache-2.0"
Expand Down
15 changes: 0 additions & 15 deletions crossbeam-channel/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,6 @@ impl Context {
/// If the deadline is reached, `Selected::Aborted` will be selected.
#[inline]
pub fn wait_until(&self, deadline: Option<Instant>) -> Selected {
// Spin for a short time, waiting until an operation is selected.
let backoff = Backoff::new();
loop {
let sel = Selected::from(self.inner.select.load(Ordering::Acquire));
if sel != Selected::Waiting {
return sel;
}

if backoff.is_completed() {
break;
} else {
backoff.snooze();
}
}

loop {
// Check whether an operation has been selected.
let sel = Selected::from(self.inner.select.load(Ordering::Acquire));
Expand Down
37 changes: 26 additions & 11 deletions crossbeam-channel/src/flavors/list.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Unbounded channel implemented as a linked list.

use std::alloc::{alloc_zeroed, handle_alloc_error, Layout};
use std::boxed::Box;
use std::cell::UnsafeCell;
use std::marker::PhantomData;
Expand Down Expand Up @@ -50,11 +51,6 @@ struct Slot<T> {
}

impl<T> Slot<T> {
const UNINIT: Self = Self {
msg: UnsafeCell::new(MaybeUninit::uninit()),
state: AtomicUsize::new(0),
};

/// Waits until a message is written into the slot.
fn wait_write(&self) {
let backoff = Backoff::new();
Expand All @@ -76,12 +72,31 @@ struct Block<T> {
}

impl<T> Block<T> {
const LAYOUT: Layout = {
let layout = Layout::new::<Self>();
assert!(
layout.size() != 0,
"Block should never be zero-sized, as it has an AtomicPtr field"
);
layout
};

/// Creates an empty block.
fn new() -> Block<T> {
Self {
next: AtomicPtr::new(ptr::null_mut()),
slots: [Slot::UNINIT; BLOCK_CAP],
fn new() -> Box<Self> {
// SAFETY: layout is not zero-sized
let ptr = unsafe { alloc_zeroed(Self::LAYOUT) };
// Handle allocation failure
if ptr.is_null() {
handle_alloc_error(Self::LAYOUT)
}
// SAFETY: This is safe because:
// [1] `Block::next` (AtomicPtr) may be safely zero initialized.
// [2] `Block::slots` (Array) may be safely zero initialized because of [3, 4].
// [3] `Slot::msg` (UnsafeCell) may be safely zero initialized because it
// holds a MaybeUninit.
// [4] `Slot::state` (AtomicUsize) may be safely zero initialized.
// TODO: unsafe { Box::new_zeroed().assume_init() }
unsafe { Box::from_raw(ptr.cast()) }
}

/// Waits until the next pointer is set.
Expand Down Expand Up @@ -223,13 +238,13 @@ impl<T> Channel<T> {
// If we're going to have to install the next block, allocate it in advance in order to
// make the wait for other threads as short as possible.
if offset + 1 == BLOCK_CAP && next_block.is_none() {
next_block = Some(Box::new(Block::<T>::new()));
next_block = Some(Block::<T>::new());
}

// If this is the first message to be sent into the channel, we need to allocate the
// first block and install it.
if block.is_null() {
let new = Box::into_raw(Box::new(Block::<T>::new()));
let new = Box::into_raw(Block::<T>::new());

if self
.tail
Expand Down
40 changes: 33 additions & 7 deletions crossbeam-channel/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@ pub struct Select<'a> {

/// The next index to assign to an operation.
next_index: usize,

/// Whether to use the index of handles as bias for selecting ready operations.
biased: bool,
}

unsafe impl Send for Select<'_> {}
Expand All @@ -633,6 +636,28 @@ impl<'a> Select<'a> {
Select {
handles: Vec::with_capacity(4),
next_index: 0,
biased: false,
}
}

/// Creates an empty list of channel operations with biased selection.
///
/// When multiple handles are ready, this will select the operation with the lowest index.
///
/// # Examples
///
/// ```
/// use crossbeam_channel::Select;
///
/// let mut sel = Select::new_biased();
///
/// // The list of operations is empty, which means no operation can be selected.
/// assert!(sel.try_select().is_err());
/// ```
pub fn new_biased() -> Self {
Self {
biased: true,
..Default::default()
}
}

Expand Down Expand Up @@ -774,7 +799,7 @@ impl<'a> Select<'a> {
/// }
/// ```
pub fn try_select(&mut self) -> Result<SelectedOperation<'a>, TrySelectError> {
try_select(&mut self.handles, false)
try_select(&mut self.handles, self.biased)
}

/// Blocks until one of the operations becomes ready and selects it.
Expand Down Expand Up @@ -821,7 +846,7 @@ impl<'a> Select<'a> {
/// }
/// ```
pub fn select(&mut self) -> SelectedOperation<'a> {
select(&mut self.handles, false)
select(&mut self.handles, self.biased)
}

/// Blocks for a limited time until one of the operations becomes ready and selects it.
Expand Down Expand Up @@ -871,7 +896,7 @@ impl<'a> Select<'a> {
&mut self,
timeout: Duration,
) -> Result<SelectedOperation<'a>, SelectTimeoutError> {
select_timeout(&mut self.handles, timeout, false)
select_timeout(&mut self.handles, timeout, self.biased)
}

/// Blocks until a given deadline, or until one of the operations becomes ready and selects it.
Expand Down Expand Up @@ -923,7 +948,7 @@ impl<'a> Select<'a> {
&mut self,
deadline: Instant,
) -> Result<SelectedOperation<'a>, SelectTimeoutError> {
select_deadline(&mut self.handles, deadline, false)
select_deadline(&mut self.handles, deadline, self.biased)
}

/// Attempts to find a ready operation without blocking.
Expand Down Expand Up @@ -962,7 +987,7 @@ impl<'a> Select<'a> {
/// }
/// ```
pub fn try_ready(&mut self) -> Result<usize, TryReadyError> {
match run_ready(&mut self.handles, Timeout::Now, false) {
match run_ready(&mut self.handles, Timeout::Now, self.biased) {
None => Err(TryReadyError),
Some(index) => Ok(index),
}
Expand Down Expand Up @@ -1015,7 +1040,7 @@ impl<'a> Select<'a> {
panic!("no operations have been added to `Select`");
}

run_ready(&mut self.handles, Timeout::Never, false).unwrap()
run_ready(&mut self.handles, Timeout::Never, self.biased).unwrap()
}

/// Blocks for a limited time until one of the operations becomes ready.
Expand Down Expand Up @@ -1108,7 +1133,7 @@ impl<'a> Select<'a> {
/// }
/// ```
pub fn ready_deadline(&mut self, deadline: Instant) -> Result<usize, ReadyTimeoutError> {
match run_ready(&mut self.handles, Timeout::At(deadline), false) {
match run_ready(&mut self.handles, Timeout::At(deadline), self.biased) {
None => Err(ReadyTimeoutError),
Some(index) => Ok(index),
}
Expand All @@ -1120,6 +1145,7 @@ impl<'a> Clone for Select<'a> {
Select {
handles: self.handles.clone(),
next_index: self.next_index,
biased: self.biased,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crossbeam-channel/src/select_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ macro_rules! crossbeam_channel_internal {
const _LEN: usize = $crate::crossbeam_channel_internal!(@count ($($cases)*));
let _handle: &dyn $crate::internal::SelectHandle = &$crate::never::<()>();

#[allow(unused_mut)]
#[allow(unused_mut, clippy::zero_repeat_side_effects)]
let mut _sel = [(_handle, 0, ::std::ptr::null()); _LEN];

$crate::crossbeam_channel_internal!(
Expand Down
15 changes: 15 additions & 0 deletions crossbeam-channel/tests/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,3 +580,18 @@ fn channel_through_channel() {
})
.unwrap();
}

// If `Block` is created on the stack, the array of slots will multiply this `BigStruct` and
// probably overflow the thread stack. It's now directly created on the heap to avoid this.
#[test]
fn stack_overflow() {
const N: usize = 32_768;
struct BigStruct {
_data: [u8; N],
}

let (sender, receiver) = unbounded::<BigStruct>();
sender.send(BigStruct { _data: [0u8; N] }).unwrap();

for _data in receiver.try_iter() {}
}
1 change: 1 addition & 0 deletions crossbeam-channel/tests/select_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ fn fairness1() {
assert!(hits.iter().all(|x| *x >= COUNT / hits.len() / 2));
}

#[cfg_attr(crossbeam_sanitize, ignore)] // TODO: flaky: https://github.com/crossbeam-rs/crossbeam/issues/1094
#[test]
fn fairness2() {
#[cfg(miri)]
Expand Down
4 changes: 4 additions & 0 deletions crossbeam-deque/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Version 0.8.6

- Fix stack overflow when pushing large value to `Injector`. (#1146, #1147, #1159)

# Version 0.8.5

- Remove dependency on `cfg-if`. (#1072)
Expand Down
2 changes: 1 addition & 1 deletion crossbeam-deque/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name = "crossbeam-deque"
# - Update CHANGELOG.md
# - Update README.md (when increasing major or minor version)
# - Run './tools/publish.sh crossbeam-deque <version>'
version = "0.8.5"
version = "0.8.6"
edition = "2021"
rust-version = "1.61"
license = "MIT OR Apache-2.0"
Expand Down
Loading