use crate::ffi;
use crate::internal_tricks::Unsendable;
use crate::types::PyAny;
use crate::Python;
use spin;
use std::ptr::NonNull;
use std::{any, sync};
static START: sync::Once = sync::Once::new();
static START_PYO3: sync::Once = sync::Once::new();
pub fn prepare_freethreaded_python() {
START.call_once(|| unsafe {
if ffi::Py_IsInitialized() != 0 {
#[cfg(not(Py_3_7))]
assert_ne!(ffi::PyEval_ThreadsInitialized(), 0);
} else {
#[cfg(not(Py_3_7))]
assert_eq!(ffi::PyEval_ThreadsInitialized(), 0);
#[cfg(not(PyPy))]
ffi::Py_InitializeEx(0);
#[cfg(not(Py_3_7))]
ffi::PyEval_InitThreads();
#[cfg(not(PyPy))]
let _thread_state = ffi::PyEval_SaveThread();
}
init_once();
});
}
#[doc(hidden)]
pub fn init_once() {
START_PYO3.call_once(|| unsafe {
POOL = Box::into_raw(Box::new(ReleasePool::new()));
});
}
#[must_use]
pub struct GILGuard {
owned: usize,
borrowed: usize,
gstate: ffi::PyGILState_STATE,
no_send: Unsendable,
}
impl Drop for GILGuard {
fn drop(&mut self) {
unsafe {
let pool: &'static mut ReleasePool = &mut *POOL;
pool.drain(self.python(), self.owned, self.borrowed, true);
ffi::PyGILState_Release(self.gstate);
}
}
}
struct ReleasePool {
owned: ArrayList<NonNull<ffi::PyObject>>,
borrowed: ArrayList<NonNull<ffi::PyObject>>,
pointers: *mut Vec<NonNull<ffi::PyObject>>,
obj: Vec<Box<dyn any::Any>>,
p: spin::Mutex<*mut Vec<NonNull<ffi::PyObject>>>,
}
impl ReleasePool {
fn new() -> ReleasePool {
ReleasePool {
owned: ArrayList::new(),
borrowed: ArrayList::new(),
pointers: Box::into_raw(Box::new(Vec::with_capacity(256))),
obj: Vec::with_capacity(8),
p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(256)))),
}
}
unsafe fn release_pointers(&mut self) {
let mut v = self.p.lock();
let vec = &mut **v;
if vec.is_empty() {
return;
}
std::mem::swap(&mut self.pointers, &mut *v);
drop(v);
for ptr in vec.iter_mut() {
ffi::Py_DECREF(ptr.as_ptr());
}
vec.set_len(0);
}
pub unsafe fn drain(&mut self, _py: Python, owned: usize, borrowed: usize, pointers: bool) {
while owned < self.owned.len() {
let last = self.owned.pop_back().unwrap();
ffi::Py_DECREF(last.as_ptr());
}
self.borrowed.truncate(borrowed);
if pointers {
self.release_pointers();
}
self.obj.clear();
}
}
static mut POOL: *mut ReleasePool = ::std::ptr::null_mut();
#[doc(hidden)]
pub struct GILPool<'p> {
py: Python<'p>,
owned: usize,
borrowed: usize,
pointers: bool,
no_send: Unsendable,
}
impl<'p> GILPool<'p> {
#[inline]
pub fn new(py: Python) -> GILPool {
let p: &'static mut ReleasePool = unsafe { &mut *POOL };
GILPool {
py,
owned: p.owned.len(),
borrowed: p.borrowed.len(),
pointers: true,
no_send: Unsendable::default(),
}
}
#[inline]
pub fn new_no_pointers(py: Python) -> GILPool {
let p: &'static mut ReleasePool = unsafe { &mut *POOL };
GILPool {
py,
owned: p.owned.len(),
borrowed: p.borrowed.len(),
pointers: false,
no_send: Unsendable::default(),
}
}
}
impl<'p> Drop for GILPool<'p> {
fn drop(&mut self) {
unsafe {
let pool: &'static mut ReleasePool = &mut *POOL;
pool.drain(self.py, self.owned, self.borrowed, self.pointers);
}
}
}
pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T {
let pool: &'static mut ReleasePool = &mut *POOL;
pool.obj.push(Box::new(obj));
pool.obj
.last()
.unwrap()
.as_ref()
.downcast_ref::<T>()
.unwrap()
}
pub unsafe fn register_pointer(obj: NonNull<ffi::PyObject>) {
let pool = &mut *POOL;
(**pool.p.lock()).push(obj);
}
pub unsafe fn register_owned(_py: Python, obj: NonNull<ffi::PyObject>) -> &PyAny {
let pool = &mut *POOL;
&*(pool.owned.push_back(obj) as *const _ as *const PyAny)
}
pub unsafe fn register_borrowed(_py: Python, obj: NonNull<ffi::PyObject>) -> &PyAny {
let pool = &mut *POOL;
&*(pool.borrowed.push_back(obj) as *const _ as *const PyAny)
}
impl GILGuard {
pub fn acquire() -> GILGuard {
prepare_freethreaded_python();
unsafe {
let gstate = ffi::PyGILState_Ensure();
let pool: &'static mut ReleasePool = &mut *POOL;
GILGuard {
owned: pool.owned.len(),
borrowed: pool.borrowed.len(),
gstate,
no_send: Unsendable::default(),
}
}
}
#[inline]
pub fn python(&self) -> Python {
unsafe { Python::assume_gil_acquired() }
}
}
use self::array_list::ArrayList;
mod array_list {
use std::collections::LinkedList;
const BLOCK_SIZE: usize = 256;
pub(super) struct ArrayList<T> {
inner: LinkedList<[Option<T>; BLOCK_SIZE]>,
length: usize,
}
impl<T: Copy> ArrayList<T> {
pub fn new() -> Self {
ArrayList {
inner: LinkedList::new(),
length: 0,
}
}
pub fn push_back(&mut self, item: T) -> &T {
let next_idx = self.next_idx();
if next_idx == 0 {
self.inner.push_back([None; BLOCK_SIZE]);
}
self.inner.back_mut().unwrap()[next_idx] = Some(item);
self.length += 1;
self.inner.back().unwrap()[next_idx].as_ref().unwrap()
}
pub fn pop_back(&mut self) -> Option<T> {
self.length -= 1;
let current_idx = self.next_idx();
if current_idx == 0 {
let last_list = self.inner.pop_back()?;
return last_list[0];
}
self.inner.back().and_then(|arr| arr[current_idx])
}
pub fn len(&self) -> usize {
self.length
}
pub fn truncate(&mut self, new_len: usize) {
if self.length <= new_len {
return;
}
while self.inner.len() > (new_len + BLOCK_SIZE - 1) / BLOCK_SIZE {
self.inner.pop_back();
}
self.length = new_len;
}
fn next_idx(&self) -> usize {
self.length % BLOCK_SIZE
}
}
}
#[cfg(test)]
mod test {
use super::{GILPool, NonNull, ReleasePool, POOL};
use crate::object::PyObject;
use crate::AsPyPointer;
use crate::Python;
use crate::ToPyObject;
use crate::{ffi, gil};
fn get_object() -> PyObject {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = py.eval("object()", None, None).unwrap();
obj.to_object(py)
}
#[test]
fn test_owned() {
gil::init_once();
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
let obj_ptr = obj.as_ptr();
let _ref = obj.clone_ref(py);
unsafe {
let p: &'static mut ReleasePool = &mut *POOL;
{
let gil = Python::acquire_gil();
let py = gil.python();
let _ = gil::register_owned(py, obj.into_nonnull());
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
assert_eq!(p.owned.len(), 1);
}
{
let _gil = Python::acquire_gil();
assert_eq!(p.owned.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
}
#[test]
fn test_owned_nested() {
gil::init_once();
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
let _ref = obj.clone_ref(py);
let obj_ptr = obj.as_ptr();
unsafe {
let p: &'static mut ReleasePool = &mut *POOL;
{
let _pool = GILPool::new(py);
assert_eq!(p.owned.len(), 0);
let _ = gil::register_owned(py, obj.into_nonnull());
assert_eq!(p.owned.len(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
{
let _pool = GILPool::new(py);
let obj = get_object();
let _ = gil::register_owned(py, obj.into_nonnull());
assert_eq!(p.owned.len(), 2);
}
assert_eq!(p.owned.len(), 1);
}
{
assert_eq!(p.owned.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
}
#[test]
fn test_borrowed() {
gil::init_once();
unsafe {
let p: &'static mut ReleasePool = &mut *POOL;
let obj = get_object();
let obj_ptr = obj.as_ptr();
{
let gil = Python::acquire_gil();
let py = gil.python();
assert_eq!(p.borrowed.len(), 0);
gil::register_borrowed(py, NonNull::new(obj_ptr).unwrap());
assert_eq!(p.borrowed.len(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
{
let _gil = Python::acquire_gil();
assert_eq!(p.borrowed.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
}
#[test]
fn test_borrowed_nested() {
gil::init_once();
unsafe {
let p: &'static mut ReleasePool = &mut *POOL;
let obj = get_object();
let obj_ptr = obj.as_ptr();
{
let gil = Python::acquire_gil();
let py = gil.python();
assert_eq!(p.borrowed.len(), 0);
gil::register_borrowed(py, NonNull::new(obj_ptr).unwrap());
assert_eq!(p.borrowed.len(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
{
let _pool = GILPool::new(py);
assert_eq!(p.borrowed.len(), 1);
gil::register_borrowed(py, NonNull::new(obj_ptr).unwrap());
assert_eq!(p.borrowed.len(), 2);
}
assert_eq!(p.borrowed.len(), 1);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
{
let _gil = Python::acquire_gil();
assert_eq!(p.borrowed.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
}
#[test]
fn test_pyobject_drop() {
gil::init_once();
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
let _ref = obj.clone_ref(py);
let obj_ptr = obj.as_ptr();
unsafe {
let p: &'static mut ReleasePool = &mut *POOL;
{
assert_eq!(p.owned.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
}
drop(obj);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
{
let _gil = Python::acquire_gil();
}
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}
}