mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Add freelists for PyComplex, PyInt, PyRange (#7345)
* Add freelists for PyComplex, PyInt, PyBoundMethod, PyRange - PyComplex(100), PyInt(100), PyBoundMethod(20), PyRange(6) - Use try_with instead of with in all freelist push/pop to prevent panic on thread-local access during thread teardown * Use alloc::dealloc in FreeList::Drop to avoid thread-local access panic During thread teardown, Box::from_raw triggers cascading destructors that may access already-destroyed thread-local storage (GC state, other freelists). Use raw dealloc instead to free memory without running destructors. * Auto-format: cargo fmt --all * Fix clippy: use core::alloc::Layout instead of alloc::alloc::Layout * Address review: PyBoundMethod clear=false, update FreeList doc comment - Set clear=false on PyBoundMethod (tp_clear=NULL in classobject.c) - Update FreeList doc comment to match actual Drop behavior (raw dealloc) * Remove PyBoundMethod freelist to fix refcount/weakref test failures Non-Option PyObjectRef fields retain references in freelist, causing weakref and refcount assertions to fail in test_unittest, test_multiprocessing, and test_socket. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
ce76a7db61
commit
e51248f779
8 files changed
+221
-73
No files matched your search
@@ -10,7 +10,9 @@ use crate::{
|
||||
stdlib::warnings,
|
||||
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
|
||||
};
|
||||
use core::cell::Cell;
|
||||
use core::num::Wrapping;
|
||||
use core::ptr::NonNull;
|
||||
use num_complex::Complex64;
|
||||
use num_traits::Zero;
|
||||
use rustpython_common::hash;
|
||||
@@ -24,11 +26,49 @@ pub struct PyComplex {
|
||||
value: Complex64,
|
||||
}
|
||||
|
||||
// spell-checker:ignore MAXFREELIST
|
||||
thread_local! {
|
||||
static COMPLEX_FREELIST: Cell<crate::object::FreeList<PyComplex>> = const { Cell::new(crate::object::FreeList::new()) };
|
||||
}
|
||||
|
||||
impl PyPayload for PyComplex {
|
||||
const MAX_FREELIST: usize = 100;
|
||||
const HAS_FREELIST: bool = true;
|
||||
|
||||
#[inline]
|
||||
fn class(ctx: &Context) -> &'static Py<PyType> {
|
||||
ctx.types.complex_type
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
COMPLEX_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
COMPLEX_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for Complex64 {
|
||||
|
||||
@@ -77,27 +77,32 @@ impl PyPayload for PyDict {
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
DICT_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
DICT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
DICT_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
DICT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,27 +49,32 @@ impl PyPayload for PyFloat {
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
FLOAT_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
FLOAT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
FLOAT_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
FLOAT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ use crate::{
|
||||
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
|
||||
};
|
||||
use alloc::fmt;
|
||||
use core::cell::Cell;
|
||||
use core::ops::{Neg, Not};
|
||||
use core::ptr::NonNull;
|
||||
use malachite_bigint::{BigInt, Sign};
|
||||
use num_integer::Integer;
|
||||
use num_traits::{One, Pow, PrimInt, Signed, ToPrimitive, Zero};
|
||||
@@ -48,7 +50,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// spell-checker:ignore MAXFREELIST
|
||||
thread_local! {
|
||||
static INT_FREELIST: Cell<crate::object::FreeList<PyInt>> = const { Cell::new(crate::object::FreeList::new()) };
|
||||
}
|
||||
|
||||
impl PyPayload for PyInt {
|
||||
const MAX_FREELIST: usize = 100;
|
||||
const HAS_FREELIST: bool = true;
|
||||
|
||||
#[inline]
|
||||
fn class(ctx: &Context) -> &'static Py<PyType> {
|
||||
ctx.types.int_type
|
||||
@@ -57,6 +67,36 @@ impl PyPayload for PyInt {
|
||||
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.ctx.new_int(self.value).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
INT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
INT_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_pyobject_int {
|
||||
|
||||
@@ -89,27 +89,32 @@ impl PyPayload for PyList {
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
LIST_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
LIST_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
LIST_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
LIST_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ use crate::{
|
||||
Representable, SelfIter,
|
||||
},
|
||||
};
|
||||
use core::cell::Cell;
|
||||
use core::cmp::max;
|
||||
use core::ptr::NonNull;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use malachite_bigint::{BigInt, Sign};
|
||||
use num_integer::Integer;
|
||||
@@ -67,11 +69,49 @@ pub struct PyRange {
|
||||
pub step: PyIntRef,
|
||||
}
|
||||
|
||||
// spell-checker:ignore MAXFREELIST
|
||||
thread_local! {
|
||||
static RANGE_FREELIST: Cell<crate::object::FreeList<PyRange>> = const { Cell::new(crate::object::FreeList::new()) };
|
||||
}
|
||||
|
||||
impl PyPayload for PyRange {
|
||||
const MAX_FREELIST: usize = 6;
|
||||
const HAS_FREELIST: bool = true;
|
||||
|
||||
#[inline]
|
||||
fn class(ctx: &Context) -> &'static Py<PyType> {
|
||||
ctx.types.range_type
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
RANGE_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
RANGE_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl PyRange {
|
||||
|
||||
@@ -60,27 +60,32 @@ impl PyPayload for PySlice {
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
|
||||
SLICE_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
SLICE_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let stored = if list.len() < Self::MAX_FREELIST {
|
||||
list.push(obj);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
fl.set(list);
|
||||
stored
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
|
||||
SLICE_FREELIST.with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
SLICE_FREELIST
|
||||
.try_with(|fl| {
|
||||
let mut list = fl.take();
|
||||
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
|
||||
fl.set(list);
|
||||
result
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -858,11 +858,11 @@ impl<T: PyPayload + core::fmt::Debug> PyInner<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-local freelist storage that properly deallocates cached objects
|
||||
/// on thread teardown.
|
||||
/// Thread-local freelist storage for reusing object allocations.
|
||||
///
|
||||
/// Wraps a `Vec<*mut PyObject>` and implements `Drop` to convert each
|
||||
/// raw pointer back into `Box<PyInner<T>>` for proper deallocation.
|
||||
/// Wraps a `Vec<*mut PyObject>`. On thread teardown, `Drop` frees raw
|
||||
/// `PyInner<T>` allocations without running payload destructors to avoid
|
||||
/// accessing already-destroyed thread-local storage (GC state, other freelists).
|
||||
pub(crate) struct FreeList<T: PyPayload> {
|
||||
items: Vec<*mut PyObject>,
|
||||
_marker: core::marker::PhantomData<T>,
|
||||
@@ -885,8 +885,16 @@ impl<T: PyPayload> Default for FreeList<T> {
|
||||
|
||||
impl<T: PyPayload> Drop for FreeList<T> {
|
||||
fn drop(&mut self) {
|
||||
// During thread teardown, we cannot safely run destructors on cached
|
||||
// objects because their Drop impls may access thread-local storage
|
||||
// (GC state, other freelists) that is already destroyed.
|
||||
// Instead, free just the raw allocation. The payload's heap fields
|
||||
// (BigInt, PyObjectRef, etc.) are leaked, but this is bounded by
|
||||
// MAX_FREELIST per type per thread.
|
||||
for ptr in self.items.drain(..) {
|
||||
drop(unsafe { Box::from_raw(ptr as *mut PyInner<T>) });
|
||||
unsafe {
|
||||
alloc::alloc::dealloc(ptr as *mut u8, core::alloc::Layout::new::<PyInner<T>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user