Keep functools Placeholder identity in interpreter state (#9198)

Fresh _functools imports must not replace the singleton returned for existing Placeholder objects. Share initialization within each interpreter and return the published winner under racing imports.

Assisted-by: OpenAI Codex: model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-19 20:14:16 +09:00
1 parent 23da4ac248
commit fc853015d1
3 files changed
+14 -13

No files matched your search

+11 -13
View File
@@ -179,16 +179,19 @@ mod _functools {
}
}
// Placeholder singleton for partial arguments
// The singleton is stored as _instance on the type class
// Keep identity in interpreter state, not on the shared static type.
#[pyattr]
#[allow(non_snake_case)]
fn Placeholder(vm: &VirtualMachine) -> PyObjectRef {
let cell = &vm.state.functools_placeholder;
if let Some(placeholder) = cell.get() {
return placeholder.clone();
}
// Allocate outside the cell initializer: allocation may trigger GC.
// Another thread can publish first; always return the published winner.
let placeholder = PyPlaceholderType.into_pyobject(vm);
// Store the singleton on the type class for slot_new to find
let typ = placeholder.class();
typ.set_attr(vm.ctx.intern_str("_instance"), placeholder.clone());
placeholder
let _ = cell.set(placeholder);
cell.get().expect("Placeholder was initialized").clone()
}
#[pyattr]
@@ -199,16 +202,11 @@ mod _functools {
impl Constructor for PyPlaceholderType {
type Args = FuncArgs;
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
if !args.args.is_empty() || !args.kwargs.is_empty() {
return Err(vm.new_type_error("_PlaceholderType takes no arguments"));
}
// Return the singleton stored on the type class
if let Some(instance) = cls.get_attr(vm.ctx.intern_str("_instance")) {
return Ok(instance);
}
// Fallback: create a new instance (shouldn't happen for base type after module init)
Ok(Self.into_pyobject(vm))
Ok(Placeholder(vm))
}
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
+1
View File
@@ -169,6 +169,7 @@ where
import_time_header: AtomicBool::new(false),
lazy_imports,
struct_format_cache: crate::buffer::FormatSpecCache::default(),
functools_placeholder: Default::default(),
finalizing: AtomicBool::new(false),
#[cfg(feature = "threading")]
finalizing_thread_ident: AtomicCell::new(0),
+2
View File
@@ -879,6 +879,8 @@ pub struct PyGlobalState {
pub(crate) import_time_header: AtomicBool,
pub(crate) lazy_imports: crate::lazy_import::LazyImportsState,
pub struct_format_cache: crate::buffer::FormatSpecCache,
/// Placeholder identity shared by all threads and fresh imports in this interpreter.
pub(crate) functools_placeholder: crate::common::lock::OnceCell<PyObjectRef>,
pub finalizing: AtomicBool,
/// The thread performing finalization, which need not be the process main thread.
#[cfg(feature = "threading")]