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