Skip to main content

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(&registry().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}