reth_tasks/
for_each_ordered.rs1use crossbeam_utils::CachePadded;
2use parking_lot::{Condvar, Mutex};
3use rayon::iter::{IndexedParallelIterator, ParallelIterator};
4use std::{
5 panic::{catch_unwind, AssertUnwindSafe},
6 sync::atomic::{AtomicBool, Ordering},
7};
8
9pub trait ForEachOrdered: IndexedParallelIterator {
12 fn for_each_ordered<F>(self, f: F)
28 where
29 Self::Item: Send,
30 F: FnMut(Self::Item);
31
32 fn for_each_ordered_in<F>(self, pool: &rayon::ThreadPool, f: F)
35 where
36 Self::Item: Send,
37 F: FnMut(Self::Item);
38}
39
40impl<I: IndexedParallelIterator> ForEachOrdered for I {
41 fn for_each_ordered<F>(self, f: F)
42 where
43 Self::Item: Send,
44 F: FnMut(Self::Item),
45 {
46 ordered_impl(self, None, f);
47 }
48
49 fn for_each_ordered_in<F>(self, pool: &rayon::ThreadPool, f: F)
50 where
51 Self::Item: Send,
52 F: FnMut(Self::Item),
53 {
54 ordered_impl(self, Some(pool), f);
55 }
56}
57
58struct Slot<T> {
60 value: Mutex<Option<T>>,
61 notify: Condvar,
62}
63
64impl<T> Slot<T> {
65 const fn new() -> Self {
66 Self { value: Mutex::new(None), notify: Condvar::new() }
67 }
68}
69
70struct Shared<T> {
71 slots: Box<[CachePadded<Slot<T>>]>,
72 panicked: AtomicBool,
73}
74
75impl<T> Shared<T> {
76 fn new(n: usize) -> Self {
77 let slots =
78 (0..n).map(|_| CachePadded::new(Slot::new())).collect::<Vec<_>>().into_boxed_slice();
79 Self { slots, panicked: AtomicBool::new(false) }
80 }
81
82 #[inline]
84 fn write(&self, i: usize, val: T) {
85 let slot = &self.slots[i];
86 *slot.value.lock() = Some(val);
87 slot.notify.notify_one();
88 }
89
90 fn take(&self, i: usize) -> Option<T> {
93 let slot = &self.slots[i];
94 let mut guard = slot.value.lock();
95 loop {
96 if let Some(val) = guard.take() {
97 return Some(val);
98 }
99 if self.panicked.load(Ordering::Acquire) {
100 return None;
101 }
102 slot.notify.wait(&mut guard);
103 }
104 }
105}
106
107fn ordered_impl<I, F>(iter: I, pool: Option<&rayon::ThreadPool>, mut f: F)
112where
113 I: IndexedParallelIterator,
114 I::Item: Send,
115 F: FnMut(I::Item),
116{
117 let n = iter.len();
118 if n == 0 {
119 return;
120 }
121
122 let shared = Shared::<I::Item>::new(n);
123
124 in_place_scope_in(pool, |s| {
125 s.spawn(|_| {
127 let res = catch_unwind(AssertUnwindSafe(|| {
128 iter.enumerate().for_each(|(i, item)| {
129 shared.write(i, item);
130 });
131 }));
132 if let Err(payload) = res {
133 shared.panicked.store(true, Ordering::Release);
134 for slot in &*shared.slots {
137 let _guard = slot.value.lock();
138 slot.notify.notify_one();
139 }
140 std::panic::resume_unwind(payload);
141 }
142 });
143
144 for i in 0..n {
146 let Some(value) = shared.take(i) else {
147 return;
148 };
149 f(value);
150 }
151 });
152}
153
154fn in_place_scope_in<'scope, F, R>(pool: Option<&rayon::ThreadPool>, f: F)
155where
156 F: FnOnce(&rayon::Scope<'scope>) -> R,
157{
158 if let Some(pool) = pool {
159 pool.in_place_scope(f);
160 } else {
161 rayon::in_place_scope(f);
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use rayon::prelude::*;
169 use std::{
170 rc::Rc,
171 sync::{
172 atomic::{AtomicUsize, Ordering},
173 Barrier,
174 },
175 };
176
177 #[test]
178 fn preserves_order() {
179 let input: Vec<u64> = (0..1000).collect();
180 let mut output = Vec::with_capacity(input.len());
181 input.par_iter().map(|x| x * 2).for_each_ordered(|x| output.push(x));
182 let expected: Vec<u64> = (0..1000).map(|x| x * 2).collect();
183 assert_eq!(output, expected);
184 }
185
186 #[test]
187 fn empty_iterator() {
188 let input: Vec<u64> = vec![];
189 let mut output = Vec::new();
190 input.par_iter().map(|x| *x).for_each_ordered(|x| output.push(x));
191 assert!(output.is_empty());
192 }
193
194 #[test]
195 fn single_element() {
196 let mut output = Vec::new();
197 vec![42u64].par_iter().map(|x| *x).for_each_ordered(|x| output.push(x));
198 assert_eq!(output, vec![42]);
199 }
200
201 #[test]
202 fn slow_early_items_still_delivered_in_order() {
203 let barrier = Barrier::new(2);
204 let n = 64usize;
205 let input: Vec<usize> = (0..n).collect();
206 let mut output = Vec::with_capacity(n);
207
208 input
209 .par_iter()
210 .map(|&i| {
211 if i == 0 || i == n - 1 {
212 barrier.wait();
213 }
214 i
215 })
216 .for_each_ordered(|x| output.push(x));
217
218 assert_eq!(output, input);
219 }
220
221 #[test]
222 fn drops_unconsumed_slots_on_panic() {
223 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
224
225 #[derive(Clone)]
226 struct Tracked(#[allow(dead_code)] u64);
227 impl Drop for Tracked {
228 fn drop(&mut self) {
229 DROP_COUNT.fetch_add(1, Ordering::Relaxed);
230 }
231 }
232
233 DROP_COUNT.store(0, Ordering::Relaxed);
234
235 let input: Vec<u64> = (0..100).collect();
236 let result = std::panic::catch_unwind(|| {
237 input
238 .par_iter()
239 .map(|&i| {
240 assert!(i != 50, "intentional");
241 Tracked(i)
242 })
243 .for_each_ordered(|_item| {});
244 });
245
246 assert!(result.is_err());
247 let drops = DROP_COUNT.load(Ordering::Relaxed);
248 assert!(drops > 0, "some items should have been dropped");
249 }
250
251 #[test]
252 fn no_double_drop() {
253 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
254
255 struct Counted(#[allow(dead_code)] u64);
256 impl Drop for Counted {
257 fn drop(&mut self) {
258 DROP_COUNT.fetch_add(1, Ordering::Relaxed);
259 }
260 }
261
262 DROP_COUNT.store(0, Ordering::Relaxed);
263 let n = 200u64;
264 let input: Vec<u64> = (0..n).collect();
265 input.par_iter().map(|&i| Counted(i)).for_each_ordered(|_item| {});
266
267 assert_eq!(DROP_COUNT.load(Ordering::Relaxed), n as usize);
268 }
269
270 #[test]
271 fn callback_is_not_send() {
272 let counter = Rc::new(std::cell::Cell::new(0u64));
273 let input: Vec<u64> = (0..100).collect();
274 input.par_iter().map(|&x| x).for_each_ordered(|x| {
275 counter.set(counter.get() + x);
276 });
277 assert_eq!(counter.get(), (0..100u64).sum::<u64>());
278 }
279
280 #[test]
281 fn panic_does_not_deadlock_consumer() {
282 for _ in 0..100 {
286 let result = std::panic::catch_unwind(|| {
287 let input: Vec<usize> = (0..256).collect();
288 input
289 .par_iter()
290 .map(|&i| {
291 if i == 128 {
292 std::thread::yield_now();
295 panic!("intentional");
296 }
297 i
298 })
299 .for_each_ordered(|_| {});
300 });
301 assert!(result.is_err());
302 }
303 }
304
305 #[test]
306 fn early_panic_at_item_zero() {
307 let result = std::panic::catch_unwind(|| {
308 let input: Vec<u64> = (0..10).collect();
309 input
310 .par_iter()
311 .map(|&i| {
312 assert!(i != 0, "boom at zero");
313 i
314 })
315 .for_each_ordered(|_| {});
316 });
317 assert!(result.is_err());
318 }
319
320 #[test]
321 fn late_panic_at_last_item() {
322 let n = 100usize;
323 let result = std::panic::catch_unwind(|| {
324 let input: Vec<usize> = (0..n).collect();
325 input
326 .par_iter()
327 .map(|&i| {
328 assert!(i != n - 1, "boom at last");
329 i
330 })
331 .for_each_ordered(|_| {});
332 });
333 assert!(result.is_err());
334 }
335
336 #[test]
337 fn large_items() {
338 let n = 500usize;
339 let input: Vec<usize> = (0..n).collect();
340 let mut output = Vec::with_capacity(n);
341 input
342 .par_iter()
343 .map(|&i| {
344 vec![i; 64]
346 })
347 .for_each_ordered(|v| output.push(v[0]));
348 assert_eq!(output, input);
349 }
350
351 #[test]
352 fn consumer_slower_than_producer() {
353 let n = 64usize;
355 let input: Vec<usize> = (0..n).collect();
356 let mut output = Vec::with_capacity(n);
357 input.par_iter().map(|&i| i).for_each_ordered(|x| {
358 if x % 8 == 0 {
359 std::thread::yield_now();
360 }
361 output.push(x);
362 });
363 assert_eq!(output, input);
364 }
365
366 #[test]
367 fn concurrent_panic_and_drop_no_leak() {
368 static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
370 static PRODUCED: AtomicUsize = AtomicUsize::new(0);
371
372 struct Tracked;
373 impl Drop for Tracked {
374 fn drop(&mut self) {
375 DROP_COUNT.fetch_add(1, Ordering::Relaxed);
376 }
377 }
378
379 DROP_COUNT.store(0, Ordering::Relaxed);
380 PRODUCED.store(0, Ordering::Relaxed);
381
382 let barrier = Barrier::new(2);
383 let result = std::panic::catch_unwind(|| {
384 let input: Vec<usize> = (0..64).collect();
385 input
386 .par_iter()
387 .map(|&i| {
388 if i == 32 {
389 barrier.wait();
390 panic!("intentional");
391 }
392 if i == 0 {
393 barrier.wait();
394 }
395 PRODUCED.fetch_add(1, Ordering::Relaxed);
396 Tracked
397 })
398 .for_each_ordered(|_| {});
399 });
400
401 assert!(result.is_err());
402 let produced = PRODUCED.load(Ordering::Relaxed);
403 let dropped = DROP_COUNT.load(Ordering::Relaxed);
404 assert_eq!(dropped, produced, "all produced items must be dropped");
405 }
406}