split `PyCell` and `PyClassObject` concepts (#3917)
* add test for refguard ref counting * split `PyCell` and `PyClassObject` concepts * rework `create_cell` to `create_class_object` * Apply suggestions from code review Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * review: Icxolu feedback --------- Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
This commit is contained in:
parent
81be11e67a
commit
2e56f659ed
|
@ -645,8 +645,7 @@ impl<'a> FnSpec<'a> {
|
||||||
#( #holders )*
|
#( #holders )*
|
||||||
let result = #call;
|
let result = #call;
|
||||||
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?;
|
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?;
|
||||||
let cell = initializer.create_cell_from_subtype(py, _slf)?;
|
_pyo3::impl_::pymethods::tp_new_impl(py, initializer, _slf)
|
||||||
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coroutine::{cancel::ThrowCallback, Coroutine},
|
coroutine::{cancel::ThrowCallback, Coroutine},
|
||||||
instance::Bound,
|
instance::Bound,
|
||||||
|
pycell::impl_::PyClassBorrowChecker,
|
||||||
pyclass::boolean_struct::False,
|
pyclass::boolean_struct::False,
|
||||||
types::{PyAnyMethods, PyString},
|
types::{PyAnyMethods, PyString},
|
||||||
IntoPy, Py, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult, Python,
|
IntoPy, Py, PyAny, PyClass, PyErr, PyObject, PyResult, Python,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn new_coroutine<F, T, E>(
|
pub fn new_coroutine<F, T, E>(
|
||||||
|
@ -32,17 +32,16 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ptr<T: PyClass>(obj: &Py<T>) -> *mut T {
|
fn get_ptr<T: PyClass>(obj: &Py<T>) -> *mut T {
|
||||||
// SAFETY: Py<T> can be casted as *const PyCell<T>
|
obj.get_class_object().get_ptr()
|
||||||
unsafe { &*(obj.as_ptr() as *const PyCell<T>) }.get_ptr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RefGuard<T: PyClass>(Py<T>);
|
pub struct RefGuard<T: PyClass>(Py<T>);
|
||||||
|
|
||||||
impl<T: PyClass> RefGuard<T> {
|
impl<T: PyClass> RefGuard<T> {
|
||||||
pub fn new(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
|
pub fn new(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||||
let owned = obj.downcast::<T>()?;
|
let bound = obj.downcast::<T>()?;
|
||||||
mem::forget(owned.try_borrow()?);
|
bound.get_class_object().borrow_checker().try_borrow()?;
|
||||||
Ok(RefGuard(owned.clone().unbind()))
|
Ok(RefGuard(bound.clone().unbind()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,9 +56,11 @@ impl<T: PyClass> Deref for RefGuard<T> {
|
||||||
impl<T: PyClass> Drop for RefGuard<T> {
|
impl<T: PyClass> Drop for RefGuard<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
Python::with_gil(|gil| {
|
Python::with_gil(|gil| {
|
||||||
#[allow(deprecated)]
|
self.0
|
||||||
let self_ref = self.0.bind(gil);
|
.bind(gil)
|
||||||
self_ref.release_ref()
|
.get_class_object()
|
||||||
|
.borrow_checker()
|
||||||
|
.release_borrow()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,9 +69,9 @@ pub struct RefMutGuard<T: PyClass<Frozen = False>>(Py<T>);
|
||||||
|
|
||||||
impl<T: PyClass<Frozen = False>> RefMutGuard<T> {
|
impl<T: PyClass<Frozen = False>> RefMutGuard<T> {
|
||||||
pub fn new(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
|
pub fn new(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||||
let owned = obj.downcast::<T>()?;
|
let bound = obj.downcast::<T>()?;
|
||||||
mem::forget(owned.try_borrow_mut()?);
|
bound.get_class_object().borrow_checker().try_borrow_mut()?;
|
||||||
Ok(RefMutGuard(owned.clone().unbind()))
|
Ok(RefMutGuard(bound.clone().unbind()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +93,11 @@ impl<T: PyClass<Frozen = False>> DerefMut for RefMutGuard<T> {
|
||||||
impl<T: PyClass<Frozen = False>> Drop for RefMutGuard<T> {
|
impl<T: PyClass<Frozen = False>> Drop for RefMutGuard<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
Python::with_gil(|gil| {
|
Python::with_gil(|gil| {
|
||||||
#[allow(deprecated)]
|
self.0
|
||||||
let self_ref = self.0.bind(gil);
|
.bind(gil)
|
||||||
self_ref.release_mut()
|
.get_class_object()
|
||||||
|
.borrow_checker()
|
||||||
|
.release_borrow_mut()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
//! Externally-accessible implementation of pycell
|
//! Externally-accessible implementation of pycell
|
||||||
pub use crate::pycell::impl_::{GetBorrowChecker, PyClassMutability};
|
pub use crate::pycell::impl_::{
|
||||||
|
GetBorrowChecker, PyClassMutability, PyClassObject, PyClassObjectBase, PyClassObjectLayout,
|
||||||
|
};
|
||||||
|
|
|
@ -2,14 +2,13 @@ use crate::{
|
||||||
exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError, PyValueError},
|
exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError, PyValueError},
|
||||||
ffi,
|
ffi,
|
||||||
impl_::freelist::FreeList,
|
impl_::freelist::FreeList,
|
||||||
impl_::pycell::{GetBorrowChecker, PyClassMutability},
|
impl_::pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
|
||||||
internal_tricks::extract_c_string,
|
internal_tricks::extract_c_string,
|
||||||
pycell::PyCellLayout,
|
|
||||||
pyclass_init::PyObjectInit,
|
pyclass_init::PyObjectInit,
|
||||||
types::any::PyAnyMethods,
|
types::any::PyAnyMethods,
|
||||||
types::PyBool,
|
types::PyBool,
|
||||||
Borrowed, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult,
|
Borrowed, Py, PyAny, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo,
|
||||||
PyTypeInfo, Python,
|
Python,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -26,13 +25,13 @@ pub use lazy_type_object::LazyTypeObject;
|
||||||
/// Gets the offset of the dictionary from the start of the object in bytes.
|
/// Gets the offset of the dictionary from the start of the object in bytes.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
|
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
|
||||||
PyCell::<T>::dict_offset()
|
PyClassObject::<T>::dict_offset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the offset of the weakref list from the start of the object in bytes.
|
/// Gets the offset of the weakref list from the start of the object in bytes.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
|
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
|
||||||
PyCell::<T>::weaklist_offset()
|
PyClassObject::<T>::weaklist_offset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the `__dict__` field for `#[pyclass]`.
|
/// Represents the `__dict__` field for `#[pyclass]`.
|
||||||
|
@ -883,6 +882,8 @@ macro_rules! generate_pyclass_richcompare_slot {
|
||||||
}
|
}
|
||||||
pub use generate_pyclass_richcompare_slot;
|
pub use generate_pyclass_richcompare_slot;
|
||||||
|
|
||||||
|
use super::pycell::PyClassObject;
|
||||||
|
|
||||||
/// Implements a freelist.
|
/// Implements a freelist.
|
||||||
///
|
///
|
||||||
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
||||||
|
@ -1095,7 +1096,7 @@ impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
|
||||||
|
|
||||||
/// Trait denoting that this class is suitable to be used as a base type for PyClass.
|
/// Trait denoting that this class is suitable to be used as a base type for PyClass.
|
||||||
pub trait PyClassBaseType: Sized {
|
pub trait PyClassBaseType: Sized {
|
||||||
type LayoutAsBase: PyCellLayout<Self>;
|
type LayoutAsBase: PyClassObjectLayout<Self>;
|
||||||
type BaseNativeType;
|
type BaseNativeType;
|
||||||
type Initializer: PyObjectInit<Self>;
|
type Initializer: PyObjectInit<Self>;
|
||||||
type PyClassMutability: PyClassMutability;
|
type PyClassMutability: PyClassMutability;
|
||||||
|
@ -1105,7 +1106,7 @@ pub trait PyClassBaseType: Sized {
|
||||||
///
|
///
|
||||||
/// In the future this will be extended to immutable PyClasses too.
|
/// In the future this will be extended to immutable PyClasses too.
|
||||||
impl<T: PyClass> PyClassBaseType for T {
|
impl<T: PyClass> PyClassBaseType for T {
|
||||||
type LayoutAsBase = crate::pycell::PyCell<T>;
|
type LayoutAsBase = crate::impl_::pycell::PyClassObject<T>;
|
||||||
type BaseNativeType = T::BaseNativeType;
|
type BaseNativeType = T::BaseNativeType;
|
||||||
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
|
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
|
||||||
type PyClassMutability = T::PyClassMutability;
|
type PyClassMutability = T::PyClassMutability;
|
||||||
|
@ -1113,7 +1114,7 @@ impl<T: PyClass> PyClassBaseType for T {
|
||||||
|
|
||||||
/// Implementation of tp_dealloc for pyclasses without gc
|
/// Implementation of tp_dealloc for pyclasses without gc
|
||||||
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
|
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
|
||||||
crate::impl_::trampoline::dealloc(obj, PyCell::<T>::tp_dealloc)
|
crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of tp_dealloc for pyclasses with gc
|
/// Implementation of tp_dealloc for pyclasses with gc
|
||||||
|
@ -1122,7 +1123,7 @@ pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::Py
|
||||||
{
|
{
|
||||||
ffi::PyObject_GC_UnTrack(obj.cast());
|
ffi::PyObject_GC_UnTrack(obj.cast());
|
||||||
}
|
}
|
||||||
crate::impl_::trampoline::dealloc(obj, PyCell::<T>::tp_dealloc)
|
crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
|
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::pycell::{PyBorrowError, PyBorrowMutError};
|
||||||
use crate::pyclass::boolean_struct::False;
|
use crate::pyclass::boolean_struct::False;
|
||||||
use crate::types::{any::PyAnyMethods, PyModule, PyType};
|
use crate::types::{any::PyAnyMethods, PyModule, PyType};
|
||||||
use crate::{
|
use crate::{
|
||||||
ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyCell, PyClass, PyErr, PyObject, PyRef,
|
ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyCell, PyClass, PyClassInitializer, PyErr,
|
||||||
PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
|
PyObject, PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
@ -569,3 +569,13 @@ impl<'py, T> std::ops::Deref for BoundRef<'_, 'py, T> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn tp_new_impl<T: PyClass>(
|
||||||
|
py: Python<'_>,
|
||||||
|
initializer: PyClassInitializer<T>,
|
||||||
|
target_type: *mut ffi::PyTypeObject,
|
||||||
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
initializer
|
||||||
|
.create_class_object_of_type(py, target_type)
|
||||||
|
.map(Bound::into_ptr)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::err::{self, PyDowncastError, PyErr, PyResult};
|
use crate::err::{self, PyDowncastError, PyErr, PyResult};
|
||||||
use crate::ffi_ptr_ext::FfiPtrExt;
|
use crate::impl_::pycell::PyClassObject;
|
||||||
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
|
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
|
||||||
use crate::pyclass::boolean_struct::{False, True};
|
use crate::pyclass::boolean_struct::{False, True};
|
||||||
use crate::type_object::HasPyGilRef;
|
use crate::type_object::HasPyGilRef;
|
||||||
|
@ -92,14 +92,7 @@ where
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
value: impl Into<PyClassInitializer<T>>,
|
value: impl Into<PyClassInitializer<T>>,
|
||||||
) -> PyResult<Bound<'py, T>> {
|
) -> PyResult<Bound<'py, T>> {
|
||||||
let initializer = value.into();
|
value.into().create_class_object(py)
|
||||||
let obj = initializer.create_cell(py)?;
|
|
||||||
let ob = unsafe {
|
|
||||||
obj.cast::<ffi::PyObject>()
|
|
||||||
.assume_owned(py)
|
|
||||||
.downcast_into_unchecked()
|
|
||||||
};
|
|
||||||
Ok(ob)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,19 +325,11 @@ where
|
||||||
where
|
where
|
||||||
T: PyClass<Frozen = True> + Sync,
|
T: PyClass<Frozen = True> + Sync,
|
||||||
{
|
{
|
||||||
let cell = self.get_cell();
|
self.1.get()
|
||||||
// SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`.
|
|
||||||
unsafe { &*cell.get_ptr() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_cell(&'py self) -> &'py PyCell<T> {
|
pub(crate) fn get_class_object(&self) -> &PyClassObject<T> {
|
||||||
let cell = self.as_ptr().cast::<PyCell<T>>();
|
self.1.get_class_object()
|
||||||
// SAFETY: Bound<T> is known to contain an object which is laid out in memory as a
|
|
||||||
// PyCell<T>.
|
|
||||||
//
|
|
||||||
// Strictly speaking for now `&'py PyCell<T>` is part of the "GIL Ref" API, so this
|
|
||||||
// could use some further refactoring later to avoid going through this reference.
|
|
||||||
unsafe { &*cell }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -887,10 +872,7 @@ where
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<Py<T>> {
|
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<Py<T>> {
|
||||||
let initializer = value.into();
|
Bound::new(py, value).map(Bound::unbind)
|
||||||
let obj = initializer.create_cell(py)?;
|
|
||||||
let ob = unsafe { Py::from_owned_ptr(py, obj as _) };
|
|
||||||
Ok(ob)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1194,12 +1176,16 @@ where
|
||||||
where
|
where
|
||||||
T: PyClass<Frozen = True> + Sync,
|
T: PyClass<Frozen = True> + Sync,
|
||||||
{
|
{
|
||||||
let any = self.as_ptr() as *const PyAny;
|
// Safety: The class itself is frozen and `Sync`
|
||||||
// SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`.
|
unsafe { &*self.get_class_object().get_ptr() }
|
||||||
unsafe {
|
}
|
||||||
let cell: &PyCell<T> = PyNativeType::unchecked_downcast(&*any);
|
|
||||||
&*cell.get_ptr()
|
/// Get a view on the underlying `PyClass` contents.
|
||||||
}
|
pub(crate) fn get_class_object(&self) -> &PyClassObject<T> {
|
||||||
|
let class_object = self.as_ptr().cast::<PyClassObject<T>>();
|
||||||
|
// Safety: Bound<T: PyClass> is known to contain an object which is laid out in memory as a
|
||||||
|
// PyClassObject<T>.
|
||||||
|
unsafe { &*class_object }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
197
src/pycell.rs
197
src/pycell.rs
|
@ -192,13 +192,10 @@
|
||||||
//! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability"
|
//! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability"
|
||||||
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
|
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
|
||||||
|
|
||||||
#[allow(deprecated)]
|
use crate::conversion::{AsPyPointer, ToPyObject};
|
||||||
use crate::conversion::FromPyPointer;
|
|
||||||
use crate::exceptions::PyRuntimeError;
|
use crate::exceptions::PyRuntimeError;
|
||||||
use crate::ffi_ptr_ext::FfiPtrExt;
|
use crate::ffi_ptr_ext::FfiPtrExt;
|
||||||
use crate::impl_::pyclass::{
|
use crate::impl_::pyclass::PyClassImpl;
|
||||||
PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef,
|
|
||||||
};
|
|
||||||
use crate::pyclass::{
|
use crate::pyclass::{
|
||||||
boolean_struct::{False, True},
|
boolean_struct::{False, True},
|
||||||
PyClass,
|
PyClass,
|
||||||
|
@ -207,28 +204,15 @@ use crate::pyclass_init::PyClassInitializer;
|
||||||
use crate::type_object::{PyLayout, PySizedLayout};
|
use crate::type_object::{PyLayout, PySizedLayout};
|
||||||
use crate::types::any::PyAnyMethods;
|
use crate::types::any::PyAnyMethods;
|
||||||
use crate::types::PyAny;
|
use crate::types::PyAny;
|
||||||
use crate::{
|
|
||||||
conversion::{AsPyPointer, ToPyObject},
|
|
||||||
type_object::get_tp_free,
|
|
||||||
PyTypeInfo,
|
|
||||||
};
|
|
||||||
use crate::{ffi, Bound, IntoPy, PyErr, PyNativeType, PyObject, PyResult, PyTypeCheck, Python};
|
use crate::{ffi, Bound, IntoPy, PyErr, PyNativeType, PyObject, PyResult, PyTypeCheck, Python};
|
||||||
use std::cell::UnsafeCell;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub(crate) mod impl_;
|
pub(crate) mod impl_;
|
||||||
use impl_::{GetBorrowChecker, PyClassBorrowChecker, PyClassMutability};
|
use impl_::PyClassBorrowChecker;
|
||||||
|
|
||||||
/// Base layout of PyCell.
|
use self::impl_::{PyClassObject, PyClassObjectLayout};
|
||||||
#[doc(hidden)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct PyCellBase<T> {
|
|
||||||
ob_base: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T, U> PyLayout<T> for PyCellBase<U> where U: PySizedLayout<T> {}
|
|
||||||
|
|
||||||
/// A container type for (mutably) accessing [`PyClass`] values
|
/// A container type for (mutably) accessing [`PyClass`] values
|
||||||
///
|
///
|
||||||
|
@ -266,20 +250,8 @@ unsafe impl<T, U> PyLayout<T> for PyCellBase<U> where U: PySizedLayout<T> {}
|
||||||
/// ```
|
/// ```
|
||||||
/// For more information on how, when and why (not) to use `PyCell` please see the
|
/// For more information on how, when and why (not) to use `PyCell` please see the
|
||||||
/// [module-level documentation](self).
|
/// [module-level documentation](self).
|
||||||
#[repr(C)]
|
#[repr(transparent)]
|
||||||
pub struct PyCell<T: PyClassImpl> {
|
pub struct PyCell<T: PyClassImpl>(PyClassObject<T>);
|
||||||
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
|
||||||
contents: PyCellContents<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub(crate) struct PyCellContents<T: PyClassImpl> {
|
|
||||||
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
|
|
||||||
pub(crate) borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage,
|
|
||||||
pub(crate) thread_checker: T::ThreadChecker,
|
|
||||||
pub(crate) dict: T::Dict,
|
|
||||||
pub(crate) weakref: T::WeakRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {
|
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {
|
||||||
type AsRefSource = T;
|
type AsRefSource = T;
|
||||||
|
@ -298,12 +270,7 @@ impl<T: PyClass> PyCell<T> {
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<&Self> {
|
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<&Self> {
|
||||||
unsafe {
|
Bound::new(py, value).map(Bound::into_gil_ref)
|
||||||
let initializer = value.into();
|
|
||||||
let self_ = initializer.create_cell(py)?;
|
|
||||||
#[allow(deprecated)]
|
|
||||||
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Immutably borrows the value `T`. This borrow lasts as long as the returned `PyRef` exists.
|
/// Immutably borrows the value `T`. This borrow lasts as long as the returned `PyRef` exists.
|
||||||
|
@ -423,10 +390,11 @@ impl<T: PyClass> PyCell<T> {
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> {
|
pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> {
|
||||||
self.ensure_threadsafe();
|
self.0.ensure_threadsafe();
|
||||||
self.borrow_checker()
|
self.0
|
||||||
|
.borrow_checker()
|
||||||
.try_borrow_unguarded()
|
.try_borrow_unguarded()
|
||||||
.map(|_: ()| &*self.contents.value.get())
|
.map(|_: ()| &*self.0.get_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide an immutable borrow of the value `T` without acquiring the GIL.
|
/// Provide an immutable borrow of the value `T` without acquiring the GIL.
|
||||||
|
@ -506,45 +474,7 @@ impl<T: PyClass> PyCell<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_ptr(&self) -> *mut T {
|
pub(crate) fn get_ptr(&self) -> *mut T {
|
||||||
self.contents.value.get()
|
self.0.get_ptr()
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the offset of the dictionary from the start of the struct in bytes.
|
|
||||||
pub(crate) fn dict_offset() -> ffi::Py_ssize_t {
|
|
||||||
use memoffset::offset_of;
|
|
||||||
|
|
||||||
let offset = offset_of!(PyCell<T>, contents) + offset_of!(PyCellContents<T>, dict);
|
|
||||||
|
|
||||||
// Py_ssize_t may not be equal to isize on all platforms
|
|
||||||
#[allow(clippy::useless_conversion)]
|
|
||||||
offset.try_into().expect("offset should fit in Py_ssize_t")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the offset of the weakref list from the start of the struct in bytes.
|
|
||||||
pub(crate) fn weaklist_offset() -> ffi::Py_ssize_t {
|
|
||||||
use memoffset::offset_of;
|
|
||||||
|
|
||||||
let offset = offset_of!(PyCell<T>, contents) + offset_of!(PyCellContents<T>, weakref);
|
|
||||||
|
|
||||||
// Py_ssize_t may not be equal to isize on all platforms
|
|
||||||
#[allow(clippy::useless_conversion)]
|
|
||||||
offset.try_into().expect("offset should fit in Py_ssize_t")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
pub(crate) fn release_ref(&self) {
|
|
||||||
self.borrow_checker().release_borrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
pub(crate) fn release_mut(&self) {
|
|
||||||
self.borrow_checker().release_borrow_mut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PyClassImpl> PyCell<T> {
|
|
||||||
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
|
|
||||||
T::PyClassMutability::borrow_checker(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,7 +605,7 @@ where
|
||||||
U: PyClass,
|
U: PyClass,
|
||||||
{
|
{
|
||||||
fn as_ref(&self) -> &T::BaseType {
|
fn as_ref(&self) -> &T::BaseType {
|
||||||
unsafe { &*self.inner.get_cell().ob_base.get_ptr() }
|
unsafe { &*self.inner.get_class_object().ob_base.get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,7 +639,7 @@ impl<'py, T: PyClass> PyRef<'py, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
|
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
|
||||||
let cell = obj.get_cell();
|
let cell = obj.get_class_object();
|
||||||
cell.ensure_threadsafe();
|
cell.ensure_threadsafe();
|
||||||
cell.borrow_checker()
|
cell.borrow_checker()
|
||||||
.try_borrow()
|
.try_borrow()
|
||||||
|
@ -717,7 +647,7 @@ impl<'py, T: PyClass> PyRef<'py, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_borrow_threadsafe(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
|
pub(crate) fn try_borrow_threadsafe(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
|
||||||
let cell = obj.get_cell();
|
let cell = obj.get_class_object();
|
||||||
cell.check_threadsafe()?;
|
cell.check_threadsafe()?;
|
||||||
cell.borrow_checker()
|
cell.borrow_checker()
|
||||||
.try_borrow()
|
.try_borrow()
|
||||||
|
@ -793,13 +723,16 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self) -> &T {
|
fn deref(&self) -> &T {
|
||||||
unsafe { &*self.inner.get_cell().get_ptr() }
|
unsafe { &*self.inner.get_class_object().get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, T: PyClass> Drop for PyRef<'p, T> {
|
impl<'p, T: PyClass> Drop for PyRef<'p, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.inner.get_cell().borrow_checker().release_borrow()
|
self.inner
|
||||||
|
.get_class_object()
|
||||||
|
.borrow_checker()
|
||||||
|
.release_borrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,7 +789,7 @@ where
|
||||||
U: PyClass<Frozen = False>,
|
U: PyClass<Frozen = False>,
|
||||||
{
|
{
|
||||||
fn as_ref(&self) -> &T::BaseType {
|
fn as_ref(&self) -> &T::BaseType {
|
||||||
unsafe { &*self.inner.get_cell().ob_base.get_ptr() }
|
unsafe { &*self.inner.get_class_object().ob_base.get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,7 +799,7 @@ where
|
||||||
U: PyClass<Frozen = False>,
|
U: PyClass<Frozen = False>,
|
||||||
{
|
{
|
||||||
fn as_mut(&mut self) -> &mut T::BaseType {
|
fn as_mut(&mut self) -> &mut T::BaseType {
|
||||||
unsafe { &mut *self.inner.get_cell().ob_base.get_ptr() }
|
unsafe { &mut *self.inner.get_class_object().ob_base.get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -900,7 +833,7 @@ impl<'py, T: PyClass<Frozen = False>> PyRefMut<'py, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowMutError> {
|
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowMutError> {
|
||||||
let cell = obj.get_cell();
|
let cell = obj.get_class_object();
|
||||||
cell.ensure_threadsafe();
|
cell.ensure_threadsafe();
|
||||||
cell.borrow_checker()
|
cell.borrow_checker()
|
||||||
.try_borrow_mut()
|
.try_borrow_mut()
|
||||||
|
@ -934,20 +867,23 @@ impl<'p, T: PyClass<Frozen = False>> Deref for PyRefMut<'p, T> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self) -> &T {
|
fn deref(&self) -> &T {
|
||||||
unsafe { &*self.inner.get_cell().get_ptr() }
|
unsafe { &*self.inner.get_class_object().get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, T: PyClass<Frozen = False>> DerefMut for PyRefMut<'p, T> {
|
impl<'p, T: PyClass<Frozen = False>> DerefMut for PyRefMut<'p, T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref_mut(&mut self) -> &mut T {
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
unsafe { &mut *self.inner.get_cell().get_ptr() }
|
unsafe { &mut *self.inner.get_class_object().get_ptr() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, T: PyClass<Frozen = False>> Drop for PyRefMut<'p, T> {
|
impl<'p, T: PyClass<Frozen = False>> Drop for PyRefMut<'p, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.inner.get_cell().borrow_checker().release_borrow_mut()
|
self.inner
|
||||||
|
.get_class_object()
|
||||||
|
.borrow_checker()
|
||||||
|
.release_borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,81 +970,6 @@ impl From<PyBorrowMutError> for PyErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait PyCellLayout<T>: PyLayout<T> {
|
|
||||||
fn ensure_threadsafe(&self);
|
|
||||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError>;
|
|
||||||
/// Implementation of tp_dealloc.
|
|
||||||
/// # Safety
|
|
||||||
/// - slf must be a valid pointer to an instance of a T or a subclass.
|
|
||||||
/// - slf must not be used after this call (as it will be freed).
|
|
||||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> PyCellLayout<T> for PyCellBase<U>
|
|
||||||
where
|
|
||||||
U: PySizedLayout<T>,
|
|
||||||
T: PyTypeInfo,
|
|
||||||
{
|
|
||||||
fn ensure_threadsafe(&self) {}
|
|
||||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
|
||||||
let type_obj = T::type_object_raw(py);
|
|
||||||
// For `#[pyclass]` types which inherit from PyAny, we can just call tp_free
|
|
||||||
if type_obj == std::ptr::addr_of_mut!(ffi::PyBaseObject_Type) {
|
|
||||||
return get_tp_free(ffi::Py_TYPE(slf))(slf as _);
|
|
||||||
}
|
|
||||||
|
|
||||||
// More complex native types (e.g. `extends=PyDict`) require calling the base's dealloc.
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
|
||||||
{
|
|
||||||
if let Some(dealloc) = (*type_obj).tp_dealloc {
|
|
||||||
// Before CPython 3.11 BaseException_dealloc would use Py_GC_UNTRACK which
|
|
||||||
// assumes the exception is currently GC tracked, so we have to re-track
|
|
||||||
// before calling the dealloc so that it can safely call Py_GC_UNTRACK.
|
|
||||||
#[cfg(not(any(Py_3_11, PyPy)))]
|
|
||||||
if ffi::PyType_FastSubclass(type_obj, ffi::Py_TPFLAGS_BASE_EXC_SUBCLASS) == 1 {
|
|
||||||
ffi::PyObject_GC_Track(slf.cast());
|
|
||||||
}
|
|
||||||
dealloc(slf as _);
|
|
||||||
} else {
|
|
||||||
get_tp_free(ffi::Py_TYPE(slf))(slf as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(Py_LIMITED_API)]
|
|
||||||
unreachable!("subclassing native types is not possible with the `abi3` feature");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PyClassImpl> PyCellLayout<T> for PyCell<T>
|
|
||||||
where
|
|
||||||
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyCellLayout<T::BaseType>,
|
|
||||||
{
|
|
||||||
fn ensure_threadsafe(&self) {
|
|
||||||
self.contents.thread_checker.ensure();
|
|
||||||
self.ob_base.ensure_threadsafe();
|
|
||||||
}
|
|
||||||
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
|
||||||
if !self.contents.thread_checker.check() {
|
|
||||||
return Err(PyBorrowError { _private: () });
|
|
||||||
}
|
|
||||||
self.ob_base.check_threadsafe()
|
|
||||||
}
|
|
||||||
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
|
||||||
// Safety: Python only calls tp_dealloc when no references to the object remain.
|
|
||||||
let cell = &mut *(slf as *mut PyCell<T>);
|
|
||||||
if cell.contents.thread_checker.can_drop(py) {
|
|
||||||
ManuallyDrop::drop(&mut cell.contents.value);
|
|
||||||
}
|
|
||||||
cell.contents.dict.clear_dict(py);
|
|
||||||
cell.contents.weakref.clear_weakrefs(slf, py);
|
|
||||||
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
//! Crate-private implementation of pycell
|
//! Crate-private implementation of PyClassObject
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, UnsafeCell};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
use crate::impl_::pyclass::{PyClassBaseType, PyClassImpl};
|
use crate::impl_::pyclass::{
|
||||||
use crate::PyCell;
|
PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef,
|
||||||
|
};
|
||||||
|
use crate::type_object::{get_tp_free, PyLayout, PySizedLayout};
|
||||||
|
use crate::{ffi, PyClass, PyTypeInfo, Python};
|
||||||
|
|
||||||
use super::{PyBorrowError, PyBorrowMutError};
|
use super::{PyBorrowError, PyBorrowMutError};
|
||||||
|
|
||||||
|
@ -156,29 +160,170 @@ impl PyClassBorrowChecker for BorrowChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GetBorrowChecker<T: PyClassImpl> {
|
pub trait GetBorrowChecker<T: PyClassImpl> {
|
||||||
fn borrow_checker(cell: &PyCell<T>) -> &<T::PyClassMutability as PyClassMutability>::Checker;
|
fn borrow_checker(
|
||||||
|
class_object: &PyClassObject<T>,
|
||||||
|
) -> &<T::PyClassMutability as PyClassMutability>::Checker;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for MutableClass {
|
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for MutableClass {
|
||||||
fn borrow_checker(cell: &PyCell<T>) -> &BorrowChecker {
|
fn borrow_checker(class_object: &PyClassObject<T>) -> &BorrowChecker {
|
||||||
&cell.contents.borrow_checker
|
&class_object.contents.borrow_checker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for ImmutableClass {
|
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for ImmutableClass {
|
||||||
fn borrow_checker(cell: &PyCell<T>) -> &EmptySlot {
|
fn borrow_checker(class_object: &PyClassObject<T>) -> &EmptySlot {
|
||||||
&cell.contents.borrow_checker
|
&class_object.contents.borrow_checker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PyClassImpl<PyClassMutability = Self>, M: PyClassMutability> GetBorrowChecker<T>
|
impl<T: PyClassImpl<PyClassMutability = Self>, M: PyClassMutability> GetBorrowChecker<T>
|
||||||
for ExtendsMutableAncestor<M>
|
for ExtendsMutableAncestor<M>
|
||||||
where
|
where
|
||||||
T::BaseType: PyClassImpl + PyClassBaseType<LayoutAsBase = PyCell<T::BaseType>>,
|
T::BaseType: PyClassImpl + PyClassBaseType<LayoutAsBase = PyClassObject<T::BaseType>>,
|
||||||
<T::BaseType as PyClassImpl>::PyClassMutability: PyClassMutability<Checker = BorrowChecker>,
|
<T::BaseType as PyClassImpl>::PyClassMutability: PyClassMutability<Checker = BorrowChecker>,
|
||||||
{
|
{
|
||||||
fn borrow_checker(cell: &PyCell<T>) -> &BorrowChecker {
|
fn borrow_checker(class_object: &PyClassObject<T>) -> &BorrowChecker {
|
||||||
<<T::BaseType as PyClassImpl>::PyClassMutability as GetBorrowChecker<T::BaseType>>::borrow_checker(&cell.ob_base)
|
<<T::BaseType as PyClassImpl>::PyClassMutability as GetBorrowChecker<T::BaseType>>::borrow_checker(&class_object.ob_base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base layout of PyClassObject.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PyClassObjectBase<T> {
|
||||||
|
ob_base: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, U> PyLayout<T> for PyClassObjectBase<U> where U: PySizedLayout<T> {}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait PyClassObjectLayout<T>: PyLayout<T> {
|
||||||
|
fn ensure_threadsafe(&self);
|
||||||
|
fn check_threadsafe(&self) -> Result<(), PyBorrowError>;
|
||||||
|
/// Implementation of tp_dealloc.
|
||||||
|
/// # Safety
|
||||||
|
/// - slf must be a valid pointer to an instance of a T or a subclass.
|
||||||
|
/// - slf must not be used after this call (as it will be freed).
|
||||||
|
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> PyClassObjectLayout<T> for PyClassObjectBase<U>
|
||||||
|
where
|
||||||
|
U: PySizedLayout<T>,
|
||||||
|
T: PyTypeInfo,
|
||||||
|
{
|
||||||
|
fn ensure_threadsafe(&self) {}
|
||||||
|
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||||
|
let type_obj = T::type_object_raw(py);
|
||||||
|
// For `#[pyclass]` types which inherit from PyAny, we can just call tp_free
|
||||||
|
if type_obj == std::ptr::addr_of_mut!(ffi::PyBaseObject_Type) {
|
||||||
|
return get_tp_free(ffi::Py_TYPE(slf))(slf.cast());
|
||||||
|
}
|
||||||
|
|
||||||
|
// More complex native types (e.g. `extends=PyDict`) require calling the base's dealloc.
|
||||||
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
{
|
||||||
|
if let Some(dealloc) = (*type_obj).tp_dealloc {
|
||||||
|
// Before CPython 3.11 BaseException_dealloc would use Py_GC_UNTRACK which
|
||||||
|
// assumes the exception is currently GC tracked, so we have to re-track
|
||||||
|
// before calling the dealloc so that it can safely call Py_GC_UNTRACK.
|
||||||
|
#[cfg(not(any(Py_3_11, PyPy)))]
|
||||||
|
if ffi::PyType_FastSubclass(type_obj, ffi::Py_TPFLAGS_BASE_EXC_SUBCLASS) == 1 {
|
||||||
|
ffi::PyObject_GC_Track(slf.cast());
|
||||||
|
}
|
||||||
|
dealloc(slf);
|
||||||
|
} else {
|
||||||
|
get_tp_free(ffi::Py_TYPE(slf))(slf.cast());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(Py_LIMITED_API)]
|
||||||
|
unreachable!("subclassing native types is not possible with the `abi3` feature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The layout of a PyClass as a Python object
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PyClassObject<T: PyClassImpl> {
|
||||||
|
pub(crate) ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
||||||
|
contents: PyClassObjectContents<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct PyClassObjectContents<T: PyClassImpl> {
|
||||||
|
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
|
||||||
|
pub(crate) borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage,
|
||||||
|
pub(crate) thread_checker: T::ThreadChecker,
|
||||||
|
pub(crate) dict: T::Dict,
|
||||||
|
pub(crate) weakref: T::WeakRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PyClassImpl> PyClassObject<T> {
|
||||||
|
pub(crate) fn get_ptr(&self) -> *mut T {
|
||||||
|
self.contents.value.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the offset of the dictionary from the start of the struct in bytes.
|
||||||
|
pub(crate) fn dict_offset() -> ffi::Py_ssize_t {
|
||||||
|
use memoffset::offset_of;
|
||||||
|
|
||||||
|
let offset =
|
||||||
|
offset_of!(PyClassObject<T>, contents) + offset_of!(PyClassObjectContents<T>, dict);
|
||||||
|
|
||||||
|
// Py_ssize_t may not be equal to isize on all platforms
|
||||||
|
#[allow(clippy::useless_conversion)]
|
||||||
|
offset.try_into().expect("offset should fit in Py_ssize_t")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the offset of the weakref list from the start of the struct in bytes.
|
||||||
|
pub(crate) fn weaklist_offset() -> ffi::Py_ssize_t {
|
||||||
|
use memoffset::offset_of;
|
||||||
|
|
||||||
|
let offset =
|
||||||
|
offset_of!(PyClassObject<T>, contents) + offset_of!(PyClassObjectContents<T>, weakref);
|
||||||
|
|
||||||
|
// Py_ssize_t may not be equal to isize on all platforms
|
||||||
|
#[allow(clippy::useless_conversion)]
|
||||||
|
offset.try_into().expect("offset should fit in Py_ssize_t")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PyClassImpl> PyClassObject<T> {
|
||||||
|
pub(crate) fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
|
||||||
|
T::PyClassMutability::borrow_checker(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: PyClassImpl> PyLayout<T> for PyClassObject<T> {}
|
||||||
|
impl<T: PyClass> PySizedLayout<T> for PyClassObject<T> {}
|
||||||
|
|
||||||
|
impl<T: PyClassImpl> PyClassObjectLayout<T> for PyClassObject<T>
|
||||||
|
where
|
||||||
|
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectLayout<T::BaseType>,
|
||||||
|
{
|
||||||
|
fn ensure_threadsafe(&self) {
|
||||||
|
self.contents.thread_checker.ensure();
|
||||||
|
self.ob_base.ensure_threadsafe();
|
||||||
|
}
|
||||||
|
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
|
||||||
|
if !self.contents.thread_checker.check() {
|
||||||
|
return Err(PyBorrowError { _private: () });
|
||||||
|
}
|
||||||
|
self.ob_base.check_threadsafe()
|
||||||
|
}
|
||||||
|
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
|
||||||
|
// Safety: Python only calls tp_dealloc when no references to the object remain.
|
||||||
|
let class_object = &mut *(slf.cast::<PyClassObject<T>>());
|
||||||
|
if class_object.contents.thread_checker.can_drop(py) {
|
||||||
|
ManuallyDrop::drop(&mut class_object.contents.value);
|
||||||
|
}
|
||||||
|
class_object.contents.dict.clear_dict(py);
|
||||||
|
class_object.contents.weakref.clear_weakrefs(slf, py);
|
||||||
|
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +334,6 @@ mod tests {
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::pyclass::boolean_struct::{False, True};
|
use crate::pyclass::boolean_struct::{False, True};
|
||||||
use crate::PyClass;
|
|
||||||
|
|
||||||
#[pyclass(crate = "crate", subclass)]
|
#[pyclass(crate = "crate", subclass)]
|
||||||
struct MutableBase;
|
struct MutableBase;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! `PyClass` and related traits.
|
//! `PyClass` and related traits.
|
||||||
use crate::{
|
use crate::{
|
||||||
callback::IntoPyCallbackOutput, ffi, impl_::pyclass::PyClassImpl, Bound, IntoPy, PyCell,
|
callback::IntoPyCallbackOutput, ffi, impl_::pyclass::PyClassImpl, IntoPy, PyCell, PyObject,
|
||||||
PyObject, PyResult, PyTypeInfo, Python,
|
PyResult, PyTypeInfo, Python,
|
||||||
};
|
};
|
||||||
use std::{cmp::Ordering, os::raw::c_int};
|
use std::{cmp::Ordering, os::raw::c_int};
|
||||||
|
|
||||||
|
@ -216,18 +216,6 @@ pub trait Frozen: boolean_struct::private::Boolean {}
|
||||||
impl Frozen for boolean_struct::True {}
|
impl Frozen for boolean_struct::True {}
|
||||||
impl Frozen for boolean_struct::False {}
|
impl Frozen for boolean_struct::False {}
|
||||||
|
|
||||||
impl<'py, T: PyClass> Bound<'py, T> {
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
pub(crate) fn release_ref(&self) {
|
|
||||||
self.get_cell().release_ref();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
|
||||||
pub(crate) fn release_mut(&self) {
|
|
||||||
self.get_cell().release_mut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compare_op_matches() {
|
fn test_compare_op_matches() {
|
||||||
|
|
|
@ -3,6 +3,7 @@ use pyo3_ffi::PyType_IS_GC;
|
||||||
use crate::{
|
use crate::{
|
||||||
exceptions::PyTypeError,
|
exceptions::PyTypeError,
|
||||||
ffi,
|
ffi,
|
||||||
|
impl_::pycell::PyClassObject,
|
||||||
impl_::pyclass::{
|
impl_::pyclass::{
|
||||||
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
|
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
|
||||||
tp_dealloc_with_gc, PyClassItemsIter,
|
tp_dealloc_with_gc, PyClassItemsIter,
|
||||||
|
@ -13,7 +14,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
types::typeobject::PyTypeMethods,
|
types::typeobject::PyTypeMethods,
|
||||||
types::PyType,
|
types::PyType,
|
||||||
Py, PyCell, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python,
|
Py, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -94,7 +95,7 @@ where
|
||||||
T::items_iter(),
|
T::items_iter(),
|
||||||
T::NAME,
|
T::NAME,
|
||||||
T::MODULE,
|
T::MODULE,
|
||||||
std::mem::size_of::<PyCell<T>>(),
|
std::mem::size_of::<PyClassObject<T>>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
//! Contains initialization utilities for `#[pyclass]`.
|
//! Contains initialization utilities for `#[pyclass]`.
|
||||||
use crate::callback::IntoPyCallbackOutput;
|
use crate::callback::IntoPyCallbackOutput;
|
||||||
|
use crate::ffi_ptr_ext::FfiPtrExt;
|
||||||
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
|
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
|
||||||
use crate::{ffi, Py, PyCell, PyClass, PyErr, PyResult, Python};
|
use crate::types::PyAnyMethods;
|
||||||
|
use crate::{ffi, Bound, Py, PyClass, PyErr, PyResult, Python};
|
||||||
use crate::{
|
use crate::{
|
||||||
ffi::PyTypeObject,
|
ffi::PyTypeObject,
|
||||||
pycell::{
|
pycell::impl_::{PyClassBorrowChecker, PyClassMutability, PyClassObjectContents},
|
||||||
impl_::{PyClassBorrowChecker, PyClassMutability},
|
|
||||||
PyCellContents,
|
|
||||||
},
|
|
||||||
type_object::{get_tp_alloc, PyTypeInfo},
|
type_object::{get_tp_alloc, PyTypeInfo},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -207,29 +206,55 @@ impl<T: PyClass> PyClassInitializer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new PyCell and initializes it.
|
/// Creates a new PyCell and initializes it.
|
||||||
#[doc(hidden)]
|
pub(crate) fn create_class_object(self, py: Python<'_>) -> PyResult<Bound<'_, T>>
|
||||||
pub fn create_cell(self, py: Python<'_>) -> PyResult<*mut PyCell<T>>
|
|
||||||
where
|
where
|
||||||
T: PyClass,
|
T: PyClass,
|
||||||
{
|
{
|
||||||
unsafe { self.create_cell_from_subtype(py, T::type_object_raw(py)) }
|
unsafe { self.create_class_object_of_type(py, T::type_object_raw(py)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new PyCell and initializes it given a typeobject `subtype`.
|
/// Creates a new class object and initializes it given a typeobject `subtype`.
|
||||||
/// Called by the Python `tp_new` implementation generated by a `#[new]` function in a `#[pymethods]` block.
|
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `subtype` must be a valid pointer to the type object of T or a subclass.
|
/// `subtype` must be a valid pointer to the type object of T or a subclass.
|
||||||
#[doc(hidden)]
|
pub(crate) unsafe fn create_class_object_of_type(
|
||||||
pub unsafe fn create_cell_from_subtype(
|
|
||||||
self,
|
self,
|
||||||
py: Python<'_>,
|
py: Python<'_>,
|
||||||
subtype: *mut crate::ffi::PyTypeObject,
|
target_type: *mut crate::ffi::PyTypeObject,
|
||||||
) -> PyResult<*mut PyCell<T>>
|
) -> PyResult<Bound<'_, T>>
|
||||||
where
|
where
|
||||||
T: PyClass,
|
T: PyClass,
|
||||||
{
|
{
|
||||||
self.into_new_object(py, subtype).map(|obj| obj as _)
|
/// Layout of a PyClassObject after base new has been called, but the contents have not yet been
|
||||||
|
/// written.
|
||||||
|
#[repr(C)]
|
||||||
|
struct PartiallyInitializedClassObject<T: PyClass> {
|
||||||
|
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
||||||
|
contents: MaybeUninit<PyClassObjectContents<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let (init, super_init) = match self.0 {
|
||||||
|
PyClassInitializerImpl::Existing(value) => return Ok(value.into_bound(py)),
|
||||||
|
PyClassInitializerImpl::New { init, super_init } => (init, super_init),
|
||||||
|
};
|
||||||
|
|
||||||
|
let obj = super_init.into_new_object(py, target_type)?;
|
||||||
|
|
||||||
|
let part_init: *mut PartiallyInitializedClassObject<T> = obj.cast();
|
||||||
|
std::ptr::write(
|
||||||
|
(*part_init).contents.as_mut_ptr(),
|
||||||
|
PyClassObjectContents {
|
||||||
|
value: ManuallyDrop::new(UnsafeCell::new(init)),
|
||||||
|
borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
|
||||||
|
thread_checker: T::ThreadChecker::new(),
|
||||||
|
dict: T::Dict::INIT,
|
||||||
|
weakref: T::WeakRef::INIT,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Safety: obj is a valid pointer to an object of type `target_type`, which` is a known
|
||||||
|
// subclass of `T`
|
||||||
|
Ok(obj.assume_owned(py).downcast_into_unchecked())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,33 +264,8 @@ impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
|
||||||
py: Python<'_>,
|
py: Python<'_>,
|
||||||
subtype: *mut PyTypeObject,
|
subtype: *mut PyTypeObject,
|
||||||
) -> PyResult<*mut ffi::PyObject> {
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
/// Layout of a PyCell after base new has been called, but the contents have not yet been
|
self.create_class_object_of_type(py, subtype)
|
||||||
/// written.
|
.map(Bound::into_ptr)
|
||||||
#[repr(C)]
|
|
||||||
struct PartiallyInitializedPyCell<T: PyClass> {
|
|
||||||
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
|
|
||||||
contents: MaybeUninit<PyCellContents<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let (init, super_init) = match self.0 {
|
|
||||||
PyClassInitializerImpl::Existing(value) => return Ok(value.into_ptr()),
|
|
||||||
PyClassInitializerImpl::New { init, super_init } => (init, super_init),
|
|
||||||
};
|
|
||||||
|
|
||||||
let obj = super_init.into_new_object(py, subtype)?;
|
|
||||||
|
|
||||||
let cell: *mut PartiallyInitializedPyCell<T> = obj as _;
|
|
||||||
std::ptr::write(
|
|
||||||
(*cell).contents.as_mut_ptr(),
|
|
||||||
PyCellContents {
|
|
||||||
value: ManuallyDrop::new(UnsafeCell::new(init)),
|
|
||||||
borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
|
|
||||||
thread_checker: T::ThreadChecker::new(),
|
|
||||||
dict: T::Dict::INIT,
|
|
||||||
weakref: T::WeakRef::INIT,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private_impl! {}
|
private_impl! {}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::types::{PyAny, PyType};
|
||||||
use crate::{ffi, Bound, PyNativeType, Python};
|
use crate::{ffi, Bound, PyNativeType, Python};
|
||||||
|
|
||||||
/// `T: PyLayout<U>` represents that `T` is a concrete representation of `U` in the Python heap.
|
/// `T: PyLayout<U>` represents that `T` is a concrete representation of `U` in the Python heap.
|
||||||
/// E.g., `PyCell` is a concrete representation of all `pyclass`es, and `ffi::PyObject`
|
/// E.g., `PyClassObject` is a concrete representation of all `pyclass`es, and `ffi::PyObject`
|
||||||
/// is of `PyAny`.
|
/// is of `PyAny`.
|
||||||
///
|
///
|
||||||
/// This trait is intended to be used internally.
|
/// This trait is intended to be used internally.
|
||||||
|
|
|
@ -290,7 +290,7 @@ macro_rules! pyobject_native_type_sized {
|
||||||
unsafe impl $crate::type_object::PyLayout<$name> for $layout {}
|
unsafe impl $crate::type_object::PyLayout<$name> for $layout {}
|
||||||
impl $crate::type_object::PySizedLayout<$name> for $layout {}
|
impl $crate::type_object::PySizedLayout<$name> for $layout {}
|
||||||
impl<$($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name {
|
impl<$($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name {
|
||||||
type LayoutAsBase = $crate::pycell::PyCellBase<$layout>;
|
type LayoutAsBase = $crate::impl_::pycell::PyClassObjectBase<$layout>;
|
||||||
type BaseNativeType = $name;
|
type BaseNativeType = $name;
|
||||||
type Initializer = $crate::pyclass_init::PyNativeTypeInitializer<Self>;
|
type Initializer = $crate::pyclass_init::PyNativeTypeInitializer<Self>;
|
||||||
type PyClassMutability = $crate::pycell::impl_::ImmutableClass;
|
type PyClassMutability = $crate::pycell::impl_::ImmutableClass;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use std::{task::Poll, thread, time::Duration};
|
use std::{task::Poll, thread, time::Duration};
|
||||||
|
|
||||||
use futures::{channel::oneshot, future::poll_fn, FutureExt};
|
use futures::{channel::oneshot, future::poll_fn, FutureExt};
|
||||||
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
coroutine::CancelHandle,
|
coroutine::CancelHandle,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
@ -259,6 +260,15 @@ fn test_async_method_receiver() {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IS_DROPPED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
impl Drop for Counter {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
IS_DROPPED.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Python::with_gil(|gil| {
|
Python::with_gil(|gil| {
|
||||||
let test = r#"
|
let test = r#"
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -291,5 +301,7 @@ fn test_async_method_receiver() {
|
||||||
"#;
|
"#;
|
||||||
let locals = [("Counter", gil.get_type_bound::<Counter>())].into_py_dict_bound(gil);
|
let locals = [("Counter", gil.get_type_bound::<Counter>())].into_py_dict_bound(gil);
|
||||||
py_run!(gil, *locals, test);
|
py_run!(gil, *locals, test);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
assert!(IS_DROPPED.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue