1use std::cell::RefCell;
2use std::panic::{AssertUnwindSafe, catch_unwind};
3use std::sync::LazyLock;
4
5use polars_utils::with_drop::WithDrop;
6use rayon::{ThreadPool, ThreadPoolBuilder, Yield};
7
8pub struct RAYON;
9
10#[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
12thread_local! {
13 static NOOP_POOL: RefCell<ThreadPool> = RefCell::new(
14 ThreadPoolBuilder::new()
15 .use_current_thread()
16 .num_threads(1)
17 .build()
18 .expect("could not create no-op thread pool")
19 );
20}
21
22impl RAYON {
23 pub fn install<OP, R>(&self, op: OP) -> R
24 where
25 OP: FnOnce() -> R + Send,
26 R: Send,
27 {
28 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
29 {
30 op()
31 }
32
33 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
34 {
35 self.with(|p| p.install(op))
36 }
37 }
38
39 pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
40 where
41 A: FnOnce() -> RA + Send,
42 B: FnOnce() -> RB + Send,
43 RA: Send,
44 RB: Send,
45 {
46 self.install(|| rayon::join(oper_a, oper_b))
47 }
48
49 pub fn scope<'scope, OP, R>(&self, op: OP) -> R
50 where
51 OP: FnOnce(&rayon::Scope<'scope>) -> R + Send,
52 R: Send,
53 {
54 self.install(|| rayon::scope(op))
55 }
56
57 pub fn spawn<OP>(&self, op: OP)
58 where
59 OP: FnOnce() + Send + 'static,
60 {
61 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
62 {
63 rayon::spawn(op)
64 }
65
66 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
67 {
68 self.with(|p| {
69 p.spawn(op);
70 if p.current_num_threads() == 1 {
71 p.yield_now();
72 }
73 })
74 }
75 }
76
77 pub fn spawn_fifo<OP>(&self, op: OP)
78 where
79 OP: FnOnce() + Send + 'static,
80 {
81 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
82 {
83 rayon::spawn_fifo(op)
84 }
85
86 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
87 {
88 self.with(|p| {
89 p.spawn_fifo(op);
90 if p.current_num_threads() == 1 {
91 p.yield_now();
92 }
93 })
94 }
95 }
96
97 pub fn current_thread_has_pending_tasks(&self) -> Option<bool> {
98 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
99 {
100 None
101 }
102
103 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
104 {
105 self.with(|p| p.current_thread_has_pending_tasks())
106 }
107 }
108
109 pub fn current_thread_index(&self) -> Option<usize> {
110 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
111 {
112 rayon::current_thread_index()
113 }
114
115 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
116 {
117 self.with(|p| p.current_thread_index())
118 }
119 }
120
121 pub fn current_num_threads(&self) -> usize {
122 #[cfg(not(any(target_os = "emscripten", not(target_family = "wasm"))))]
123 {
124 rayon::current_num_threads()
125 }
126
127 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
128 {
129 self.with(|p| p.current_num_threads())
130 }
131 }
132
133 #[cfg(any(target_os = "emscripten", not(target_family = "wasm")))]
134 pub fn with<OP, R>(&self, op: OP) -> R
135 where
136 OP: FnOnce(&ThreadPool) -> R + Send,
137 R: Send,
138 {
139 if polars_async::executor::ALLOW_RAYON_THREADS.get()
140 || THREAD_POOL.current_thread_index().is_some()
141 {
142 op(&THREAD_POOL)
143 } else {
144 NOOP_POOL.with(|v| op(&v.borrow()))
145 }
146 }
147
148 pub fn block_on<R: Send, F: FnOnce() -> R + Send>(&self, f: F) -> R {
153 if THREAD_POOL.current_thread_index().is_some() {
154 let mut opt_f: Option<F> = Some(f);
157 let mut out: Option<std::thread::Result<R>> = None;
158 let mut wrap_f = || {
159 let f = AssertUnwindSafe(opt_f.take().unwrap());
160 out = Some(catch_unwind(f));
161 };
162
163 let abort = WithDrop::new((), |()| std::process::abort());
167 let ref_wrap_f: &mut (dyn Send + FnMut()) = &mut wrap_f;
168 let static_wrap_f: &'static mut (dyn Send + FnMut() + 'static) =
169 unsafe { core::mem::transmute(ref_wrap_f) };
170 let join_handle = ASYNC.spawn_blocking(static_wrap_f);
171
172 while !join_handle.is_finished() {
173 match rayon::yield_now() {
174 Some(Yield::Executed) => {},
175 Some(Yield::Idle) => std::thread::yield_now(),
176 None => unreachable!(),
177 }
178 }
179
180 WithDrop::dismiss(abort);
181 match out.unwrap() {
182 Ok(v) => v,
183 Err(panic) => std::panic::resume_unwind(panic),
184 }
185 } else {
186 f()
187 }
188 }
189}
190
191#[cfg(not(target_family = "wasm"))] pub static THREAD_POOL: LazyLock<ThreadPool> = LazyLock::new(|| {
194 let thread_name = std::env::var("POLARS_THREAD_NAME").unwrap_or_else(|_| "polars".to_string());
195 ThreadPoolBuilder::new()
196 .num_threads(polars_config::config().max_threads())
197 .thread_name(move |i| format!("{thread_name}-{i}"))
198 .build()
199 .expect("could not spawn threads")
200});
201
202#[cfg(all(target_os = "emscripten", target_family = "wasm"))] pub static THREAD_POOL: LazyLock<ThreadPool> = LazyLock::new(|| {
204 ThreadPoolBuilder::new()
205 .num_threads(1)
206 .use_current_thread()
207 .build()
208 .expect("could not create pool")
209});
210
211pub use polars_async::ASYNC;