rustpython_vm/vm/runtime.rs
1//! Process-global runtime support for multiple interpreters (PEP 734 preparation).
2//!
3//! CPython maps roughly as:
4//! - this module ≈ `_PyRuntimeState.interpreters` + ID allocation
5//! - [`crate::vm::PyGlobalState`] ≈ `PyInterpreterState`
6//! - [`crate::VirtualMachine`] ≈ `PyThreadState` (plus shared refs to interpreter state)
7//!
8//! Multiple [`crate::Interpreter`] instances can coexist in one process. Each owns
9//! an isolated `PyGlobalState` (modules, codecs, thread registry, stop-the-world, …)
10//! while sharing the process-wide [`crate::Context`] (builtin types / immortals).
11
12use crate::common::rc::PyRc;
13use crate::vm::PyGlobalState;
14use core::sync::atomic::{AtomicI64, Ordering};
15use parking_lot::Mutex;
16use std::collections::HashMap;
17
18/// Where an interpreter state came from (mirrors CPython `_PyInterpreterState_GetWhence`).
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[repr(i32)]
21pub enum InterpreterWhence {
22 /// Unknown / not recorded.
23 Unknown = 0,
24 /// Created as the process main interpreter at runtime init.
25 Runtime = 1,
26 /// Legacy C-API creation path (reserved for C-API parity).
27 LegacyCapi = 2,
28 /// Modern C-API creation path (reserved for C-API parity).
29 Capi = 3,
30 /// Cross-interpreter C-API (reserved).
31 Xi = 4,
32 /// Created via the stdlib / Rust subinterpreter API (PEP 734).
33 Stdlib = 5,
34}
35
36impl InterpreterWhence {
37 #[must_use]
38 pub const fn as_i32(self) -> i32 {
39 self as i32
40 }
41}
42
43/// Snapshot of a registered interpreter for enumeration APIs.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct InterpreterInfo {
46 pub id: i64,
47 pub whence: InterpreterWhence,
48}
49
50struct RegistryEntry {
51 whence: InterpreterWhence,
52 /// Weak handle so the registry does not keep interpreters alive.
53 /// Type matches `PyRc` (Arc when threading, Rc otherwise).
54 #[cfg(feature = "threading")]
55 state: alloc::sync::Weak<PyGlobalState>,
56 #[cfg(not(feature = "threading"))]
57 state: alloc::rc::Weak<PyGlobalState>,
58}
59
60/// `main_id` value before any main interpreter has been registered.
61const NO_MAIN_INTERPRETER: i64 = -1;
62
63struct InterpreterRegistry {
64 next_id: AtomicI64,
65 /// Id of the first registered `is_main` interpreter (PEP 734 `get_main()`),
66 /// or [`NO_MAIN_INTERPRETER`].
67 main_id: AtomicI64,
68 /// id → entry. Main interpreter is always id 0 when created first.
69 entries: Mutex<HashMap<i64, RegistryEntry>>,
70}
71
72impl InterpreterRegistry {
73 fn new() -> Self {
74 Self {
75 // Monotonic ids starting at 0. Concurrent Interpreter construction
76 // (e.g. cargo test threads) must never share an id.
77 next_id: AtomicI64::new(0),
78 main_id: AtomicI64::new(NO_MAIN_INTERPRETER),
79 entries: Mutex::new(HashMap::new()),
80 }
81 }
82}
83
84/// The interpreter registry.
85///
86/// With `threading` this is one process-global table. Without it, `PyRc` is
87/// `Rc` and each OS thread owns an independent `Context::genesis()` and
88/// `GcState`, so the registry is thread-local for the same reason `gc_state()`
89/// is: an `Rc` handle must never be reachable from another thread.
90/// `static_cell!` provides exactly that split.
91fn registry() -> &'static InterpreterRegistry {
92 rustpython_common::static_cell! {
93 static REGISTRY: InterpreterRegistry;
94 }
95 REGISTRY.get_or_init(InterpreterRegistry::new)
96}
97
98/// Conventional id of the first process main interpreter when allocation is
99/// sequential (CPython parity). Concurrent construction may assign other ids;
100/// use [`PyGlobalState::is_main`] / [`crate::Interpreter::is_main`] to identify
101/// a main interpreter, not this constant alone.
102pub const MAIN_INTERPRETER_ID: i64 = 0;
103
104/// Backs `sys.implementation.supports_isolated_interpreters`.
105///
106/// Isolated interpreters are available wherever the threading substrate can
107/// own a subinterpreter handle. WASM builds match CPython and stay `false`.
108pub const SUPPORTS_ISOLATED_INTERPRETERS: bool =
109 cfg!(all(feature = "threading", not(target_arch = "wasm32")));
110
111/// Feature flags copied from `PyInterpreterConfig` / `Py_RTFLAGS_*`.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct InterpFeatureFlags {
114 pub use_main_obmalloc: bool,
115 pub allow_fork: bool,
116 pub allow_exec: bool,
117 pub allow_threads: bool,
118 pub allow_daemon_threads: bool,
119 pub check_multi_interp_extensions: bool,
120}
121
122impl InterpFeatureFlags {
123 /// Isolated config (`_PyInterpreterConfig_INIT`).
124 pub const ISOLATED: Self = InterpreterConfig::ISOLATED.feature_flags();
125
126 /// Legacy config (`_PyInterpreterConfig_LEGACY_INIT`).
127 pub const LEGACY: Self = InterpreterConfig::LEGACY.feature_flags();
128}
129
130impl Default for InterpFeatureFlags {
131 fn default() -> Self {
132 Self::LEGACY
133 }
134}
135
136/// Named `PyInterpreterConfig` used by `_interpreters.create` / `new_config`.
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct InterpreterConfig {
139 pub use_main_obmalloc: bool,
140 pub allow_fork: bool,
141 pub allow_exec: bool,
142 pub allow_threads: bool,
143 pub allow_daemon_threads: bool,
144 pub check_multi_interp_extensions: bool,
145 /// `"default"`, `"shared"`, or `"own"`. RustPython has no process-wide
146 /// interpreter lock, so this is recorded for API parity only.
147 pub gil: InterpreterGil,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum InterpreterGil {
152 Default,
153 Shared,
154 Own,
155}
156
157impl InterpreterGil {
158 #[must_use]
159 pub const fn as_str(self) -> &'static str {
160 match self {
161 Self::Default => "default",
162 Self::Shared => "shared",
163 Self::Own => "own",
164 }
165 }
166
167 #[must_use]
168 pub fn from_name(s: &str) -> Option<Self> {
169 Some(match s {
170 "default" => Self::Default,
171 "shared" => Self::Shared,
172 "own" => Self::Own,
173 _ => return None,
174 })
175 }
176}
177
178impl InterpreterConfig {
179 pub const ISOLATED: Self = Self {
180 use_main_obmalloc: false,
181 allow_fork: false,
182 allow_exec: false,
183 allow_threads: true,
184 allow_daemon_threads: false,
185 check_multi_interp_extensions: true,
186 gil: InterpreterGil::Own,
187 };
188
189 pub const LEGACY: Self = Self {
190 use_main_obmalloc: true,
191 allow_fork: true,
192 allow_exec: true,
193 allow_threads: true,
194 allow_daemon_threads: true,
195 check_multi_interp_extensions: false,
196 gil: InterpreterGil::Shared,
197 };
198
199 pub const EMPTY: Self = Self {
200 use_main_obmalloc: false,
201 allow_fork: false,
202 allow_exec: false,
203 allow_threads: false,
204 allow_daemon_threads: false,
205 check_multi_interp_extensions: false,
206 gil: InterpreterGil::Default,
207 };
208
209 /// The config the runtime uses for the main interpreter: legacy features
210 /// with its own GIL.
211 pub const MAIN: Self = Self {
212 gil: InterpreterGil::Own,
213 ..Self::LEGACY
214 };
215
216 #[must_use]
217 pub fn named(name: &str) -> Option<Self> {
218 match name {
219 "" | "default" | "isolated" => Some(Self::ISOLATED),
220 "legacy" => Some(Self::LEGACY),
221 "empty" => Some(Self::EMPTY),
222 _ => None,
223 }
224 }
225
226 #[must_use]
227 pub const fn feature_flags(self) -> InterpFeatureFlags {
228 InterpFeatureFlags {
229 use_main_obmalloc: self.use_main_obmalloc,
230 allow_fork: self.allow_fork,
231 allow_exec: self.allow_exec,
232 allow_threads: self.allow_threads,
233 allow_daemon_threads: self.allow_daemon_threads,
234 check_multi_interp_extensions: self.check_multi_interp_extensions,
235 }
236 }
237
238 /// Rebuild a config from the flags an interpreter kept
239 /// (`_PyInterpreterConfig_InitFromState`).
240 #[must_use]
241 pub const fn from_state(flags: InterpFeatureFlags, own_gil: bool) -> Self {
242 Self {
243 use_main_obmalloc: flags.use_main_obmalloc,
244 allow_fork: flags.allow_fork,
245 allow_exec: flags.allow_exec,
246 allow_threads: flags.allow_threads,
247 allow_daemon_threads: flags.allow_daemon_threads,
248 check_multi_interp_extensions: flags.check_multi_interp_extensions,
249 gil: if own_gil {
250 InterpreterGil::Own
251 } else {
252 InterpreterGil::Shared
253 },
254 }
255 }
256
257 #[must_use]
258 pub const fn own_gil(self) -> bool {
259 matches!(self.gil, InterpreterGil::Own)
260 }
261
262 /// `init_interp_settings`: per-interpreter obmalloc requires the
263 /// multi-interpreter extension check.
264 pub const fn check(self) -> Result<(), &'static str> {
265 if !self.use_main_obmalloc && !self.check_multi_interp_extensions {
266 return Err(
267 "per-interpreter obmalloc does not support single-phase init extension modules",
268 );
269 }
270 Ok(())
271 }
272}
273
274/// Id of the main interpreter (PEP 734 `get_main()`), or `None` before any
275/// interpreter has been created.
276///
277/// This is distinct from [`PyGlobalState::is_main`]: every top-level (non-sub)
278/// interpreter carries `is_main` for its own signal / main-thread bookkeeping,
279/// but only the first one registered becomes *the* main.
280#[must_use]
281pub fn main_interpreter_id() -> Option<i64> {
282 match registry().main_id.load(Ordering::Acquire) {
283 NO_MAIN_INTERPRETER => None,
284 id => Some(id),
285 }
286}
287
288/// Allocate a unique interpreter id.
289///
290/// Ids are strictly monotonic and never reused for the lifetime of the
291/// registry, so concurrent `Interpreter` construction (parallel unit tests,
292/// multi-threaded embedding) never shares an id. Without `threading` the
293/// registry — like `Context::genesis()` and the GC state — is per OS thread, so
294/// ids are unique within a thread rather than across the process.
295pub(crate) fn alloc_interpreter_id() -> i64 {
296 registry().next_id.fetch_add(1, Ordering::Relaxed)
297}
298
299/// Gate between registering an interpreter and a collection's stop-the-world.
300///
301/// A collection snapshots the registry, stops every interpreter in the
302/// snapshot, and then reads tracked objects with those threads parked. An
303/// interpreter that registered after the snapshot was taken would not be in it,
304/// so nothing would stop it, and its bootstrap — which runs Python and mutates
305/// the shared generation lists — would run underneath that scan. Registration
306/// therefore waits for an in-flight stop to end; the next collection's snapshot
307/// then contains the new interpreter.
308fn admission() -> &'static Mutex<()> {
309 static ADMISSION: std::sync::OnceLock<Mutex<()>> = std::sync::OnceLock::new();
310 ADMISSION.get_or_init(|| Mutex::new(()))
311}
312
313/// Take the admission gate for the duration of a stop-the-world.
314#[cfg(feature = "threading")]
315pub(crate) fn lock_admission_for_stop() -> parking_lot::MutexGuard<'static, ()> {
316 admission().lock()
317}
318
319/// Add the registry entry, behind the admission gate.
320///
321/// Only ever called with this thread detached, because the gate is held across
322/// a stop-the-world: an attached thread waiting here, or re-attaching while
323/// holding the gate, would leave that stop no safepoint to complete at. Nothing
324/// under the gate blocks or allocates a tracked object, so this cannot re-enter
325/// the collection it waits for.
326fn insert_registry_entry(state: &PyRc<PyGlobalState>) {
327 let _admission = admission().lock();
328 let mut entries = registry().entries.lock();
329 // Entries are weak and an interpreter's lifetime is decided by its last
330 // `PyRc<PyGlobalState>` — which outlives the `Interpreter` handle whenever
331 // `new_thread()` workers are still running — so nothing removes them at a
332 // fixed point. Reap the dead ones here to bound the table instead.
333 entries.retain(|_, entry| entry.state.strong_count() > 0);
334 entries.insert(
335 state.interpreter_id,
336 RegistryEntry {
337 whence: state.whence,
338 state: PyRc::downgrade(state),
339 },
340 );
341}
342
343/// Register an interpreter state in the registry.
344pub(crate) fn register_interpreter(state: &PyRc<PyGlobalState>) {
345 let id = state.interpreter_id;
346 if state.is_main {
347 // First `is_main` interpreter defines the main for `get_main()`.
348 // Additional top-level Interpreters (embedding) keep their own `is_main`
349 // flag but do not displace the recorded main.
350 let _ = registry().main_id.compare_exchange(
351 NO_MAIN_INTERPRETER,
352 id,
353 Ordering::AcqRel,
354 Ordering::Relaxed,
355 );
356 }
357 // A subinterpreter is registered by a thread that is running its parent, so
358 // detach for the whole insert rather than only for the wait.
359 let detached = crate::vm::thread::try_with_current_vm(|vm| {
360 vm.allow_threads(|| insert_registry_entry(state))
361 });
362 if detached.is_none() {
363 insert_registry_entry(state);
364 }
365}
366
367/// Look up a live interpreter state by id.
368#[must_use]
369pub fn lookup_interpreter(id: i64) -> Option<PyRc<PyGlobalState>> {
370 let entries = registry().entries.lock();
371 entries.get(&id).and_then(|e| e.state.upgrade())
372}
373
374/// List all currently registered (still-alive) interpreters.
375#[must_use]
376pub fn list_interpreters() -> Vec<InterpreterInfo> {
377 let entries = registry().entries.lock();
378 let mut out: Vec<InterpreterInfo> = entries
379 .iter()
380 .filter_map(|(&id, entry)| {
381 // Drop dead weak refs from the listing.
382 if entry.state.strong_count() == 0 {
383 return None;
384 }
385 Some(InterpreterInfo {
386 id,
387 whence: entry.whence,
388 })
389 })
390 .collect();
391 out.sort_by_key(|info| info.id);
392 out
393}
394
395/// Number of registered interpreters that are still alive.
396#[must_use]
397pub fn interpreter_count() -> usize {
398 list_interpreters().len()
399}
400
401/// Reset the registry's locks after `fork()`.
402///
403/// The tables are reachable from every thread, so a thread that died in the
404/// fork may have left one locked; the child would then deadlock the first time
405/// it enumerates interpreters (which the collector now does on every stop).
406///
407/// # Safety
408/// Must only be called after `fork()` in the child process, when no other
409/// threads exist and the calling thread holds none of these locks.
410#[cfg(all(unix, feature = "threading"))]
411pub unsafe fn reinit_after_fork() {
412 unsafe {
413 crate::common::lock::reinit_mutex_after_fork(®istry().entries);
414 crate::common::lock::reinit_mutex_after_fork(owned_interpreters());
415 crate::common::lock::reinit_mutex_after_fork(admission());
416 }
417}
418
419/// All live interpreter states, ordered by id.
420///
421/// Used by the cyclic collector, which must stop every interpreter's threads
422/// (not just the collecting one) because GC-tracked objects from all
423/// interpreters share one object graph. Ordering is deterministic so that
424/// multiple stop-the-world requesters always take exclusions in the same order.
425#[must_use]
426pub fn live_interpreter_states() -> Vec<PyRc<PyGlobalState>> {
427 let entries = registry().entries.lock();
428 let mut states: Vec<(i64, PyRc<PyGlobalState>)> = entries
429 .iter()
430 .filter_map(|(&id, entry)| entry.state.upgrade().map(|state| (id, state)))
431 .collect();
432 drop(entries);
433 states.sort_by_key(|(id, _)| *id);
434 states.into_iter().map(|(_, state)| state).collect()
435}
436
437/// Runtime-owned interpreters (the ownership anchor for the Python
438/// `_interpreters` API).
439///
440/// A Rust [`crate::Interpreter`] handle is normally owned by its Rust caller.
441/// For PEP 734, `_interpreters.create()` returns only an id and the runtime
442/// must keep the interpreter alive until `_interpreters.destroy(id)`. These
443/// functions hold that ownership, keyed by interpreter id, while the weak
444/// [`registry`] above still drives enumeration and lookup.
445///
446/// Only available with the `threading` feature: a runtime-owned interpreter is
447/// reachable from other OS threads, which requires `Interpreter: Send` (true
448/// only when `PyObjectRef` is `Arc`-backed).
449///
450/// The original `Interpreter.vm` is left idle after creation. Callers that
451/// need to run Python obtain a fresh [`crate::vm::thread::ThreadedVirtualMachine`]
452/// via [`owned_new_thread`], which only clones the shared interpreter fields
453/// (`sys`, builtins, `PyGlobalState`).
454#[cfg(feature = "threading")]
455struct OwnedInterpreter {
456 root_id: i64,
457 interpreter: crate::Interpreter,
458}
459
460#[cfg(feature = "threading")]
461fn owned_interpreters() -> &'static Mutex<HashMap<i64, OwnedInterpreter>> {
462 use std::sync::OnceLock;
463 static OWNED: OnceLock<Mutex<HashMap<i64, OwnedInterpreter>>> = OnceLock::new();
464 OWNED.get_or_init(|| Mutex::new(HashMap::new()))
465}
466
467/// Transfer ownership of `interp` to the runtime, returning its id.
468#[cfg(feature = "threading")]
469pub fn store_owned_interpreter(interp: crate::Interpreter) -> i64 {
470 let id = interp.id();
471 let root_id = interp.global_state.runtime_root_id;
472 // Ids are strictly monotonic, so this never displaces (and drops) an
473 // existing entry under the lock.
474 owned_interpreters().lock().insert(
475 id,
476 OwnedInterpreter {
477 root_id,
478 interpreter: interp,
479 },
480 );
481 id
482}
483
484/// Reclaim a runtime-owned interpreter, removing it from the owner table.
485///
486/// The returned handle is dropped by the caller *outside* the owner lock; its
487/// `Drop` unregisters the interpreter from the weak [`registry`].
488#[cfg(feature = "threading")]
489#[must_use]
490pub fn take_owned_interpreter(id: i64) -> Option<crate::Interpreter> {
491 owned_interpreters()
492 .lock()
493 .remove(&id)
494 .map(|owned| owned.interpreter)
495}
496
497/// Create a thread-state VM for a runtime-owned interpreter.
498///
499/// The lock is held only while cloning shared interpreter fields.
500#[cfg(feature = "threading")]
501#[must_use]
502pub fn owned_new_thread(id: i64) -> Option<crate::vm::thread::ThreadedVirtualMachine> {
503 owned_interpreters()
504 .lock()
505 .get(&id)
506 .map(|owned| owned.interpreter.new_thread())
507}
508
509/// Whether `id` refers to a runtime-owned interpreter.
510#[cfg(feature = "threading")]
511#[must_use]
512pub fn is_owned_interpreter(id: i64) -> bool {
513 owned_interpreters().lock().contains_key(&id)
514}
515
516/// Number of runtime-owned interpreters currently alive.
517#[cfg(feature = "threading")]
518#[must_use]
519pub fn owned_interpreter_count() -> usize {
520 owned_interpreters().lock().len()
521}
522
523/// Ids of the runtime-owned interpreters currently alive, oldest first.
524#[cfg(feature = "threading")]
525#[must_use]
526pub fn owned_interpreter_ids() -> Vec<i64> {
527 let mut ids: Vec<i64> = owned_interpreters().lock().keys().copied().collect();
528 ids.sort_unstable();
529 ids
530}
531
532/// Ids of runtime-owned interpreters belonging to `root_id`, oldest first.
533#[cfg(feature = "threading")]
534#[must_use]
535pub fn owned_interpreter_ids_for(root_id: i64) -> Vec<i64> {
536 let mut ids: Vec<i64> = owned_interpreters()
537 .lock()
538 .iter()
539 .filter_map(|(&id, owned)| (owned.root_id == root_id).then_some(id))
540 .collect();
541 ids.sort_unstable();
542 ids
543}
544
545/// Finalize and drop a runtime-owned interpreter.
546///
547/// The caller must already have checked that the interpreter is not the
548/// current one and is not running `__main__`.
549#[cfg(feature = "threading")]
550#[must_use]
551pub fn destroy_owned_interpreter(id: i64) -> Option<()> {
552 let interp = take_owned_interpreter(id)?;
553 // Finalize like `Py_EndInterpreter`: flush, join non-daemons, atexit, GC.
554 let _ = interp.finalize(None);
555 Some(())
556}