polars_utils/
python_thread_pool.rs1use pyo3::call::PyCallArgs;
2use pyo3::sync::PyOnceLock;
3use pyo3::types::{PyAnyMethods as _, PyDict};
4use pyo3::{Bound, IntoPyObject, Py, PyAny, PyResult, Python};
5
6#[derive(IntoPyObject, Debug)]
7pub struct PyThreadPool(
8 Py<PyAny>,
10);
11
12impl<'py> IntoPyObject<'py> for &'py PyThreadPool {
13 type Output = <&'py Py<PyAny> as IntoPyObject<'py>>::Output;
14 type Target = <&'py Py<PyAny> as IntoPyObject<'py>>::Target;
15 type Error = <&'py Py<PyAny> as IntoPyObject<'py>>::Error;
16
17 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
18 IntoPyObject::into_pyobject(&self.0, py)
19 }
20}
21
22impl PyThreadPool {
23 pub fn new(num_threads: usize) -> Self {
24 return Python::attach(|py| {
25 Self(
26 py_scan_resolve_threadpool_cls(py)
27 .bind(py)
28 .call1((num_threads,))
29 .map(|x| x.unbind())
30 .unwrap(),
31 )
32 });
33
34 fn py_scan_resolve_threadpool_cls(py: Python<'_>) -> &'static Py<PyAny> {
35 static CLS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
36
37 CLS.get_or_init(py, || {
38 py.import("polars._utils.threading")
39 .unwrap()
40 .getattr("PyThreadPool")
41 .unwrap()
42 .unbind()
43 })
44 }
45 }
46
47 pub fn new_unbounded() -> Self {
48 Self::new(usize::MAX)
49 }
50
51 pub fn new_scan_resolve_thread_pool() -> Self {
52 use std::num::NonZeroUsize;
53
54 let num_threads: usize =
55 std::env::var("POLARS_PYTHON_SCAN_RESOLVE_THREADS").map_or(128, |x| {
56 x.parse::<NonZeroUsize>()
57 .unwrap_or_else(|_| {
58 panic!("invalid value for POLARS_PYTHON_SCAN_RESOLVE_THREADS: {x}")
59 })
60 .get()
61 });
62
63 if polars_config::config().verbose() {
64 eprintln!("python scan_resolve_threadpool threads: {num_threads}")
65 }
66
67 Self::new(num_threads)
68 }
69
70 pub fn spawn_call<'a>(
71 &self,
72 py: Python<'a>,
73 function: &Py<PyAny>,
74 args: impl PyCallArgs<'a>,
75 kwargs: Option<&Bound<'a, PyDict>>,
76 ) -> PyResult<Py<PyAny>> {
77 static CLS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
78
79 CLS.get_or_init(py, || {
80 py.import("polars._utils.threading")
81 .unwrap()
82 .getattr("FnPoolWrap")
83 .unwrap()
84 .unbind()
85 })
86 .call1(py, (function, self))?
87 .call(py, args, kwargs)
88 }
89}