Skip to content

Commit

Permalink
Merge pull request #2285 from PyO3/spin-loop-hint
Browse files Browse the repository at this point in the history
Replace unhinted spin loops by sleeping events.
  • Loading branch information
davidhewitt authored Apr 8, 2022
2 parents 2092218 + 6a4ebbf commit 749fe6c
Showing 1 changed file with 43 additions and 18 deletions.
61 changes: 43 additions & 18 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ impl EnsureGIL {
mod tests {
use super::{gil_is_acquired, GILPool, GIL_COUNT, OWNED_OBJECTS, POOL};
use crate::{ffi, gil, AsPyPointer, IntoPyPointer, PyObject, Python, ToPyObject};
use parking_lot::{const_mutex, Condvar, Mutex};
use std::ptr::NonNull;

fn get_object(py: Python<'_>) -> PyObject {
Expand Down Expand Up @@ -699,16 +700,41 @@ mod tests {
assert_eq!(count + 1, c.get_refcnt(py));
}

struct Event {
set: Mutex<bool>,
wait: Condvar,
}

impl Event {
const fn new() -> Self {
Self {
set: const_mutex(false),
wait: Condvar::new(),
}
}

fn set(&self) {
*self.set.lock() = true;
self.wait.notify_all();
}

fn wait(&self) {
let mut set = self.set.lock();
while !*set {
self.wait.wait(&mut set);
}
}
}

#[test]
fn test_clone_without_gil() {
use crate::{Py, PyAny};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{sync::Arc, thread};

// Some spinlocks for synchronizing
static GIL_ACQUIRED: AtomicBool = AtomicBool::new(false);
static OBJECT_CLONED: AtomicBool = AtomicBool::new(false);
static REFCNT_CHECKED: AtomicBool = AtomicBool::new(false);
// Some events for synchronizing
static GIL_ACQUIRED: Event = Event::new();
static OBJECT_CLONED: Event = Event::new();
static REFCNT_CHECKED: Event = Event::new();

Python::with_gil(|py| {
let obj: Arc<Py<PyAny>> = Arc::new(get_object(py));
Expand All @@ -723,31 +749,31 @@ mod tests {
let handle = thread::spawn(move || {
Python::with_gil(move |py| {
println!("3. The GIL has been acquired on another thread.");
GIL_ACQUIRED.store(true, Ordering::Release);
GIL_ACQUIRED.set();

// Spin a bit while the main thread registers obj in POOL
while !OBJECT_CLONED.load(Ordering::Acquire) {}
// Wait while the main thread registers obj in POOL
OBJECT_CLONED.wait();
println!("5. Checking refcnt");
assert_eq!(thread_obj.get_refcnt(py), count);

REFCNT_CHECKED.store(true, Ordering::Release);
REFCNT_CHECKED.set();
})
});

let cloned = py.allow_threads(|| {
println!("2. The GIL has been released.");

// spin until the gil has been acquired on the thread.
while !GIL_ACQUIRED.load(Ordering::Acquire) {}
// Wait until the GIL has been acquired on the thread.
GIL_ACQUIRED.wait();

println!("4. The other thread is now hogging the GIL, we clone without it held");
// Cloning without GIL should not update reference count
let cloned = Py::clone(&*obj);
OBJECT_CLONED.store(true, Ordering::Release);
OBJECT_CLONED.set();
cloned
});

while !REFCNT_CHECKED.load(Ordering::Acquire) {}
REFCNT_CHECKED.wait();

// Returning from allow_threads doesn't clear the pool
py.allow_threads(|| {
Expand All @@ -773,11 +799,10 @@ mod tests {
#[test]
fn test_clone_in_other_thread() {
use crate::Py;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{sync::Arc, thread};

// Some spinlocks for synchronizing
static OBJECT_CLONED: AtomicBool = AtomicBool::new(false);
// Some events for synchronizing
static OBJECT_CLONED: Event = Event::new();

let (obj, count, ptr) = Python::with_gil(|py| {
let obj = Arc::new(get_object(py));
Expand All @@ -789,10 +814,10 @@ mod tests {
// Cloning without GIL should not update reference count
#[allow(clippy::redundant_clone)]
let _ = Py::clone(&*thread_obj);
OBJECT_CLONED.store(true, Ordering::Release);
OBJECT_CLONED.set();
});

while !OBJECT_CLONED.load(Ordering::Acquire) {}
OBJECT_CLONED.wait();
assert_eq!(count, obj.get_refcnt(py));

t.join().unwrap();
Expand Down

0 comments on commit 749fe6c

Please sign in to comment.