1 use rand::distributions::Uniform;
2 use rand::{thread_rng, Rng};
3 use rayon::prelude::*;
4 use std::cell::Cell;
5 use std::cmp::Ordering;
6 use std::panic;
7 use std::sync::atomic::AtomicUsize;
8 use std::sync::atomic::Ordering::Relaxed;
9 use std::thread;
10
11 // const is needed for array initializer
12 #[allow(clippy::declare_interior_mutable_const)]
13 const ZERO: AtomicUsize = AtomicUsize::new(0);
14 const LEN: usize = 20_000;
15
16 static VERSIONS: AtomicUsize = ZERO;
17
18 static DROP_COUNTS: [AtomicUsize; LEN] = [ZERO; LEN];
19
20 #[derive(Clone, Eq)]
21 struct DropCounter {
22 x: u32,
23 id: usize,
24 version: Cell<usize>,
25 }
26
27 impl PartialEq for DropCounter {
eq(&self, other: &Self) -> bool28 fn eq(&self, other: &Self) -> bool {
29 self.partial_cmp(other) == Some(Ordering::Equal)
30 }
31 }
32
33 #[allow(clippy::non_canonical_partial_ord_impl)]
34 impl PartialOrd for DropCounter {
partial_cmp(&self, other: &Self) -> Option<Ordering>35 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
36 self.version.set(self.version.get() + 1);
37 other.version.set(other.version.get() + 1);
38 VERSIONS.fetch_add(2, Relaxed);
39 self.x.partial_cmp(&other.x)
40 }
41 }
42
43 impl Ord for DropCounter {
cmp(&self, other: &Self) -> Ordering44 fn cmp(&self, other: &Self) -> Ordering {
45 self.partial_cmp(other).unwrap()
46 }
47 }
48
49 impl Drop for DropCounter {
drop(&mut self)50 fn drop(&mut self) {
51 DROP_COUNTS[self.id].fetch_add(1, Relaxed);
52 VERSIONS.fetch_sub(self.version.get(), Relaxed);
53 }
54 }
55
56 macro_rules! test {
57 ($input:ident, $func:ident) => {
58 let len = $input.len();
59
60 // Work out the total number of comparisons required to sort
61 // this array...
62 let count = AtomicUsize::new(0);
63 $input.to_owned().$func(|a, b| {
64 count.fetch_add(1, Relaxed);
65 a.cmp(b)
66 });
67
68 let mut panic_countdown = count.load(Relaxed);
69 let step = if len <= 100 {
70 1
71 } else {
72 Ord::max(1, panic_countdown / 10)
73 };
74
75 // ... and then panic after each `step` comparisons.
76 loop {
77 // Refresh the counters.
78 VERSIONS.store(0, Relaxed);
79 for i in 0..len {
80 DROP_COUNTS[i].store(0, Relaxed);
81 }
82
83 let v = $input.to_owned();
84 let _ = thread::spawn(move || {
85 let mut v = v;
86 let panic_countdown = AtomicUsize::new(panic_countdown);
87 v.$func(|a, b| {
88 if panic_countdown.fetch_sub(1, Relaxed) == 1 {
89 SILENCE_PANIC.with(|s| s.set(true));
90 panic!();
91 }
92 a.cmp(b)
93 })
94 })
95 .join();
96
97 // Check that the number of things dropped is exactly
98 // what we expect (i.e. the contents of `v`).
99 for (i, c) in DROP_COUNTS.iter().enumerate().take(len) {
100 let count = c.load(Relaxed);
101 assert!(
102 count == 1,
103 "found drop count == {} for i == {}, len == {}",
104 count,
105 i,
106 len
107 );
108 }
109
110 // Check that the most recent versions of values were dropped.
111 assert_eq!(VERSIONS.load(Relaxed), 0);
112
113 if panic_countdown < step {
114 break;
115 }
116 panic_countdown -= step;
117 }
118 };
119 }
120
121 thread_local!(static SILENCE_PANIC: Cell<bool> = const { Cell::new(false) });
122
123 #[test]
124 #[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
sort_panic_safe()125 fn sort_panic_safe() {
126 let prev = panic::take_hook();
127 panic::set_hook(Box::new(move |info| {
128 if !SILENCE_PANIC.with(Cell::get) {
129 prev(info);
130 }
131 }));
132
133 for &len in &[1, 2, 3, 4, 5, 10, 20, 100, 500, 5_000, 20_000] {
134 let len_dist = Uniform::new(0, len);
135 for &modulus in &[5, 30, 1_000, 20_000] {
136 for &has_runs in &[false, true] {
137 let mut rng = thread_rng();
138 let mut input = (0..len)
139 .map(|id| DropCounter {
140 x: rng.gen_range(0..modulus),
141 id,
142 version: Cell::new(0),
143 })
144 .collect::<Vec<_>>();
145
146 if has_runs {
147 for c in &mut input {
148 c.x = c.id as u32;
149 }
150
151 for _ in 0..5 {
152 let a = rng.sample(len_dist);
153 let b = rng.sample(len_dist);
154 if a < b {
155 input[a..b].reverse();
156 } else {
157 input.swap(a, b);
158 }
159 }
160 }
161
162 test!(input, par_sort_by);
163 test!(input, par_sort_unstable_by);
164 }
165 }
166 }
167 }
168