#![allow(
clippy::disallowed_types,
reason = "Preserve upstream synchronization semantics."
)]
use super::{SendPtr, current_num_threads, join::join_context};
use std::{
collections::LinkedList,
iter,
marker::PhantomData,
mem, ptr, slice,
sync::{
Mutex,
atomic::{AtomicBool, Ordering},
},
};
pub(crate) fn map_collect_vec<I: Send, T: Send>(
items: Vec<I>,
map: &(impl Fn(I) -> T + Sync),
) -> Vec<T> {
let mut vec = Vec::new();
let len = items.len();
collect_with_consumer(&mut vec, len, |consumer| {
bridge(items, MapConsumer::new(consumer, map))
});
vec
}
pub(crate) fn try_map_collect_vec<I: Send, T: Send, E: Send>(
items: Vec<I>,
map: &(impl Fn(I) -> Result<T, E> + Sync),
) -> Result<Vec<T>, E> {
let saved_error = Mutex::new(None);
let list = {
let ok = ok(&saved_error);
let full = AtomicBool::new(false);
let consumer = WhileSomeConsumer {
base: ListVecConsumer,
full: &full,
};
bridge(
items,
MapConsumer::new(MapConsumer::new(consumer, &ok), map),
)
};
if let Some(error) = saved_error.into_inner().unwrap() {
drop(list);
return Err(error);
}
let mut collection = Vec::with_capacity(list.iter().map(Vec::len).sum());
for mut other in list {
collection.append(&mut other);
}
Ok(collection)
}
pub(crate) fn map_init_collect_vec<I: Send, S: Send, T: Send>(
items: Vec<I>,
init: &(impl Fn() -> S + Sync),
map: &(impl Fn(&mut S, I) -> T + Sync),
) -> Vec<T> {
let mut vec = Vec::new();
let len = items.len();
collect_with_consumer(&mut vec, len, |consumer| {
bridge(items, MapInitConsumer::new(consumer, init, map))
});
vec
}
pub(crate) fn fold_init<I: Send, S: Send, R: Send>(
items: Vec<I>,
init: &(impl Fn() -> S + Sync),
identity: &(impl Fn() -> R + Sync),
fold: &(impl Fn(R, &mut S, I) -> R + Sync),
reduce: &(impl Fn(R, R) -> R + Sync),
) -> R {
let fold_identity = || (init(), identity());
let fold_op = |(mut init_val, acc): (S, R), item: I| {
let new_acc = fold(acc, &mut init_val, item);
(init_val, new_acc)
};
let map_op = |(_, acc): (S, R)| acc;
let consumer = FoldConsumer {
base: MapConsumer::new(
ReduceConsumer {
identity,
reduce_op: reduce,
},
&map_op,
),
fold_op: &fold_op,
identity: &fold_identity,
};
bridge(items, consumer)
}
pub(crate) fn try_fold<I: Send, R: Send, E: Send>(
items: Vec<I>,
identity: &(impl Fn() -> R + Sync),
fold: &(impl Fn(R, I) -> Result<R, E> + Sync),
reduce: &(impl Fn(R, R) -> R + Sync),
) -> Result<R, E> {
let reduce_op = |a: R, b: R| -> Result<R, E> { Ok(reduce(a, b)) };
let full = AtomicBool::new(false);
let consumer = TryFoldConsumer {
base: TryReduceConsumer {
identity,
reduce_op: &reduce_op,
full: &full,
},
identity,
fold_op: fold,
};
bridge(items, consumer)
}
trait Consumer<Item>: Send + Sized {
type Folder: Folder<Item, Result = Self::Result>;
type Reducer: Reducer<Self::Result>;
type Result: Send;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer);
fn into_folder(self) -> Self::Folder;
fn full(&self) -> bool;
}
trait Folder<Item>: Sized {
type Result;
fn consume(self, item: Item) -> Self;
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = Item>,
{
for item in iter {
self = self.consume(item);
if self.full() {
break;
}
}
self
}
fn complete(self) -> Self::Result;
fn full(&self) -> bool;
}
trait Reducer<Result> {
fn reduce(self, left: Result, right: Result) -> Result;
}
#[derive(Clone, Copy)]
struct Splitter {
splits: usize,
}
impl Splitter {
#[inline]
fn new() -> Self {
Self {
splits: current_num_threads(),
}
}
#[inline]
fn try_split(&mut self, stolen: bool) -> bool {
let Self { splits } = *self;
if stolen {
self.splits = Ord::max(current_num_threads(), self.splits / 2);
true
} else if splits > 0 {
self.splits /= 2;
true
} else {
false
}
}
}
#[derive(Clone, Copy)]
struct LengthSplitter {
inner: Splitter,
min: usize,
}
impl LengthSplitter {
#[inline]
fn new(min: usize, max: usize, len: usize) -> Self {
let mut splitter = Self {
inner: Splitter::new(),
min: Ord::max(min, 1),
};
let min_splits = len / Ord::max(max, 1);
if min_splits > splitter.inner.splits {
splitter.inner.splits = min_splits;
}
splitter
}
#[inline]
fn try_split(&mut self, len: usize, stolen: bool) -> bool {
len / 2 >= self.min && self.inner.try_split(stolen)
}
}
fn with_producer<T: Send, R>(
mut vec: Vec<T>,
callback: impl FnOnce(DrainProducer<'_, T>) -> R,
) -> R {
let len = vec.len();
unsafe {
vec.set_len(0);
let producer = DrainProducer::from_vec(&mut vec, len);
callback(producer)
}
}
fn bridge<T: Send, C: Consumer<T>>(vec: Vec<T>, consumer: C) -> C::Result {
let len = vec.len();
with_producer(vec, |producer| {
bridge_producer_consumer(len, producer, consumer)
})
}
fn bridge_producer_consumer<T: Send, C: Consumer<T>>(
len: usize,
producer: DrainProducer<'_, T>,
consumer: C,
) -> C::Result {
let splitter = LengthSplitter::new(1, usize::MAX, len);
return helper(len, false, splitter, producer, consumer);
fn helper<T: Send, C: Consumer<T>>(
len: usize,
migrated: bool,
mut splitter: LengthSplitter,
producer: DrainProducer<'_, T>,
consumer: C,
) -> C::Result {
if consumer.full() {
consumer.into_folder().complete()
} else if splitter.try_split(len, migrated) {
let mid = len / 2;
let (left_producer, right_producer) = producer.split_at(mid);
let (left_consumer, right_consumer, reducer) = consumer.split_at(mid);
let (left_result, right_result) = join_context(
|context| {
helper(
mid,
context.migrated(),
splitter,
left_producer,
left_consumer,
)
},
|context| {
helper(
len - mid,
context.migrated(),
splitter,
right_producer,
right_consumer,
)
},
);
reducer.reduce(left_result, right_result)
} else {
producer.fold_with(consumer.into_folder()).complete()
}
}
}
struct DrainProducer<'data, T: Send> {
slice: &'data mut [T],
}
impl<'data, T: Send> DrainProducer<'data, T> {
const unsafe fn new(slice: &'data mut [T]) -> Self {
Self { slice }
}
unsafe fn from_vec(vec: &'data mut Vec<T>, len: usize) -> Self {
let start = vec.len();
assert!(vec.capacity() - start >= len);
unsafe {
let ptr = vec.as_mut_ptr().add(start);
Self::new(slice::from_raw_parts_mut(ptr, len))
}
}
fn split_at(mut self, index: usize) -> (Self, Self) {
let slice = mem::take(&mut self.slice);
let (left, right) = slice.split_at_mut(index);
unsafe { (Self::new(left), Self::new(right)) }
}
fn fold_with<F>(self, folder: F) -> F
where
F: Folder<T>,
{
folder.consume_iter(self)
}
}
impl<'data, T: 'data + Send> IntoIterator for DrainProducer<'data, T> {
type Item = T;
type IntoIter = SliceDrain<'data, T>;
fn into_iter(mut self) -> Self::IntoIter {
let slice = mem::take(&mut self.slice);
SliceDrain {
iter: slice.iter_mut(),
}
}
}
impl<'data, T: 'data + Send> Drop for DrainProducer<'data, T> {
fn drop(&mut self) {
let slice_ptr: *mut [T] = mem::take::<&'data mut [T]>(&mut self.slice);
unsafe { ptr::drop_in_place::<[T]>(slice_ptr) };
}
}
struct SliceDrain<'data, T> {
iter: slice::IterMut<'data, T>,
}
impl<'data, T: 'data> Iterator for SliceDrain<'data, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
let ptr: *const T = self.iter.next()?;
Some(unsafe { ptr::read(ptr) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn count(self) -> usize {
self.iter.len()
}
}
impl<'data, T: 'data> DoubleEndedIterator for SliceDrain<'data, T> {
fn next_back(&mut self) -> Option<Self::Item> {
let ptr: *const T = self.iter.next_back()?;
Some(unsafe { ptr::read(ptr) })
}
}
impl<'data, T: 'data> ExactSizeIterator for SliceDrain<'data, T> {
fn len(&self) -> usize {
self.iter.len()
}
}
impl<'data, T: 'data> iter::FusedIterator for SliceDrain<'data, T> {}
impl<'data, T: 'data> Drop for SliceDrain<'data, T> {
fn drop(&mut self) {
let slice_ptr: *mut [T] = mem::replace(&mut self.iter, [].iter_mut()).into_slice();
unsafe { ptr::drop_in_place::<[T]>(slice_ptr) };
}
}
fn collect_with_consumer<T, F>(vec: &mut Vec<T>, len: usize, scope_fn: F)
where
T: Send,
F: FnOnce(CollectConsumer<'_, T>) -> CollectResult<'_, T>,
{
vec.reserve(len);
let result = scope_fn(CollectConsumer::appender(vec, len));
let actual_writes = result.len();
assert!(
actual_writes == len,
"expected {len} total writes, but got {actual_writes}"
);
result.release_ownership();
let new_len = vec.len() + len;
unsafe {
vec.set_len(new_len);
}
}
struct CollectConsumer<'c, T: Send> {
start: SendPtr<T>,
len: usize,
marker: PhantomData<&'c mut T>,
}
impl<'c, T: Send + 'c> CollectConsumer<'c, T> {
fn appender(vec: &'c mut Vec<T>, len: usize) -> Self {
let start = vec.len();
assert!(vec.capacity() - start >= len);
unsafe { Self::new(vec.as_mut_ptr().add(start), len) }
}
const unsafe fn new(start: *mut T, len: usize) -> Self {
Self {
start: SendPtr(start),
len,
marker: PhantomData,
}
}
}
#[must_use]
struct CollectResult<'c, T> {
start: SendPtr<T>,
total_len: usize,
initialized_len: usize,
invariant_lifetime: PhantomData<&'c mut &'c mut [T]>,
}
unsafe impl<'c, T> Send for CollectResult<'c, T> where T: Send {}
impl<'c, T> CollectResult<'c, T> {
const fn len(&self) -> usize {
self.initialized_len
}
fn release_ownership(mut self) -> usize {
let ret = self.initialized_len;
self.initialized_len = 0;
ret
}
}
impl<'c, T> Drop for CollectResult<'c, T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
self.start.0,
self.initialized_len,
));
}
}
}
impl<'c, T: Send + 'c> Consumer<T> for CollectConsumer<'c, T> {
type Folder = CollectResult<'c, T>;
type Reducer = CollectReducer;
type Result = CollectResult<'c, T>;
fn split_at(self, index: usize) -> (Self, Self, CollectReducer) {
let CollectConsumer { start, len, .. } = self;
unsafe {
assert!(index <= len);
(
Self::new(start.0, index),
Self::new(start.0.add(index), len - index),
CollectReducer,
)
}
}
fn into_folder(self) -> Self::Folder {
CollectResult {
start: self.start,
total_len: self.len,
initialized_len: 0,
invariant_lifetime: PhantomData,
}
}
fn full(&self) -> bool {
false
}
}
impl<'c, T: Send + 'c> Folder<T> for CollectResult<'c, T> {
type Result = Self;
fn consume(mut self, item: T) -> Self {
assert!(
self.initialized_len < self.total_len,
"too many values pushed to consumer"
);
unsafe {
self.start.0.add(self.initialized_len).write(item);
self.initialized_len += 1;
}
self
}
fn complete(self) -> Self::Result {
self
}
fn full(&self) -> bool {
false
}
}
struct CollectReducer;
impl<'c, T> Reducer<CollectResult<'c, T>> for CollectReducer {
fn reduce(
self,
mut left: CollectResult<'c, T>,
right: CollectResult<'c, T>,
) -> CollectResult<'c, T> {
unsafe {
let left_end = left.start.0.add(left.initialized_len);
if left_end == right.start.0 {
left.total_len += right.total_len;
left.initialized_len += right.release_ownership();
}
left
}
}
}
struct ListVecConsumer;
struct ListVecFolder<T> {
vec: Vec<T>,
}
impl<T: Send> Consumer<T> for ListVecConsumer {
type Folder = ListVecFolder<T>;
type Reducer = ListReducer;
type Result = LinkedList<Vec<T>>;
fn split_at(self, _index: usize) -> (Self, Self, Self::Reducer) {
(Self, Self, ListReducer)
}
fn into_folder(self) -> Self::Folder {
ListVecFolder { vec: Vec::new() }
}
fn full(&self) -> bool {
false
}
}
impl<T> Folder<T> for ListVecFolder<T> {
type Result = LinkedList<Vec<T>>;
fn consume(mut self, item: T) -> Self {
self.vec.push(item);
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.vec.extend(iter);
self
}
fn complete(self) -> Self::Result {
let mut list = LinkedList::new();
if !self.vec.is_empty() {
list.push_back(self.vec);
}
list
}
fn full(&self) -> bool {
false
}
}
struct ListReducer;
impl<T> Reducer<LinkedList<T>> for ListReducer {
fn reduce(self, mut left: LinkedList<T>, mut right: LinkedList<T>) -> LinkedList<T> {
left.append(&mut right);
left
}
}
struct MapConsumer<'f, C, F> {
base: C,
map_op: &'f F,
}
impl<'f, C, F> MapConsumer<'f, C, F> {
const fn new(base: C, map_op: &'f F) -> Self {
Self { base, map_op }
}
}
impl<'f, T, R, C, F> Consumer<T> for MapConsumer<'f, C, F>
where
C: Consumer<F::Output>,
F: Fn(T) -> R + Sync,
R: Send,
{
type Folder = MapFolder<'f, C::Folder, F>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
MapConsumer::new(left, self.map_op),
MapConsumer::new(right, self.map_op),
reducer,
)
}
fn into_folder(self) -> Self::Folder {
MapFolder {
base: self.base.into_folder(),
map_op: self.map_op,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
struct MapFolder<'f, C, F> {
base: C,
map_op: &'f F,
}
impl<'f, T, R, C, F> Folder<T> for MapFolder<'f, C, F>
where
C: Folder<F::Output>,
F: Fn(T) -> R,
{
type Result = C::Result;
fn consume(self, item: T) -> Self {
let mapped_item = (self.map_op)(item);
MapFolder {
base: self.base.consume(mapped_item),
map_op: self.map_op,
}
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.base = self.base.consume_iter(iter.into_iter().map(self.map_op));
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.base.full()
}
}
struct MapInitConsumer<'f, C, INIT, F> {
base: C,
init: &'f INIT,
map_op: &'f F,
}
impl<'f, C, INIT, F> MapInitConsumer<'f, C, INIT, F> {
const fn new(base: C, init: &'f INIT, map_op: &'f F) -> Self {
Self { base, init, map_op }
}
}
impl<'f, T, INIT, U, R, C, F> Consumer<T> for MapInitConsumer<'f, C, INIT, F>
where
C: Consumer<R>,
INIT: Fn() -> U + Sync,
F: Fn(&mut U, T) -> R + Sync,
R: Send,
{
type Folder = MapWithFolder<'f, C::Folder, U, F>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
MapInitConsumer::new(left, self.init, self.map_op),
MapInitConsumer::new(right, self.init, self.map_op),
reducer,
)
}
fn into_folder(self) -> Self::Folder {
MapWithFolder {
base: self.base.into_folder(),
item: (self.init)(),
map_op: self.map_op,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
struct MapWithFolder<'f, C, U, F> {
base: C,
item: U,
map_op: &'f F,
}
impl<'f, T, U, R, C, F> Folder<T> for MapWithFolder<'f, C, U, F>
where
C: Folder<R>,
F: Fn(&mut U, T) -> R,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
let mapped_item = (self.map_op)(&mut self.item, item);
self.base = self.base.consume(mapped_item);
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
fn with<'f, T, U, R>(
item: &'f mut U,
map_op: impl Fn(&mut U, T) -> R + 'f,
) -> impl FnMut(T) -> R + 'f {
move |x| map_op(item, x)
}
{
let mapped_iter = iter.into_iter().map(with(&mut self.item, self.map_op));
self.base = self.base.consume_iter(mapped_iter);
}
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.base.full()
}
}
fn ok<T, E>(saved: &Mutex<Option<E>>) -> impl Fn(Result<T, E>) -> Option<T> + '_ {
move |item| match item {
Ok(item) => Some(item),
Err(error) => {
if let Ok(mut guard) = saved.try_lock()
&& guard.is_none()
{
*guard = Some(error);
}
None
}
}
}
struct WhileSomeConsumer<'f, C> {
base: C,
full: &'f AtomicBool,
}
impl<'f, T, C> Consumer<Option<T>> for WhileSomeConsumer<'f, C>
where
C: Consumer<T>,
T: Send,
{
type Folder = WhileSomeFolder<'f, C::Folder>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
WhileSomeConsumer { base: left, ..self },
WhileSomeConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
WhileSomeFolder {
base: self.base.into_folder(),
full: self.full,
}
}
fn full(&self) -> bool {
self.full.load(Ordering::Relaxed) || self.base.full()
}
}
struct WhileSomeFolder<'f, C> {
base: C,
full: &'f AtomicBool,
}
impl<'f, T, C> Folder<Option<T>> for WhileSomeFolder<'f, C>
where
C: Folder<T>,
{
type Result = C::Result;
fn consume(mut self, item: Option<T>) -> Self {
match item {
Some(item) => self.base = self.base.consume(item),
None => self.full.store(true, Ordering::Relaxed),
}
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = Option<T>>,
{
fn some<T>(full: &AtomicBool) -> impl Fn(&Option<T>) -> bool + '_ {
move |x| match *x {
Some(_) => !full.load(Ordering::Relaxed),
None => {
full.store(true, Ordering::Relaxed);
false
}
}
}
self.base = self.base.consume_iter(
iter.into_iter()
.take_while(some(self.full))
.map(Option::unwrap),
);
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.full.load(Ordering::Relaxed) || self.base.full()
}
}
struct FoldConsumer<'c, C, ID, F> {
base: C,
fold_op: &'c F,
identity: &'c ID,
}
impl<'r, U, T, C, ID, F> Consumer<T> for FoldConsumer<'r, C, ID, F>
where
C: Consumer<U>,
F: Fn(U, T) -> U + Sync,
ID: Fn() -> U + Sync,
U: Send,
{
type Folder = FoldFolder<'r, C::Folder, U, F>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
FoldConsumer { base: left, ..self },
FoldConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
FoldFolder {
base: self.base.into_folder(),
item: (self.identity)(),
fold_op: self.fold_op,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
struct FoldFolder<'r, C, ID, F> {
base: C,
fold_op: &'r F,
item: ID,
}
impl<'r, C, ID, F, T> Folder<T> for FoldFolder<'r, C, ID, F>
where
C: Folder<ID>,
F: Fn(ID, T) -> ID + Sync,
{
type Result = C::Result;
fn consume(self, item: T) -> Self {
let item = (self.fold_op)(self.item, item);
FoldFolder {
base: self.base,
fold_op: self.fold_op,
item,
}
}
fn consume_iter<I>(self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
fn not_full<C, ID, T>(base: &C) -> impl Fn(&T) -> bool + '_
where
C: Folder<ID>,
{
move |_| !base.full()
}
let base = self.base;
let item = iter
.into_iter()
.take_while(not_full(&base))
.fold(self.item, self.fold_op);
FoldFolder {
base,
item,
fold_op: self.fold_op,
}
}
fn complete(self) -> C::Result {
self.base.consume(self.item).complete()
}
fn full(&self) -> bool {
self.base.full()
}
}
struct ReduceConsumer<'r, R, ID> {
identity: &'r ID,
reduce_op: &'r R,
}
impl<'r, R, ID> Copy for ReduceConsumer<'r, R, ID> {}
impl<'r, R, ID> Clone for ReduceConsumer<'r, R, ID> {
fn clone(&self) -> Self {
*self
}
}
impl<'r, R, ID, T> Consumer<T> for ReduceConsumer<'r, R, ID>
where
R: Fn(T, T) -> T + Sync,
ID: Fn() -> T + Sync,
T: Send,
{
type Folder = ReduceFolder<'r, R, T>;
type Reducer = Self;
type Result = T;
fn split_at(self, _index: usize) -> (Self, Self, Self) {
(self, self, self)
}
fn into_folder(self) -> Self::Folder {
ReduceFolder {
reduce_op: self.reduce_op,
item: (self.identity)(),
}
}
fn full(&self) -> bool {
false
}
}
impl<'r, R, ID, T> Reducer<T> for ReduceConsumer<'r, R, ID>
where
R: Fn(T, T) -> T + Sync,
{
fn reduce(self, left: T, right: T) -> T {
(self.reduce_op)(left, right)
}
}
struct ReduceFolder<'r, R, T> {
reduce_op: &'r R,
item: T,
}
impl<'r, R, T> Folder<T> for ReduceFolder<'r, R, T>
where
R: Fn(T, T) -> T,
{
type Result = T;
fn consume(self, item: T) -> Self {
ReduceFolder {
reduce_op: self.reduce_op,
item: (self.reduce_op)(self.item, item),
}
}
fn consume_iter<I>(self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
ReduceFolder {
reduce_op: self.reduce_op,
item: iter.into_iter().fold(self.item, self.reduce_op),
}
}
fn complete(self) -> T {
self.item
}
fn full(&self) -> bool {
false
}
}
struct TryFoldConsumer<'c, C, ID, F> {
base: C,
identity: &'c ID,
fold_op: &'c F,
}
impl<'r, R, E, T, C, ID, F> Consumer<T> for TryFoldConsumer<'r, C, ID, F>
where
C: Consumer<Result<R, E>>,
F: Fn(R, T) -> Result<R, E> + Sync,
ID: Fn() -> R + Sync,
R: Send,
E: Send,
{
type Folder = TryFoldFolder<'r, C::Folder, R, E, F>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
TryFoldConsumer { base: left, ..self },
TryFoldConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
TryFoldFolder {
base: self.base.into_folder(),
control: Ok((self.identity)()),
fold_op: self.fold_op,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
struct TryFoldFolder<'r, C, R, E, F> {
base: C,
fold_op: &'r F,
control: Result<R, E>,
}
impl<'r, C, R, E, F, T> Folder<T> for TryFoldFolder<'r, C, R, E, F>
where
C: Folder<Result<R, E>>,
F: Fn(R, T) -> Result<R, E> + Sync,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
let fold_op = self.fold_op;
if let Ok(acc) = self.control {
self.control = fold_op(acc, item);
}
self
}
fn complete(self) -> C::Result {
self.base.consume(self.control).complete()
}
fn full(&self) -> bool {
match self.control {
Err(_) => true,
_ => self.base.full(),
}
}
}
struct TryReduceConsumer<'r, R, ID> {
identity: &'r ID,
reduce_op: &'r R,
full: &'r AtomicBool,
}
impl<'r, R, ID> Copy for TryReduceConsumer<'r, R, ID> {}
impl<'r, R, ID> Clone for TryReduceConsumer<'r, R, ID> {
fn clone(&self) -> Self {
*self
}
}
impl<'r, R, ID, T, E> Consumer<Result<T, E>> for TryReduceConsumer<'r, R, ID>
where
R: Fn(T, T) -> Result<T, E> + Sync,
ID: Fn() -> T + Sync,
T: Send,
E: Send,
{
type Folder = TryReduceFolder<'r, R, T, E>;
type Reducer = Self;
type Result = Result<T, E>;
fn split_at(self, _index: usize) -> (Self, Self, Self) {
(self, self, self)
}
fn into_folder(self) -> Self::Folder {
TryReduceFolder {
reduce_op: self.reduce_op,
control: Ok((self.identity)()),
full: self.full,
}
}
fn full(&self) -> bool {
self.full.load(Ordering::Relaxed)
}
}
impl<'r, R, ID, T, E> Reducer<Result<T, E>> for TryReduceConsumer<'r, R, ID>
where
R: Fn(T, T) -> Result<T, E> + Sync,
{
fn reduce(self, left: Result<T, E>, right: Result<T, E>) -> Result<T, E> {
match (left, right) {
(Ok(left), Ok(right)) => (self.reduce_op)(left, right),
(Err(r), _) | (_, Err(r)) => Err(r),
}
}
}
struct TryReduceFolder<'r, R, T, E> {
reduce_op: &'r R,
control: Result<T, E>,
full: &'r AtomicBool,
}
impl<'r, R, T, E> Folder<Result<T, E>> for TryReduceFolder<'r, R, T, E>
where
R: Fn(T, T) -> Result<T, E>,
{
type Result = Result<T, E>;
fn consume(mut self, item: Result<T, E>) -> Self {
let reduce_op = self.reduce_op;
self.control = match (self.control, item) {
(Ok(left), Ok(right)) => reduce_op(left, right),
(control @ Err(_), _) | (_, control @ Err(_)) => control,
};
if self.control.is_err() {
self.full.store(true, Ordering::Relaxed);
}
self
}
fn complete(self) -> Result<T, E> {
self.control
}
fn full(&self) -> bool {
match self.control {
Err(_) => true,
_ => self.full.load(Ordering::Relaxed),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rayon::{ThreadPool, ThreadPoolBuilder, current_thread_index, join};
use commonware_utils::{sync::Mutex, test_rng};
use rand::{Rng as _, RngExt as _};
use std::{
fmt,
panic::{self, AssertUnwindSafe},
sync::atomic::AtomicUsize,
thread::{self, Result as ThreadResult},
time::{Duration, Instant},
};
const LENGTHS: [usize; 6] = [0, 1, 2, 7, 1000, 100_000];
fn pool(threads: usize) -> ThreadPool {
ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap()
}
fn in_pool<R: Send>(threads: usize, op: impl FnOnce() -> R + Send) -> R {
pool(threads).install(op)
}
fn wait_until(condition: impl Fn() -> bool) {
let start = Instant::now();
while !condition() {
assert!(
start.elapsed() < Duration::from_secs(10),
"the condition must hold within 10 seconds"
);
thread::sleep(Duration::from_millis(1));
}
}
fn append<T>(mut a: Vec<T>, b: Vec<T>) -> Vec<T> {
a.extend(b);
a
}
struct Ledger {
calls: Vec<AtomicUsize>,
inputs: Vec<AtomicUsize>,
created: Vec<AtomicUsize>,
outputs: Vec<AtomicUsize>,
}
struct Input<'a> {
index: usize,
ledger: &'a Ledger,
}
impl Drop for Input<'_> {
fn drop(&mut self) {
self.ledger.inputs[self.index].fetch_add(1, Ordering::SeqCst);
}
}
struct Output<'a> {
index: usize,
ledger: &'a Ledger,
}
impl Drop for Output<'_> {
fn drop(&mut self) {
self.ledger.outputs[self.index].fetch_add(1, Ordering::SeqCst);
}
}
impl Ledger {
fn new(len: usize) -> Self {
let counters = || (0..len).map(|_| AtomicUsize::new(0)).collect();
Self {
calls: counters(),
inputs: counters(),
created: counters(),
outputs: counters(),
}
}
fn inputs(&self) -> Vec<Input<'_>> {
(0..self.calls.len())
.map(|index| Input {
index,
ledger: self,
})
.collect()
}
fn call(&self, input: &Input<'_>) -> usize {
self.calls[input.index].fetch_add(1, Ordering::SeqCst);
input.index
}
fn output(&self, index: usize) -> Output<'_> {
self.created[index].fetch_add(1, Ordering::SeqCst);
Output {
index,
ledger: self,
}
}
fn called(&self) -> Vec<usize> {
(0..self.calls.len())
.filter(|&index| self.calls[index].load(Ordering::SeqCst) > 0)
.collect()
}
fn assert_dropped_once(&self) {
for index in 0..self.calls.len() {
let calls = self.calls[index].load(Ordering::SeqCst);
assert!(
calls <= 1,
"item {index} must be called at most once, got {calls} calls"
);
let drops = self.inputs[index].load(Ordering::SeqCst);
assert_eq!(drops, 1, "item {index} must be dropped exactly once");
let created = self.created[index].load(Ordering::SeqCst);
assert!(
created <= calls,
"item {index} must make at most one output per call, made {created}"
);
let drops = self.outputs[index].load(Ordering::SeqCst);
assert_eq!(
drops, created,
"each output of item {index} must be dropped exactly once"
);
}
}
}
#[test]
fn maps_keep_the_order_of_their_items() {
let mut rng = test_rng();
for threads in [1, 2, 4] {
let pool = pool(threads);
for len in LENGTHS {
let items: Vec<u64> = (0..len).map(|_| rng.next_u64()).collect();
let expected: Vec<String> = items.iter().map(u64::to_string).collect();
let mapped = pool.install(|| map_collect_vec(items.clone(), &|x| x.to_string()));
assert_eq!(mapped, expected);
let mapped = pool.install(|| {
map_init_collect_vec(items.clone(), &|| 0usize, &|seen, x| {
*seen += 1;
x.to_string()
})
});
assert_eq!(mapped, expected);
let mapped = pool.install(|| {
try_map_collect_vec(items.clone(), &|x| Ok::<_, ()>(x.to_string()))
});
assert_eq!(mapped, Ok(expected));
}
}
}
#[test]
fn folds_reduce_in_the_order_of_their_items() {
let mut rng = test_rng();
let concat = |mut a: String, b: String| {
a.push_str(&b);
a
};
for threads in [1, 2, 4] {
let pool = pool(threads);
for len in LENGTHS {
let items: Vec<String> = (0..len)
.map(|_| {
(0..rng.random_range(1..4))
.map(|_| char::from(b'a' + rng.random_range(0..26u8)))
.collect()
})
.collect();
let expected = items.concat();
let folded = pool.install(|| {
fold_init(
items.clone(),
&|| 0usize,
&String::new,
&|mut acc, seen, item| {
*seen += 1;
acc.push_str(&item);
acc
},
&concat,
)
});
assert_eq!(folded, expected);
let folded = pool.install(|| {
try_fold(
items.clone(),
&String::new,
&|mut acc, item| {
acc.push_str(&item);
Ok::<_, ()>(acc)
},
&concat,
)
});
assert_eq!(folded, Ok(expected));
}
}
}
#[test]
fn reductions_follow_the_tree_of_splits() {
let pool = pool(1);
let group = |a: String, b: String| format!("({a}{b})");
for len in [0, 1, 2, 7, 1000] {
let items: Vec<String> = (0..len).map(|i| format!("{i},")).collect();
let mid = len / 2;
let (expected, pieces) = if len < 2 {
(format!("({})", items.concat()), 1)
} else {
let (left, right) = items.split_at(mid);
(format!("(({})({}))", left.concat(), right.concat()), 2)
};
let states = AtomicUsize::new(0);
let folded = pool.install(|| {
fold_init(
items.clone(),
&|| states.fetch_add(1, Ordering::SeqCst),
&String::new,
&|mut acc, _, item| {
acc.push_str(&item);
acc
},
&group,
)
});
assert_eq!(folded, expected);
assert_eq!(states.load(Ordering::SeqCst), pieces);
let folded = pool.install(|| {
try_fold(
items.clone(),
&String::new,
&|mut acc, item| {
acc.push_str(&item);
Ok::<_, ()>(acc)
},
&group,
)
});
assert_eq!(folded, Ok(expected));
}
}
#[test]
fn a_stolen_half_splits_again() {
const LEN: usize = 1024;
struct Piece<'a> {
worker: usize,
ordinal: usize,
len: usize,
finished: &'a [AtomicUsize; 2],
pieces: &'a Mutex<Vec<(usize, usize)>>,
}
impl Drop for Piece<'_> {
fn drop(&mut self) {
self.pieces.lock().push((self.worker, self.len));
self.finished[self.worker].fetch_add(1, Ordering::SeqCst);
}
}
let started = [AtomicUsize::new(0), AtomicUsize::new(0)];
let finished = [AtomicUsize::new(0), AtomicUsize::new(0)];
let mapped = [AtomicUsize::new(0), AtomicUsize::new(0)];
let pieces = Mutex::new(Vec::new());
let (first, outputs) = pool(2).install(|| {
let first = current_thread_index().unwrap();
let second = 1 - first;
let (outputs, ()) = join(
|| {
map_init_collect_vec(
(0..LEN).collect(),
&|| {
let worker = current_thread_index().unwrap();
Piece {
worker,
ordinal: started[worker].fetch_add(1, Ordering::SeqCst),
len: 0,
finished: &finished,
pieces: &pieces,
}
},
&|piece, item| {
if piece.worker == first && piece.ordinal == 1 && piece.len == 0 {
wait_until(|| mapped[second].load(Ordering::SeqCst) == LEN / 2);
}
piece.len += 1;
mapped[piece.worker].fetch_add(1, Ordering::SeqCst);
item
},
)
},
|| wait_until(|| finished[first].load(Ordering::SeqCst) >= 1),
);
(first, outputs)
});
assert_eq!(outputs, (0..LEN).collect::<Vec<_>>());
let pieces = pieces.into_inner();
let lens = |worker| {
pieces
.iter()
.filter(|&&(w, _)| w == worker)
.map(|&(_, len)| len)
.collect::<Vec<_>>()
};
assert_eq!(lens(first), [LEN / 4; 2]);
assert_eq!(lens(1 - first), [LEN / 16; 8]);
}
#[test]
fn try_map_stops_at_an_error_and_drops_every_item_once() {
const LEN: usize = 1000;
for (threads, errors) in [(1, &[10][..]), (2, &[10, 500, 999]), (4, &[10, 500, 999])] {
let ledger = Ledger::new(LEN);
let result = in_pool(threads, || {
try_map_collect_vec(ledger.inputs(), &|input| {
let index = ledger.call(&input);
if errors.contains(&index) {
Err(index)
} else {
Ok(ledger.output(index))
}
})
});
let error = result.err().expect("an item must fail");
assert!(errors.contains(&error));
if threads == 1 {
assert_eq!(ledger.called(), (0..=10).collect::<Vec<_>>());
}
ledger.assert_dropped_once();
}
let ledger = Ledger::new(LEN);
let outputs = in_pool(4, || {
try_map_collect_vec(ledger.inputs(), &|input| {
Ok::<_, ()>(ledger.output(ledger.call(&input)))
})
})
.unwrap();
assert!(outputs.iter().map(|output| output.index).eq(0..LEN));
drop(outputs);
ledger.assert_dropped_once();
}
#[test]
fn try_map_drops_every_item_when_an_output_drop_panics() {
struct PanickingOutput<'a> {
output: Output<'a>,
panic_at: usize,
}
impl Drop for PanickingOutput<'_> {
fn drop(&mut self) {
assert_ne!(
self.output.index, self.panic_at,
"output index must differ from the panic index"
);
}
}
const LEN: usize = 16;
for panic_at in [0, LEN / 2, LEN - 2] {
let ledger = Ledger::new(LEN);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
in_pool(1, || {
let _ = try_map_collect_vec(ledger.inputs(), &|input| {
let index = ledger.call(&input);
if index == LEN - 1 {
Err(input)
} else {
Ok(PanickingOutput {
output: ledger.output(index),
panic_at,
})
}
});
});
}));
assert_is_panic_with_message(&result, "output index must differ from the panic index");
assert_eq!(ledger.called(), (0..LEN).collect::<Vec<_>>());
ledger.assert_dropped_once();
}
}
#[test]
fn try_fold_stops_at_an_error_and_drops_every_item_once() {
const LEN: usize = 1000;
for (threads, errors) in [(1, &[10][..]), (2, &[10, 500, 999]), (4, &[10, 500, 999])] {
let ledger = Ledger::new(LEN);
let result = in_pool(threads, || {
try_fold(
ledger.inputs(),
&Vec::new,
&|mut acc, input| {
let index = ledger.call(&input);
if errors.contains(&index) {
return Err(index);
}
acc.push(input);
Ok(acc)
},
&append,
)
});
let error = result.err().expect("an item must fail");
assert!(errors.contains(&error));
if threads == 1 {
assert_eq!(ledger.called(), (0..=10).collect::<Vec<_>>());
}
ledger.assert_dropped_once();
}
let ledger = Ledger::new(LEN);
let items = in_pool(4, || {
try_fold(
ledger.inputs(),
&Vec::new,
&|mut acc, input| {
ledger.call(&input);
acc.push(input);
Ok::<_, ()>(acc)
},
&append,
)
})
.unwrap();
assert!(items.iter().map(|input| input.index).eq(0..LEN));
drop(items);
ledger.assert_dropped_once();
}
#[test]
fn a_panicking_map_drops_every_item_and_output_once() {
const LEN: usize = 1000;
type Map = for<'a> fn(&'a Ledger, Vec<Input<'a>>, usize);
let maps: [Map; 3] = [
|ledger, inputs, panic_at| {
map_collect_vec(inputs, &|input| {
let index = ledger.call(&input);
assert_ne!(index, panic_at, "map panicked");
ledger.output(index)
});
},
|ledger, inputs, panic_at| {
map_init_collect_vec(inputs, &|| (), &|_, input| {
let index = ledger.call(&input);
assert_ne!(index, panic_at, "map panicked");
ledger.output(index)
});
},
|ledger, inputs, panic_at| {
let _ = try_map_collect_vec(inputs, &|input| {
let index = ledger.call(&input);
assert_ne!(index, panic_at, "map panicked");
Ok::<_, ()>(ledger.output(index))
});
},
];
for map in maps {
for threads in [1, 2, 4] {
for panic_at in [0, 10, LEN / 2, LEN - 1] {
let ledger = Ledger::new(LEN);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
in_pool(threads, || map(&ledger, ledger.inputs(), panic_at));
}));
assert_is_panic_with_message(&result, "map panicked");
if threads == 1 && panic_at < LEN / 2 {
let expected: Vec<_> = (0..=panic_at).chain(LEN / 2..LEN).collect();
assert_eq!(ledger.called(), expected);
}
ledger.assert_dropped_once();
}
}
}
}
#[test]
fn a_panicking_fold_drops_every_item_once() {
const LEN: usize = 1000;
for threads in [1, 2, 4] {
for panic_at in [0, 10, LEN / 2, LEN - 1] {
let ledger = Ledger::new(LEN);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
in_pool(threads, || {
fold_init(
ledger.inputs(),
&|| (),
&Vec::new,
&|mut acc, _, input| {
assert_ne!(ledger.call(&input), panic_at, "fold panicked");
acc.push(input);
acc
},
&append,
)
});
}));
assert_is_panic_with_message(&result, "fold panicked");
ledger.assert_dropped_once();
let ledger = Ledger::new(LEN);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
let _ = in_pool(threads, || {
try_fold(
ledger.inputs(),
&Vec::new,
&|mut acc, input| {
assert_ne!(ledger.call(&input), panic_at, "fold panicked");
acc.push(input);
Ok::<_, ()>(acc)
},
&append,
)
});
}));
assert_is_panic_with_message(&result, "fold panicked");
ledger.assert_dropped_once();
}
}
}
#[test]
fn operations_require_a_worker() {
let operations: [fn(&Ledger); 5] = [
|ledger| {
map_collect_vec(ledger.inputs(), &|input| ledger.call(&input));
},
|ledger| {
let _ =
try_map_collect_vec(ledger.inputs(), &|input| Ok::<_, ()>(ledger.call(&input)));
},
|ledger| {
map_init_collect_vec(ledger.inputs(), &|| (), &|_, input| ledger.call(&input));
},
|ledger| {
fold_init(
ledger.inputs(),
&|| (),
&|| 0,
&|acc, _, input| acc + ledger.call(&input),
&|a, b| a + b,
);
},
|ledger| {
let _ = try_fold(
ledger.inputs(),
&|| 0,
&|acc, input| Ok::<_, ()>(acc + ledger.call(&input)),
&|a, b| a + b,
);
},
];
for operation in operations {
let ledger = Ledger::new(8);
let result = panic::catch_unwind(AssertUnwindSafe(|| operation(&ledger)));
assert_is_panic_with_message(
&result,
"the calling thread must be a worker of a thread pool",
);
assert!(ledger.called().is_empty());
ledger.assert_dropped_once();
}
}
#[test]
fn execute() {
let a: Vec<i32> = (0..1024).collect();
let b = in_pool(4, || map_collect_vec(a, &|i| i + 1));
let c: Vec<i32> = (0..1024).map(|i| i + 1).collect();
assert_eq!(b, c);
}
#[test]
fn check_move() {
let a = vec![vec![1, 2, 3]];
let ptr = a[0].as_ptr();
let b = in_pool(4, || map_collect_vec(a, &|v| v));
assert_eq!(ptr, b[0].as_ptr());
}
#[test]
fn check_drops() {
#[derive(Clone)]
struct DropCounter<'a>(&'a AtomicUsize);
impl Drop for DropCounter<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
let c = AtomicUsize::new(0);
let a = vec![DropCounter(&c); 10];
let b = in_pool(4, || map_collect_vec(a.clone(), &|x| x));
assert_eq!(c.load(Ordering::Relaxed), 0);
with_producer(b, |_| ());
assert_eq!(c.load(Ordering::Relaxed), 10);
with_producer(a, |producer| {
let (a, _) = producer.split_at(5);
a.into_iter().next();
});
assert_eq!(c.load(Ordering::Relaxed), 20);
}
#[commonware_macros::test_group("miri")]
#[test]
fn drain_producer_split_at() {
fn check_len<I: ExactSizeIterator>(iter: &I, len: usize) {
assert_eq!(iter.size_hint(), (len, Some(len)));
assert_eq!(iter.len(), len);
}
let v: Vec<_> = (0..10).collect();
let end = v.len() + 1;
for i in 0..end {
for j in i..end {
for k in j..end {
for reverse in [false, true] {
let result = with_producer(v.clone(), |producer| {
let (left, d) = producer.split_at(k);
let (a, mid) = left.split_at(i);
let (b, c) = mid.split_at(j - i);
let a = a.into_iter();
let b = b.into_iter();
let c = c.into_iter();
let d = d.into_iter();
check_len(&a, i);
check_len(&b, j - i);
check_len(&c, k - j);
let chain = a.chain(b).chain(c).chain(d);
if reverse {
chain.rev().collect::<Vec<_>>()
} else {
chain.collect()
}
});
if reverse {
assert!(result.iter().eq(v.iter().rev()));
} else {
assert_eq!(result, v);
}
}
}
}
}
}
#[commonware_macros::test_group("miri")]
#[test]
fn drain_producer_drops_unproduced_items() {
let items = || (0..10).map(|i| i.to_string()).collect::<Vec<_>>();
with_producer(items(), |_| ());
let produced = with_producer(items(), |producer| {
let (left, right) = producer.split_at(4);
let (_, mid) = right.split_at(3);
let mut iter = mid.into_iter();
let first = iter.next();
let last = iter.next_back();
drop(left);
(first, last)
});
assert_eq!(produced, (Some("7".to_string()), Some("9".to_string())));
}
#[test]
fn par_iter_collect_result() {
in_pool(4, || {
let a: Result<Vec<_>, ()> = (0_i32..2048).map(Ok).collect();
let b: Result<Vec<_>, ()> = try_map_collect_vec((0_i32..2048).collect(), &Ok);
assert_eq!(a, b);
let c: Result<Vec<_>, _> = try_map_collect_vec((0_i32..2048).collect(), &|x| {
if x == 1234 { Err(x) } else { Ok(x) }
});
assert_eq!(c, Err(1234));
let d: Result<Vec<_>, _> = try_map_collect_vec((0_i32..2048).collect(), &|x| {
if x % 100 == 99 { Err(x) } else { Ok(x) }
});
assert_eq!(d.map_err(|x| x % 100), Err(99));
});
}
#[test]
fn collect_drop_on_unwind() {
struct Recorddrop<'a>(i64, &'a Mutex<Vec<i64>>);
impl Drop for Recorddrop<'_> {
fn drop(&mut self) {
self.1.lock().push(self.0);
}
}
let test_collect_panic = |will_panic: bool| {
let test_vec_len = 1024;
let panic_point = 740;
let mut inserts = Mutex::new(Vec::new());
let mut drops = Mutex::new(Vec::new());
let a = (0..test_vec_len).collect::<Vec<_>>();
let b = (0..test_vec_len).collect::<Vec<_>>();
let _result = panic::catch_unwind(AssertUnwindSafe(|| {
let pairs = a.iter().copied().zip(b.iter().copied()).collect();
let result = in_pool(4, || {
map_collect_vec(pairs, &|(a, b)| {
if a > panic_point && will_panic {
panic!("unwinding for test");
}
let elt = a + b;
inserts.lock().push(elt);
Recorddrop(elt, &drops)
})
});
assert_eq!(a.len(), result.len());
}));
let inserts = inserts.get_mut();
let drops = drops.get_mut();
assert_eq!(inserts.len(), drops.len(), "Incorrect number of drops");
inserts.sort();
drops.sort();
assert_eq!(inserts, drops, "Incorrect elements were dropped");
};
for &should_panic in &[true, false] {
test_collect_panic(should_panic);
}
}
#[test]
fn collect_drop_on_unwind_zst() {
static INSERTS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);
struct RecorddropZst;
impl Drop for RecorddropZst {
fn drop(&mut self) {
DROPS.fetch_add(1, Ordering::SeqCst);
}
}
let test_collect_panic = |will_panic: bool| {
INSERTS.store(0, Ordering::SeqCst);
DROPS.store(0, Ordering::SeqCst);
let test_vec_len = 1024;
let panic_point = 740;
let a = (0..test_vec_len).collect::<Vec<_>>();
let _result = panic::catch_unwind(AssertUnwindSafe(|| {
let result = in_pool(4, || {
map_collect_vec(a.clone(), &|a| {
if a > panic_point && will_panic {
panic!("unwinding for test");
}
INSERTS.fetch_add(1, Ordering::SeqCst);
RecorddropZst
})
});
assert_eq!(a.len(), result.len());
}));
let inserts = INSERTS.load(Ordering::SeqCst);
let drops = DROPS.load(Ordering::SeqCst);
assert_eq!(inserts, drops, "Incorrect number of drops");
assert!(will_panic || drops == test_vec_len)
};
for &should_panic in &[true, false] {
test_collect_panic(should_panic);
}
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "too many values")]
fn produce_too_many_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 2, |consumer| {
let _folder = consumer.into_folder().consume(22).consume(23).consume(24);
unreachable!("folder does not complete")
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 5 total writes, but got 2")]
fn produce_fewer_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 5, |consumer| {
let mut folder = consumer.into_folder();
folder = folder.consume(22);
folder = folder.consume(23);
folder.complete()
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 2")]
fn left_produces_items_with_no_complete() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let _left_folder = left_consumer.into_folder().consume(0).consume(1);
let mut right_folder = right_consumer.into_folder();
right_folder = right_folder.consume(2).consume(3);
right_folder.complete()
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 2")]
fn right_produces_items_with_no_complete() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let _right_folder = right_consumer.into_folder().consume(2).consume(3);
left_folder = left_folder.consume(0).consume(1);
left_folder.complete()
});
}
#[commonware_macros::test_group("miri")]
#[test]
fn produces_items_with_no_complete() {
let counter = DropCounter::default();
let mut v = vec![];
let panic_result = panic::catch_unwind(AssertUnwindSafe(|| {
collect_with_consumer(&mut v, 2, |consumer| {
let _folder = consumer
.into_folder()
.consume(counter.element())
.consume(counter.element());
panic!("folder does not complete");
});
}));
assert!(v.is_empty());
assert_is_panic_with_message(&panic_result, "folder does not complete");
counter.assert_drop_count();
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "too many values")]
fn left_produces_too_many_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let _left_folder = left_consumer.into_folder().consume(0).consume(1).consume(2);
let _ = right_consumer
.into_folder()
.consume(2)
.consume(3)
.complete();
unreachable!("folder does not complete");
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "too many values")]
fn right_produces_too_many_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let left_folder = left_consumer.into_folder().consume(0).consume(1);
let _right_folder = right_consumer
.into_folder()
.consume(2)
.consume(3)
.consume(4);
let _ = left_folder.complete();
unreachable!("folder does not complete");
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 1")]
fn left_produces_fewer_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(0);
right_folder = right_folder.consume(2).consume(3);
let left_result = left_folder.complete();
let right_result = right_folder.complete();
reducer.reduce(left_result, right_result)
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 2")]
fn only_left_result() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(0).consume(1);
right_folder = right_folder.consume(2).consume(3);
let left_result = left_folder.complete();
let _ = right_folder.complete();
left_result
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 2")]
fn only_right_result() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, _) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(0).consume(1);
right_folder = right_folder.consume(2).consume(3);
let _ = left_folder.complete();
right_folder.complete()
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 2")]
fn reducer_does_not_preserve_order() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(0).consume(1);
right_folder = right_folder.consume(2).consume(3);
let left_result = left_folder.complete();
let right_result = right_folder.complete();
reducer.reduce(right_result, left_result)
});
}
#[commonware_macros::test_group("miri")]
#[test]
#[should_panic(expected = "expected 4 total writes, but got 3")]
fn right_produces_fewer_items() {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(0).consume(1);
right_folder = right_folder.consume(2);
let left_result = left_folder.complete();
let right_result = right_folder.complete();
reducer.reduce(left_result, right_result)
});
}
#[test]
#[should_panic(expected = "left consumer panic")]
fn left_panics() {
in_pool(4, || {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let (left_result, right_result) = join(
|| {
let _left_folder = left_consumer.into_folder().consume(0);
panic!("left consumer panic");
},
|| {
let mut right_folder = right_consumer.into_folder();
right_folder = right_folder.consume(2);
right_folder.complete() },
);
reducer.reduce(left_result, right_result)
});
unreachable!();
});
}
#[test]
#[should_panic(expected = "right consumer panic")]
fn right_panics() {
in_pool(4, || {
let mut v = vec![];
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let (left_result, right_result) = join(
|| {
let mut left_folder = left_consumer.into_folder();
left_folder = left_folder.consume(0);
left_folder.complete() },
|| {
let _right_folder = right_consumer.into_folder().consume(2);
panic!("right consumer panic");
},
);
reducer.reduce(left_result, right_result)
});
unreachable!();
});
}
#[commonware_macros::test_group("miri")]
#[test]
fn left_produces_fewer_items_drops() {
let counter = DropCounter::default();
let mut v = vec![];
let panic_result = panic::catch_unwind(AssertUnwindSafe(|| {
collect_with_consumer(&mut v, 4, |consumer| {
let (left_consumer, right_consumer, reducer) = consumer.split_at(2);
let mut left_folder = left_consumer.into_folder();
let mut right_folder = right_consumer.into_folder();
left_folder = left_folder.consume(counter.element());
right_folder = right_folder
.consume(counter.element())
.consume(counter.element());
let left_result = left_folder.complete();
let right_result = right_folder.complete();
reducer.reduce(left_result, right_result)
});
}));
assert!(v.is_empty());
assert_is_panic_with_message(&panic_result, "expected 4 total writes, but got 1");
counter.assert_drop_count();
}
#[derive(Default)]
struct DropCounter {
created: AtomicUsize,
dropped: AtomicUsize,
}
struct Element<'a>(&'a AtomicUsize);
impl DropCounter {
fn created(&self) -> usize {
self.created.load(Ordering::SeqCst)
}
fn dropped(&self) -> usize {
self.dropped.load(Ordering::SeqCst)
}
fn element(&self) -> Element<'_> {
self.created.fetch_add(1, Ordering::SeqCst);
Element(&self.dropped)
}
fn assert_drop_count(&self) {
assert_eq!(
self.created(),
self.dropped(),
"Expected {} dropped elements, but found {}",
self.created(),
self.dropped()
);
}
}
impl Drop for Element<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn assert_is_panic_with_message<T>(result: &ThreadResult<T>, expected: &str)
where
T: fmt::Debug,
{
match result {
Ok(value) => {
panic!("assertion failure: Expected panic, got successful {value:?}");
}
Err(error) => {
let message_str = error.downcast_ref::<&'static str>().cloned();
let message_string = error.downcast_ref::<String>().map(String::as_str);
if let Some(message) = message_str.or(message_string) {
if !message.contains(expected) {
panic!(
"assertion failure: Expected {expected:?}, but found panic with {message:?}"
);
}
} else {
panic!(
"assertion failure: Expected {expected:?}, but found panic with unknown value"
);
}
}
}
}
}