rustpython_vm/vm/
thread.rs

1use crate::{AsObject, PyObject, PyObjectRef, VirtualMachine};
2use itertools::Itertools;
3use std::{
4    cell::{Cell, RefCell},
5    ptr::NonNull,
6    thread_local,
7};
8
9thread_local! {
10    pub(super) static VM_STACK: RefCell<Vec<NonNull<VirtualMachine>>> = Vec::with_capacity(1).into();
11    static VM_CURRENT: RefCell<*const VirtualMachine> = std::ptr::null::<VirtualMachine>().into();
12
13    pub(crate) static COROUTINE_ORIGIN_TRACKING_DEPTH: Cell<u32> = const { Cell::new(0) };
14    pub(crate) static ASYNC_GEN_FINALIZER: RefCell<Option<PyObjectRef>> = const { RefCell::new(None) };
15    pub(crate) static ASYNC_GEN_FIRSTITER: RefCell<Option<PyObjectRef>> = const { RefCell::new(None) };
16}
17
18pub fn with_current_vm<R>(f: impl FnOnce(&VirtualMachine) -> R) -> R {
19    VM_CURRENT.with(|x| unsafe {
20        f(x.clone()
21            .into_inner()
22            .as_ref()
23            .expect("call with_current_vm() but VM_CURRENT is null"))
24    })
25}
26
27pub fn enter_vm<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
28    VM_STACK.with(|vms| {
29        vms.borrow_mut().push(vm.into());
30        let prev = VM_CURRENT.with(|current| current.replace(vm));
31        let ret = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
32        vms.borrow_mut().pop();
33        VM_CURRENT.with(|current| current.replace(prev));
34        ret.unwrap_or_else(|e| std::panic::resume_unwind(e))
35    })
36}
37
38pub fn with_vm<F, R>(obj: &PyObject, f: F) -> Option<R>
39where
40    F: Fn(&VirtualMachine) -> R,
41{
42    let vm_owns_obj = |intp: NonNull<VirtualMachine>| {
43        // SAFETY: all references in VM_STACK should be valid
44        let vm = unsafe { intp.as_ref() };
45        obj.fast_isinstance(vm.ctx.types.object_type)
46    };
47    VM_STACK.with(|vms| {
48        let intp = match vms.borrow().iter().copied().exactly_one() {
49            Ok(x) => {
50                debug_assert!(vm_owns_obj(x));
51                x
52            }
53            Err(mut others) => others.find(|x| vm_owns_obj(*x))?,
54        };
55        // SAFETY: all references in VM_STACK should be valid, and should not be changed or moved
56        // at least until this function returns and the stack unwinds to an enter_vm() call
57        let vm = unsafe { intp.as_ref() };
58        let prev = VM_CURRENT.with(|current| current.replace(vm));
59        let ret = f(vm);
60        VM_CURRENT.with(|current| current.replace(prev));
61        Some(ret)
62    })
63}
64
65#[must_use = "ThreadedVirtualMachine does nothing unless you move it to another thread and call .run()"]
66#[cfg(feature = "threading")]
67pub struct ThreadedVirtualMachine {
68    pub(super) vm: VirtualMachine,
69}
70
71#[cfg(feature = "threading")]
72impl ThreadedVirtualMachine {
73    /// Create a `FnOnce()` that can easily be passed to a function like [`std::thread::Builder::spawn`]
74    ///
75    /// # Note
76    ///
77    /// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't `join()`
78    /// on the thread this `FnOnce` runs in, there is a possibility that that thread will panic
79    /// as `PyObjectRef`'s `Drop` implementation tries to run the `__del__` destructor of a
80    /// Python object but finds that it's not in the context of any vm.
81    pub fn make_spawn_func<F, R>(self, f: F) -> impl FnOnce() -> R
82    where
83        F: FnOnce(&VirtualMachine) -> R,
84    {
85        move || self.run(f)
86    }
87
88    /// Run a function in this thread context
89    ///
90    /// # Note
91    ///
92    /// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't return the object
93    /// to the parent thread and then `join()` on the `JoinHandle` (or similar), there is a possibility that
94    /// the current thread will panic as `PyObjectRef`'s `Drop` implementation tries to run the `__del__`
95    /// destructor of a python object but finds that it's not in the context of any vm.
96    pub fn run<F, R>(&self, f: F) -> R
97    where
98        F: FnOnce(&VirtualMachine) -> R,
99    {
100        let vm = &self.vm;
101        enter_vm(vm, || f(vm))
102    }
103}
104
105impl VirtualMachine {
106    /// Start a new thread with access to the same interpreter.
107    ///
108    /// # Note
109    ///
110    /// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't `join()`
111    /// on the thread, there is a possibility that that thread will panic as `PyObjectRef`'s `Drop`
112    /// implementation tries to run the `__del__` destructor of a python object but finds that it's
113    /// not in the context of any vm.
114    #[cfg(feature = "threading")]
115    pub fn start_thread<F, R>(&self, f: F) -> std::thread::JoinHandle<R>
116    where
117        F: FnOnce(&VirtualMachine) -> R,
118        F: Send + 'static,
119        R: Send + 'static,
120    {
121        let func = self.new_thread().make_spawn_func(f);
122        std::thread::spawn(func)
123    }
124
125    /// Create a new VM thread that can be passed to a function like [`std::thread::spawn`]
126    /// to use the same interpreter on a different thread. Note that if you just want to
127    /// use this with `thread::spawn`, you can use
128    /// [`vm.start_thread()`](`VirtualMachine::start_thread`) as a convenience.
129    ///
130    /// # Usage
131    ///
132    /// ```
133    /// # rustpython_vm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
134    /// use std::thread::Builder;
135    /// let handle = Builder::new()
136    ///     .name("my thread :)".into())
137    ///     .spawn(vm.new_thread().make_spawn_func(|vm| vm.ctx.none()))
138    ///     .expect("couldn't spawn thread");
139    /// let returned_obj = handle.join().expect("thread panicked");
140    /// assert!(vm.is_none(&returned_obj));
141    /// # })
142    /// ```
143    ///
144    /// Note: this function is safe, but running the returned ThreadedVirtualMachine in the same
145    /// thread context (i.e. with the same thread-local storage) doesn't have any
146    /// specific guaranteed behavior.
147    #[cfg(feature = "threading")]
148    pub fn new_thread(&self) -> ThreadedVirtualMachine {
149        let vm = VirtualMachine {
150            builtins: self.builtins.clone(),
151            sys_module: self.sys_module.clone(),
152            ctx: self.ctx.clone(),
153            frames: RefCell::new(vec![]),
154            wasm_id: self.wasm_id.clone(),
155            exceptions: RefCell::default(),
156            import_func: self.import_func.clone(),
157            profile_func: RefCell::new(self.ctx.none()),
158            trace_func: RefCell::new(self.ctx.none()),
159            use_tracing: Cell::new(false),
160            recursion_limit: self.recursion_limit.clone(),
161            signal_handlers: None,
162            signal_rx: None,
163            repr_guards: RefCell::default(),
164            state: self.state.clone(),
165            initialized: self.initialized,
166            recursion_depth: Cell::new(0),
167        };
168        ThreadedVirtualMachine { vm }
169    }
170}