use crate::common::rc::PyRc;
use crate::vm::PyGlobalState;
use core::sync::atomic::{AtomicI64, Ordering};
use parking_lot::Mutex;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum InterpreterWhence {
Unknown = 0,
Runtime = 1,
LegacyCapi = 2,
Capi = 3,
Xi = 4,
Stdlib = 5,
}
impl InterpreterWhence {
#[must_use]
pub const fn as_i32(self) -> i32 {
self as i32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterpreterInfo {
pub id: i64,
pub whence: InterpreterWhence,
}
struct RegistryEntry {
whence: InterpreterWhence,
#[cfg(feature = "threading")]
state: alloc::sync::Weak<PyGlobalState>,
#[cfg(not(feature = "threading"))]
state: alloc::rc::Weak<PyGlobalState>,
}
const NO_MAIN_INTERPRETER: i64 = -1;
struct InterpreterRegistry {
next_id: AtomicI64,
main_id: AtomicI64,
entries: Mutex<HashMap<i64, RegistryEntry>>,
}
impl InterpreterRegistry {
fn new() -> Self {
Self {
next_id: AtomicI64::new(0),
main_id: AtomicI64::new(NO_MAIN_INTERPRETER),
entries: Mutex::new(HashMap::new()),
}
}
}
fn registry() -> &'static InterpreterRegistry {
rustpython_common::static_cell! {
static REGISTRY: InterpreterRegistry;
}
REGISTRY.get_or_init(InterpreterRegistry::new)
}
pub const MAIN_INTERPRETER_ID: i64 = 0;
pub const SUPPORTS_ISOLATED_INTERPRETERS: bool =
cfg!(all(feature = "threading", not(target_arch = "wasm32")));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterpFeatureFlags {
pub use_main_obmalloc: bool,
pub allow_fork: bool,
pub allow_exec: bool,
pub allow_threads: bool,
pub allow_daemon_threads: bool,
pub check_multi_interp_extensions: bool,
}
impl InterpFeatureFlags {
pub const ISOLATED: Self = InterpreterConfig::ISOLATED.feature_flags();
pub const LEGACY: Self = InterpreterConfig::LEGACY.feature_flags();
}
impl Default for InterpFeatureFlags {
fn default() -> Self {
Self::LEGACY
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterpreterConfig {
pub use_main_obmalloc: bool,
pub allow_fork: bool,
pub allow_exec: bool,
pub allow_threads: bool,
pub allow_daemon_threads: bool,
pub check_multi_interp_extensions: bool,
pub gil: InterpreterGil,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterpreterGil {
Default,
Shared,
Own,
}
impl InterpreterGil {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Default => "default",
Self::Shared => "shared",
Self::Own => "own",
}
}
#[must_use]
pub fn from_name(s: &str) -> Option<Self> {
Some(match s {
"default" => Self::Default,
"shared" => Self::Shared,
"own" => Self::Own,
_ => return None,
})
}
}
impl InterpreterConfig {
pub const ISOLATED: Self = Self {
use_main_obmalloc: false,
allow_fork: false,
allow_exec: false,
allow_threads: true,
allow_daemon_threads: false,
check_multi_interp_extensions: true,
gil: InterpreterGil::Own,
};
pub const LEGACY: Self = Self {
use_main_obmalloc: true,
allow_fork: true,
allow_exec: true,
allow_threads: true,
allow_daemon_threads: true,
check_multi_interp_extensions: false,
gil: InterpreterGil::Shared,
};
pub const EMPTY: Self = Self {
use_main_obmalloc: false,
allow_fork: false,
allow_exec: false,
allow_threads: false,
allow_daemon_threads: false,
check_multi_interp_extensions: false,
gil: InterpreterGil::Default,
};
pub const MAIN: Self = Self {
gil: InterpreterGil::Own,
..Self::LEGACY
};
#[must_use]
pub fn named(name: &str) -> Option<Self> {
match name {
"" | "default" | "isolated" => Some(Self::ISOLATED),
"legacy" => Some(Self::LEGACY),
"empty" => Some(Self::EMPTY),
_ => None,
}
}
#[must_use]
pub const fn feature_flags(self) -> InterpFeatureFlags {
InterpFeatureFlags {
use_main_obmalloc: self.use_main_obmalloc,
allow_fork: self.allow_fork,
allow_exec: self.allow_exec,
allow_threads: self.allow_threads,
allow_daemon_threads: self.allow_daemon_threads,
check_multi_interp_extensions: self.check_multi_interp_extensions,
}
}
#[must_use]
pub const fn from_state(flags: InterpFeatureFlags, own_gil: bool) -> Self {
Self {
use_main_obmalloc: flags.use_main_obmalloc,
allow_fork: flags.allow_fork,
allow_exec: flags.allow_exec,
allow_threads: flags.allow_threads,
allow_daemon_threads: flags.allow_daemon_threads,
check_multi_interp_extensions: flags.check_multi_interp_extensions,
gil: if own_gil {
InterpreterGil::Own
} else {
InterpreterGil::Shared
},
}
}
#[must_use]
pub const fn own_gil(self) -> bool {
matches!(self.gil, InterpreterGil::Own)
}
pub const fn check(self) -> Result<(), &'static str> {
if !self.use_main_obmalloc && !self.check_multi_interp_extensions {
return Err(
"per-interpreter obmalloc does not support single-phase init extension modules",
);
}
Ok(())
}
}
#[must_use]
pub fn main_interpreter_id() -> Option<i64> {
match registry().main_id.load(Ordering::Acquire) {
NO_MAIN_INTERPRETER => None,
id => Some(id),
}
}
pub(crate) fn alloc_interpreter_id() -> i64 {
registry().next_id.fetch_add(1, Ordering::Relaxed)
}
fn admission() -> &'static Mutex<()> {
static ADMISSION: std::sync::OnceLock<Mutex<()>> = std::sync::OnceLock::new();
ADMISSION.get_or_init(|| Mutex::new(()))
}
#[cfg(feature = "threading")]
pub(crate) fn lock_admission_for_stop() -> parking_lot::MutexGuard<'static, ()> {
admission().lock()
}
fn insert_registry_entry(state: &PyRc<PyGlobalState>) {
let _admission = admission().lock();
let mut entries = registry().entries.lock();
entries.retain(|_, entry| entry.state.strong_count() > 0);
entries.insert(
state.interpreter_id,
RegistryEntry {
whence: state.whence,
state: PyRc::downgrade(state),
},
);
}
pub(crate) fn register_interpreter(state: &PyRc<PyGlobalState>) {
let id = state.interpreter_id;
if state.is_main {
let _ = registry().main_id.compare_exchange(
NO_MAIN_INTERPRETER,
id,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
let detached = crate::vm::thread::try_with_current_vm(|vm| {
vm.allow_threads(|| insert_registry_entry(state))
});
if detached.is_none() {
insert_registry_entry(state);
}
}
#[must_use]
pub fn lookup_interpreter(id: i64) -> Option<PyRc<PyGlobalState>> {
let entries = registry().entries.lock();
entries.get(&id).and_then(|e| e.state.upgrade())
}
#[must_use]
pub fn list_interpreters() -> Vec<InterpreterInfo> {
let entries = registry().entries.lock();
let mut out: Vec<InterpreterInfo> = entries
.iter()
.filter_map(|(&id, entry)| {
if entry.state.strong_count() == 0 {
return None;
}
Some(InterpreterInfo {
id,
whence: entry.whence,
})
})
.collect();
out.sort_by_key(|info| info.id);
out
}
#[must_use]
pub fn interpreter_count() -> usize {
list_interpreters().len()
}
#[cfg(all(unix, feature = "threading"))]
pub unsafe fn reinit_after_fork() {
unsafe {
crate::common::lock::reinit_mutex_after_fork(®istry().entries);
crate::common::lock::reinit_mutex_after_fork(owned_interpreters());
crate::common::lock::reinit_mutex_after_fork(admission());
}
}
#[must_use]
pub fn live_interpreter_states() -> Vec<PyRc<PyGlobalState>> {
let entries = registry().entries.lock();
let mut states: Vec<(i64, PyRc<PyGlobalState>)> = entries
.iter()
.filter_map(|(&id, entry)| entry.state.upgrade().map(|state| (id, state)))
.collect();
drop(entries);
states.sort_by_key(|(id, _)| *id);
states.into_iter().map(|(_, state)| state).collect()
}
#[cfg(feature = "threading")]
struct OwnedInterpreter {
root_id: i64,
interpreter: crate::Interpreter,
}
#[cfg(feature = "threading")]
fn owned_interpreters() -> &'static Mutex<HashMap<i64, OwnedInterpreter>> {
use std::sync::OnceLock;
static OWNED: OnceLock<Mutex<HashMap<i64, OwnedInterpreter>>> = OnceLock::new();
OWNED.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(feature = "threading")]
pub fn store_owned_interpreter(interp: crate::Interpreter) -> i64 {
let id = interp.id();
let root_id = interp.global_state.runtime_root_id;
owned_interpreters().lock().insert(
id,
OwnedInterpreter {
root_id,
interpreter: interp,
},
);
id
}
#[cfg(feature = "threading")]
#[must_use]
pub fn take_owned_interpreter(id: i64) -> Option<crate::Interpreter> {
owned_interpreters()
.lock()
.remove(&id)
.map(|owned| owned.interpreter)
}
#[cfg(feature = "threading")]
#[must_use]
pub fn owned_new_thread(id: i64) -> Option<crate::vm::thread::ThreadedVirtualMachine> {
owned_interpreters()
.lock()
.get(&id)
.map(|owned| owned.interpreter.new_thread())
}
#[cfg(feature = "threading")]
#[must_use]
pub fn is_owned_interpreter(id: i64) -> bool {
owned_interpreters().lock().contains_key(&id)
}
#[cfg(feature = "threading")]
#[must_use]
pub fn owned_interpreter_count() -> usize {
owned_interpreters().lock().len()
}
#[cfg(feature = "threading")]
#[must_use]
pub fn owned_interpreter_ids() -> Vec<i64> {
let mut ids: Vec<i64> = owned_interpreters().lock().keys().copied().collect();
ids.sort_unstable();
ids
}
#[cfg(feature = "threading")]
#[must_use]
pub fn owned_interpreter_ids_for(root_id: i64) -> Vec<i64> {
let mut ids: Vec<i64> = owned_interpreters()
.lock()
.iter()
.filter_map(|(&id, owned)| (owned.root_id == root_id).then_some(id))
.collect();
ids.sort_unstable();
ids
}
#[cfg(feature = "threading")]
#[must_use]
pub fn destroy_owned_interpreter(id: i64) -> Option<()> {
let interp = take_owned_interpreter(id)?;
let _ = interp.finalize(None);
Some(())
}