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