1use core::cell::Cell;
2use core::num::NonZeroUsize;
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum ExecutionPolicy {
25 AmbientRayon,
30 Sequential,
32 Rayon { max_threads: NonZeroUsize },
37}
38
39thread_local! {
40 static ACTIVE_POLICY: Cell<ExecutionPolicy> = const { Cell::new(ExecutionPolicy::AmbientRayon) };
41 static ACTIVE_FANOUT: Cell<bool> = const { Cell::new(false) };
42}
43
44fn restrict(outer: ExecutionPolicy, inner: ExecutionPolicy) -> ExecutionPolicy {
45 match (outer, inner) {
46 (ExecutionPolicy::Sequential, _) | (_, ExecutionPolicy::Sequential) => {
47 ExecutionPolicy::Sequential
48 }
49 (ExecutionPolicy::AmbientRayon, policy) | (policy, ExecutionPolicy::AmbientRayon) => policy,
50 (
51 ExecutionPolicy::Rayon { max_threads: outer },
52 ExecutionPolicy::Rayon { max_threads: inner },
53 ) => ExecutionPolicy::Rayon {
54 max_threads: outer.min(inner),
55 },
56 }
57}
58
59#[derive(Clone, Copy)]
60struct ExecutionState {
61 policy: ExecutionPolicy,
62 fanout_active: bool,
63}
64
65struct StateGuard {
66 previous: ExecutionState,
67}
68
69impl Drop for StateGuard {
70 fn drop(&mut self) {
71 set_state(self.previous);
72 }
73}
74
75fn state() -> ExecutionState {
76 ExecutionState {
77 policy: ACTIVE_POLICY.with(Cell::get),
78 fanout_active: ACTIVE_FANOUT.with(Cell::get),
79 }
80}
81
82fn set_state(state: ExecutionState) {
83 ACTIVE_POLICY.with(|active| active.set(state.policy));
84 ACTIVE_FANOUT.with(|active| active.set(state.fanout_active));
85}
86
87#[cfg(feature = "parallel")]
88fn with_state<R>(next: ExecutionState, operation: impl FnOnce() -> R) -> R {
89 let previous = state();
90 set_state(next);
91 let _guard = StateGuard { previous };
92 operation()
93}
94
95#[inline]
122pub fn with_execution_policy<R>(policy: ExecutionPolicy, operation: impl FnOnce() -> R) -> R {
123 let policy = match policy {
124 ExecutionPolicy::AmbientRayon => return operation(),
125 policy => policy,
126 };
127 let previous = state();
128 set_state(ExecutionState {
129 policy: restrict(previous.policy, policy),
130 fanout_active: previous.fanout_active,
131 });
132 let _guard = StateGuard { previous };
133 operation()
134}
135
136#[cfg(feature = "parallel")]
137pub(crate) fn active_policy() -> ExecutionPolicy {
138 ACTIVE_POLICY.with(Cell::get)
139}
140
141#[cfg(feature = "parallel")]
142pub(crate) fn fanout_active() -> bool {
143 ACTIVE_FANOUT.with(Cell::get)
144}
145
146#[cfg(feature = "parallel")]
147#[inline(always)]
148pub(crate) fn with_owned_execution<R>(
149 policy: ExecutionPolicy,
150 fanout_active: bool,
151 operation: impl FnOnce() -> R,
152) -> R {
153 match policy {
154 ExecutionPolicy::AmbientRayon => operation(),
155 ExecutionPolicy::Sequential | ExecutionPolicy::Rayon { .. } => {
156 let previous = state();
157 with_state(
158 ExecutionState {
159 policy: restrict(previous.policy, policy),
160 fanout_active: previous.fanout_active || fanout_active,
161 },
162 operation,
163 )
164 }
165 }
166}
167
168#[cfg(feature = "parallel")]
169pub(crate) fn with_scheduler_suspended<R>(operation: impl FnOnce() -> R) -> R {
170 with_state(
171 ExecutionState {
172 policy: ExecutionPolicy::AmbientRayon,
173 fanout_active: false,
174 },
175 operation,
176 )
177}
178
179#[cfg(feature = "parallel")]
180pub(crate) fn permutation_copy_parallel_eligible(
181 policy: ExecutionPolicy,
182 fanout_active: bool,
183 current_pool_threads: usize,
184) -> bool {
185 if fanout_active || current_pool_threads <= 1 {
186 return false;
187 }
188 match policy {
189 ExecutionPolicy::AmbientRayon => true,
190 ExecutionPolicy::Sequential => false,
191 ExecutionPolicy::Rayon { max_threads } => current_pool_threads <= max_threads.get(),
192 }
193}
194
195#[cfg(feature = "parallel")]
196pub(crate) fn rayon_threads() -> usize {
197 if fanout_active() {
198 return 1;
199 }
200 match active_policy() {
201 ExecutionPolicy::Sequential => 1,
202 ExecutionPolicy::AmbientRayon => crate::threading::current_pool_threads(),
203 ExecutionPolicy::Rayon { max_threads } => {
204 crate::threading::current_pool_threads().min(max_threads.get())
205 }
206 }
207}
208
209#[cfg(test)]
210mod default_tests {
211 use super::*;
212 use std::panic::{catch_unwind, AssertUnwindSafe};
213
214 #[test]
215 fn ambient_scope_returns_result_without_changing_state() {
216 let before = state();
217
218 let value = with_execution_policy(ExecutionPolicy::AmbientRayon, || {
219 let active = state();
220 assert_eq!(active.policy, before.policy);
221 assert_eq!(active.fanout_active, before.fanout_active);
222 17usize
223 });
224
225 let after = state();
226 assert_eq!(value, 17);
227 assert_eq!(after.policy, before.policy);
228 assert_eq!(after.fanout_active, before.fanout_active);
229 }
230
231 #[test]
232 fn explicit_scope_restores_state_after_return_and_panic() {
233 let before = state();
234 let two = NonZeroUsize::new(2).unwrap();
235
236 let value = with_execution_policy(ExecutionPolicy::Rayon { max_threads: two }, || {
237 assert_eq!(state().policy, ExecutionPolicy::Rayon { max_threads: two });
238 23usize
239 });
240 assert_eq!(value, 23);
241 assert_eq!(state().policy, before.policy);
242 assert_eq!(state().fanout_active, before.fanout_active);
243
244 let panic = catch_unwind(AssertUnwindSafe(|| {
245 with_execution_policy(ExecutionPolicy::Sequential, || panic!("policy scope panic"));
246 }));
247 assert!(panic.is_err());
248 assert_eq!(state().policy, before.policy);
249 assert_eq!(state().fanout_active, before.fanout_active);
250 }
251
252 #[test]
253 fn nested_explicit_scopes_combine_conservatively() {
254 let two = NonZeroUsize::new(2).unwrap();
255 let four = NonZeroUsize::new(4).unwrap();
256
257 with_execution_policy(ExecutionPolicy::Rayon { max_threads: four }, || {
258 assert_eq!(state().policy, ExecutionPolicy::Rayon { max_threads: four });
259
260 with_execution_policy(ExecutionPolicy::Rayon { max_threads: two }, || {
261 assert_eq!(state().policy, ExecutionPolicy::Rayon { max_threads: two });
262 });
263 assert_eq!(state().policy, ExecutionPolicy::Rayon { max_threads: four });
264
265 with_execution_policy(ExecutionPolicy::Sequential, || {
266 assert_eq!(state().policy, ExecutionPolicy::Sequential);
267 });
268 assert_eq!(state().policy, ExecutionPolicy::Rayon { max_threads: four });
269 });
270
271 with_execution_policy(ExecutionPolicy::Sequential, || {
272 with_execution_policy(ExecutionPolicy::Rayon { max_threads: four }, || {
273 assert_eq!(state().policy, ExecutionPolicy::Sequential);
274 });
275 });
276 assert_eq!(state().policy, ExecutionPolicy::AmbientRayon);
277 assert!(!state().fanout_active);
278 }
279}
280
281#[cfg(all(test, feature = "parallel"))]
282mod tests {
283 use super::*;
284 use std::panic::{catch_unwind, AssertUnwindSafe};
285
286 #[test]
287 fn permutation_copy_parallel_eligibility_is_deterministic() {
288 let two = NonZeroUsize::new(2).unwrap();
289 let four = NonZeroUsize::new(4).unwrap();
290
291 assert!(permutation_copy_parallel_eligible(
292 ExecutionPolicy::Rayon { max_threads: two },
293 false,
294 2,
295 ));
296 assert!(permutation_copy_parallel_eligible(
297 ExecutionPolicy::Rayon { max_threads: four },
298 false,
299 2,
300 ));
301 assert!(!permutation_copy_parallel_eligible(
302 ExecutionPolicy::Rayon { max_threads: two },
303 false,
304 4,
305 ));
306 assert!(!permutation_copy_parallel_eligible(
307 ExecutionPolicy::Rayon { max_threads: two },
308 true,
309 2,
310 ));
311 assert!(!permutation_copy_parallel_eligible(
312 ExecutionPolicy::Sequential,
313 false,
314 2,
315 ));
316 assert!(permutation_copy_parallel_eligible(
317 ExecutionPolicy::AmbientRayon,
318 false,
319 2,
320 ));
321 }
322
323 #[test]
324 fn scheduler_panic_restores_owned_policy_and_fanout_state() {
325 let two = NonZeroUsize::new(2).unwrap();
326 let policy = ExecutionPolicy::Rayon { max_threads: two };
327
328 with_execution_policy(policy, || {
329 with_owned_execution(policy, true, || {
330 let panic = catch_unwind(AssertUnwindSafe(|| {
331 with_scheduler_suspended(|| panic!("scheduler boundary panic"));
332 }));
333 assert!(panic.is_err());
334 assert_eq!(active_policy(), policy);
335 assert!(fanout_active());
336 });
337 });
338 assert_eq!(active_policy(), ExecutionPolicy::AmbientRayon);
339 assert!(!fanout_active());
340 }
341
342 #[test]
343 fn leaf_panic_restores_ambient_policy_and_inactive_fanout() {
344 let two = NonZeroUsize::new(2).unwrap();
345 let policy = ExecutionPolicy::Rayon { max_threads: two };
346
347 let panic = catch_unwind(AssertUnwindSafe(|| {
348 with_owned_execution(policy, true, || panic!("owned leaf panic"));
349 }));
350 assert!(panic.is_err());
351 assert_eq!(active_policy(), ExecutionPolicy::AmbientRayon);
352 assert!(!fanout_active());
353 }
354}