Skip to main content

polars_core/
runtime.rs

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// Thread locals to allow disabling threading for specific threads.
11#[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    /// Calls a blocking function without blocking the rayon thread pool by
149    /// moving it to a different thread.
150    ///
151    /// If this thread isn't a rayon thread this simply calls f directly.
152    pub fn block_on<R: Send, F: FnOnce() -> R + Send>(&self, f: F) -> R {
153        if THREAD_POOL.current_thread_index().is_some() {
154            // We wrap F such that the resulting closure no longer has F or R as
155            // parameters, and whose only limitation is that it isn't 'static.
156            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            // SAFETY: we always await the future to completion before returning from here, meaning
164            // wrap_f stays alive for as long as it needs to. If for whatever reason we unwind we
165            // abort.
166            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// this is re-exported in utils for polars child crates
192#[cfg(not(target_family = "wasm"))] // only use this on non wasm targets
193pub 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"))] // Use 1 rayon thread on emscripten
203pub 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;