1 | //! Interaction with Python's global interpreter lock |
2 | |
3 | use crate::impl_::not_send::{NotSend, NOT_SEND}; |
4 | use crate::{ffi, Python}; |
5 | use parking_lot::{const_mutex, Mutex, Once}; |
6 | use std::cell::Cell; |
7 | #[cfg (debug_assertions)] |
8 | use std::cell::RefCell; |
9 | #[cfg (not(debug_assertions))] |
10 | use std::cell::UnsafeCell; |
11 | use std::{mem, ptr::NonNull}; |
12 | |
13 | static START: Once = Once::new(); |
14 | |
15 | cfg_if::cfg_if! { |
16 | if #[cfg(thread_local_const_init)] { |
17 | use std::thread_local as thread_local_const_init; |
18 | } else { |
19 | macro_rules! thread_local_const_init { |
20 | ($($(#[$attr:meta])* static $name:ident: $ty:ty = const { $init:expr };)*) => ( |
21 | thread_local! { $($(#[$attr])* static $name: $ty = $init;)* } |
22 | ) |
23 | } |
24 | } |
25 | } |
26 | |
27 | thread_local_const_init! { |
28 | /// This is an internal counter in pyo3 monitoring whether this thread has the GIL. |
29 | /// |
30 | /// It will be incremented whenever a GILGuard or GILPool is created, and decremented whenever |
31 | /// they are dropped. |
32 | /// |
33 | /// As a result, if this thread has the GIL, GIL_COUNT is greater than zero. |
34 | /// |
35 | /// Additionally, we sometimes need to prevent safe access to the GIL, |
36 | /// e.g. when implementing `__traverse__`, which is represented by a negative value. |
37 | static GIL_COUNT: Cell<isize> = const { Cell::new(0) }; |
38 | |
39 | /// Temporarily hold objects that will be released when the GILPool drops. |
40 | #[cfg (debug_assertions)] |
41 | static OWNED_OBJECTS: RefCell<PyObjVec> = const { RefCell::new(Vec::new()) }; |
42 | #[cfg (not(debug_assertions))] |
43 | static OWNED_OBJECTS: UnsafeCell<PyObjVec> = const { UnsafeCell::new(Vec::new()) }; |
44 | } |
45 | |
46 | const GIL_LOCKED_DURING_TRAVERSE: isize = -1; |
47 | |
48 | /// Checks whether the GIL is acquired. |
49 | /// |
50 | /// Note: This uses pyo3's internal count rather than PyGILState_Check for two reasons: |
51 | /// 1) for performance |
52 | /// 2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called, |
53 | /// which could lead to incorrect conclusions that the GIL is held. |
54 | #[inline (always)] |
55 | fn gil_is_acquired() -> bool { |
56 | GIL_COUNT.try_with(|c| c.get() > 0).unwrap_or(default:false) |
57 | } |
58 | |
59 | /// Prepares the use of Python in a free-threaded context. |
60 | /// |
61 | /// If the Python interpreter is not already initialized, this function will initialize it with |
62 | /// signal handling disabled (Python will not raise the `KeyboardInterrupt` exception). Python |
63 | /// signal handling depends on the notion of a 'main thread', which must be the thread that |
64 | /// initializes the Python interpreter. |
65 | /// |
66 | /// If the Python interpreter is already initialized, this function has no effect. |
67 | /// |
68 | /// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other |
69 | /// software). Support for this is tracked on the |
70 | /// [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286). |
71 | /// |
72 | /// # Examples |
73 | /// ```rust |
74 | /// use pyo3::prelude::*; |
75 | /// |
76 | /// # fn main() -> PyResult<()> { |
77 | /// pyo3::prepare_freethreaded_python(); |
78 | /// Python::with_gil(|py| py.run("print('Hello World')" , None, None)) |
79 | /// # } |
80 | /// ``` |
81 | #[cfg (not(PyPy))] |
82 | pub fn prepare_freethreaded_python() { |
83 | // Protect against race conditions when Python is not yet initialized and multiple threads |
84 | // concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against |
85 | // concurrent initialization of the Python runtime by other users of the Python C API. |
86 | START.call_once_force(|_| unsafe { |
87 | // Use call_once_force because if initialization panics, it's okay to try again. |
88 | if ffi::Py_IsInitialized() == 0 { |
89 | ffi::Py_InitializeEx(arg1:0); |
90 | |
91 | // Release the GIL. |
92 | ffi::PyEval_SaveThread(); |
93 | } |
94 | }); |
95 | } |
96 | |
97 | /// Executes the provided closure with an embedded Python interpreter. |
98 | /// |
99 | /// This function initializes the Python interpreter, executes the provided closure, and then |
100 | /// finalizes the Python interpreter. |
101 | /// |
102 | /// After execution all Python resources are cleaned up, and no further Python APIs can be called. |
103 | /// Because many Python modules implemented in C do not support multiple Python interpreters in a |
104 | /// single process, it is not safe to call this function more than once. (Many such modules will not |
105 | /// initialize correctly on the second run.) |
106 | /// |
107 | /// # Panics |
108 | /// - If the Python interpreter is already initialized before calling this function. |
109 | /// |
110 | /// # Safety |
111 | /// - This function should only ever be called once per process (usually as part of the `main` |
112 | /// function). It is also not thread-safe. |
113 | /// - No Python APIs can be used after this function has finished executing. |
114 | /// - The return value of the closure must not contain any Python value, _including_ `PyResult`. |
115 | /// |
116 | /// # Examples |
117 | /// |
118 | /// ```rust |
119 | /// unsafe { |
120 | /// pyo3::with_embedded_python_interpreter(|py| { |
121 | /// if let Err(e) = py.run("print('Hello World')" , None, None) { |
122 | /// // We must make sure to not return a `PyErr`! |
123 | /// e.print(py); |
124 | /// } |
125 | /// }); |
126 | /// } |
127 | /// ``` |
128 | #[cfg (not(PyPy))] |
129 | pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R |
130 | where |
131 | F: for<'p> FnOnce(Python<'p>) -> R, |
132 | { |
133 | assert_eq!( |
134 | ffi::Py_IsInitialized(), |
135 | 0, |
136 | "called `with_embedded_python_interpreter` but a Python interpreter is already running." |
137 | ); |
138 | |
139 | ffi::Py_InitializeEx(0); |
140 | |
141 | // Safety: the GIL is already held because of the Py_IntializeEx call. |
142 | let pool = GILPool::new(); |
143 | |
144 | // Import the threading module - this ensures that it will associate this thread as the "main" |
145 | // thread, which is important to avoid an `AssertionError` at finalization. |
146 | pool.python().import("threading" ).unwrap(); |
147 | |
148 | // Execute the closure. |
149 | let result = f(pool.python()); |
150 | |
151 | // Drop the pool before finalizing. |
152 | drop(pool); |
153 | |
154 | // Finalize the Python interpreter. |
155 | ffi::Py_Finalize(); |
156 | |
157 | result |
158 | } |
159 | |
160 | /// RAII type that represents the Global Interpreter Lock acquisition. |
161 | pub(crate) struct GILGuard { |
162 | gstate: ffi::PyGILState_STATE, |
163 | pool: mem::ManuallyDrop<GILPool>, |
164 | } |
165 | |
166 | impl GILGuard { |
167 | /// PyO3 internal API for acquiring the GIL. The public API is Python::with_gil. |
168 | /// |
169 | /// If the GIL was already acquired via PyO3, this returns `None`. Otherwise, |
170 | /// the GIL will be acquired and a new `GILPool` created. |
171 | pub(crate) fn acquire() -> Option<Self> { |
172 | if gil_is_acquired() { |
173 | return None; |
174 | } |
175 | |
176 | // Maybe auto-initialize the GIL: |
177 | // - If auto-initialize feature set and supported, try to initialize the interpreter. |
178 | // - If the auto-initialize feature is set but unsupported, emit hard errors only when the |
179 | // extension-module feature is not activated - extension modules don't care about |
180 | // auto-initialize so this avoids breaking existing builds. |
181 | // - Otherwise, just check the GIL is initialized. |
182 | cfg_if::cfg_if! { |
183 | if #[cfg(all(feature = "auto-initialize" , not(PyPy)))] { |
184 | prepare_freethreaded_python(); |
185 | } else { |
186 | // This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need |
187 | // to specify `--features auto-initialize` manually. Tests within the crate itself |
188 | // all depend on the auto-initialize feature for conciseness but Cargo does not |
189 | // provide a mechanism to specify required features for tests. |
190 | #[cfg (not(PyPy))] |
191 | if option_env!("CARGO_PRIMARY_PACKAGE" ).is_some() { |
192 | prepare_freethreaded_python(); |
193 | } |
194 | |
195 | START.call_once_force(|_| unsafe { |
196 | // Use call_once_force because if there is a panic because the interpreter is |
197 | // not initialized, it's fine for the user to initialize the interpreter and |
198 | // retry. |
199 | assert_ne!( |
200 | ffi::Py_IsInitialized(), |
201 | 0, |
202 | "The Python interpreter is not initialized and the `auto-initialize` \ |
203 | feature is not enabled. \n\n\ |
204 | Consider calling `pyo3::prepare_freethreaded_python()` before attempting \ |
205 | to use Python APIs." |
206 | ); |
207 | }); |
208 | } |
209 | } |
210 | |
211 | Self::acquire_unchecked() |
212 | } |
213 | |
214 | /// Acquires the `GILGuard` without performing any state checking. |
215 | /// |
216 | /// This can be called in "unsafe" contexts where the normal interpreter state |
217 | /// checking performed by `GILGuard::acquire` may fail. This includes calling |
218 | /// as part of multi-phase interpreter initialization. |
219 | pub(crate) fn acquire_unchecked() -> Option<Self> { |
220 | if gil_is_acquired() { |
221 | return None; |
222 | } |
223 | |
224 | let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL |
225 | let pool = unsafe { mem::ManuallyDrop::new(GILPool::new()) }; |
226 | |
227 | Some(GILGuard { gstate, pool }) |
228 | } |
229 | } |
230 | |
231 | /// The Drop implementation for `GILGuard` will release the GIL. |
232 | impl Drop for GILGuard { |
233 | fn drop(&mut self) { |
234 | unsafe { |
235 | // Drop the objects in the pool before attempting to release the thread state |
236 | mem::ManuallyDrop::drop(&mut self.pool); |
237 | |
238 | ffi::PyGILState_Release(self.gstate); |
239 | } |
240 | } |
241 | } |
242 | |
243 | // Vector of PyObject |
244 | type PyObjVec = Vec<NonNull<ffi::PyObject>>; |
245 | |
246 | /// Thread-safe storage for objects which were inc_ref / dec_ref while the GIL was not held. |
247 | struct ReferencePool { |
248 | // .0 is INCREFs, .1 is DECREFs |
249 | pointer_ops: Mutex<(PyObjVec, PyObjVec)>, |
250 | } |
251 | |
252 | impl ReferencePool { |
253 | const fn new() -> Self { |
254 | Self { |
255 | pointer_ops: const_mutex((Vec::new(), Vec::new())), |
256 | } |
257 | } |
258 | |
259 | fn register_incref(&self, obj: NonNull<ffi::PyObject>) { |
260 | self.pointer_ops.lock().0.push(obj); |
261 | } |
262 | |
263 | fn register_decref(&self, obj: NonNull<ffi::PyObject>) { |
264 | self.pointer_ops.lock().1.push(obj); |
265 | } |
266 | |
267 | fn update_counts(&self, _py: Python<'_>) { |
268 | let mut ops = self.pointer_ops.lock(); |
269 | if ops.0.is_empty() && ops.1.is_empty() { |
270 | return; |
271 | } |
272 | |
273 | let (increfs, decrefs) = mem::take(&mut *ops); |
274 | drop(ops); |
275 | |
276 | // Always increase reference counts first - as otherwise objects which have a |
277 | // nonzero total reference count might be incorrectly dropped by Python during |
278 | // this update. |
279 | for ptr in increfs { |
280 | unsafe { ffi::Py_INCREF(ptr.as_ptr()) }; |
281 | } |
282 | |
283 | for ptr in decrefs { |
284 | unsafe { ffi::Py_DECREF(ptr.as_ptr()) }; |
285 | } |
286 | } |
287 | } |
288 | |
289 | unsafe impl Sync for ReferencePool {} |
290 | |
291 | static POOL: ReferencePool = ReferencePool::new(); |
292 | |
293 | /// A guard which can be used to temporarily release the GIL and restore on `Drop`. |
294 | pub(crate) struct SuspendGIL { |
295 | count: isize, |
296 | tstate: *mut ffi::PyThreadState, |
297 | } |
298 | |
299 | impl SuspendGIL { |
300 | pub(crate) unsafe fn new() -> Self { |
301 | let count: isize = GIL_COUNT.with(|c: &Cell| c.replace(val:0)); |
302 | let tstate: *mut PyThreadState = ffi::PyEval_SaveThread(); |
303 | |
304 | Self { count, tstate } |
305 | } |
306 | } |
307 | |
308 | impl Drop for SuspendGIL { |
309 | fn drop(&mut self) { |
310 | GIL_COUNT.with(|c: &Cell| c.set(self.count)); |
311 | unsafe { |
312 | ffi::PyEval_RestoreThread(self.tstate); |
313 | |
314 | // Update counts of PyObjects / Py that were cloned or dropped while the GIL was released. |
315 | POOL.update_counts(_py:Python::assume_gil_acquired()); |
316 | } |
317 | } |
318 | } |
319 | |
320 | /// Used to lock safe access to the GIL |
321 | pub(crate) struct LockGIL { |
322 | count: isize, |
323 | } |
324 | |
325 | impl LockGIL { |
326 | /// Lock access to the GIL while an implementation of `__traverse__` is running |
327 | pub fn during_traverse() -> Self { |
328 | Self::new(GIL_LOCKED_DURING_TRAVERSE) |
329 | } |
330 | |
331 | fn new(reason: isize) -> Self { |
332 | let count: isize = GIL_COUNT.with(|c: &Cell| c.replace(val:reason)); |
333 | |
334 | Self { count } |
335 | } |
336 | |
337 | #[cold ] |
338 | fn bail(current: isize) { |
339 | match current { |
340 | GIL_LOCKED_DURING_TRAVERSE => panic!( |
341 | "Access to the GIL is prohibited while a __traverse__ implmentation is running." |
342 | ), |
343 | _ => panic!("Access to the GIL is currently prohibited." ), |
344 | } |
345 | } |
346 | } |
347 | |
348 | impl Drop for LockGIL { |
349 | fn drop(&mut self) { |
350 | GIL_COUNT.with(|c: &Cell| c.set(self.count)); |
351 | } |
352 | } |
353 | |
354 | /// A RAII pool which PyO3 uses to store owned Python references. |
355 | /// |
356 | /// See the [Memory Management] chapter of the guide for more information about how PyO3 uses |
357 | /// [`GILPool`] to manage memory. |
358 | |
359 | /// |
360 | /// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory |
361 | pub struct GILPool { |
362 | /// Initial length of owned objects and anys. |
363 | /// `Option` is used since TSL can be broken when `new` is called from `atexit`. |
364 | start: Option<usize>, |
365 | _not_send: NotSend, |
366 | } |
367 | |
368 | impl GILPool { |
369 | /// Creates a new [`GILPool`]. This function should only ever be called with the GIL held. |
370 | /// |
371 | /// It is recommended not to use this API directly, but instead to use [`Python::new_pool`], as |
372 | /// that guarantees the GIL is held. |
373 | /// |
374 | /// # Safety |
375 | /// |
376 | /// As well as requiring the GIL, see the safety notes on [`Python::new_pool`]. |
377 | #[inline ] |
378 | pub unsafe fn new() -> GILPool { |
379 | increment_gil_count(); |
380 | // Update counts of PyObjects / Py that have been cloned or dropped since last acquisition |
381 | POOL.update_counts(Python::assume_gil_acquired()); |
382 | GILPool { |
383 | start: OWNED_OBJECTS |
384 | .try_with(|owned_objects| { |
385 | #[cfg (debug_assertions)] |
386 | let len = owned_objects.borrow().len(); |
387 | #[cfg (not(debug_assertions))] |
388 | // SAFETY: This is not re-entrant. |
389 | let len = unsafe { (*owned_objects.get()).len() }; |
390 | len |
391 | }) |
392 | .ok(), |
393 | _not_send: NOT_SEND, |
394 | } |
395 | } |
396 | |
397 | /// Gets the Python token associated with this [`GILPool`]. |
398 | #[inline ] |
399 | pub fn python(&self) -> Python<'_> { |
400 | unsafe { Python::assume_gil_acquired() } |
401 | } |
402 | } |
403 | |
404 | impl Drop for GILPool { |
405 | fn drop(&mut self) { |
406 | if let Some(start) = self.start { |
407 | let owned_objects = OWNED_OBJECTS.with(|owned_objects| { |
408 | #[cfg (debug_assertions)] |
409 | let mut owned_objects = owned_objects.borrow_mut(); |
410 | #[cfg (not(debug_assertions))] |
411 | // SAFETY: `OWNED_OBJECTS` is released before calling Py_DECREF, |
412 | // or Py_DECREF may call `GILPool::drop` recursively, resulting in invalid borrowing. |
413 | let owned_objects = unsafe { &mut *owned_objects.get() }; |
414 | if start < owned_objects.len() { |
415 | owned_objects.split_off(start) |
416 | } else { |
417 | Vec::new() |
418 | } |
419 | }); |
420 | for obj in owned_objects { |
421 | unsafe { |
422 | ffi::Py_DECREF(obj.as_ptr()); |
423 | } |
424 | } |
425 | } |
426 | decrement_gil_count(); |
427 | } |
428 | } |
429 | |
430 | /// Registers a Python object pointer inside the release pool, to have its reference count increased |
431 | /// the next time the GIL is acquired in pyo3. |
432 | /// |
433 | /// If the GIL is held, the reference count will be increased immediately instead of being queued |
434 | /// for later. |
435 | /// |
436 | /// # Safety |
437 | /// The object must be an owned Python reference. |
438 | pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) { |
439 | if gil_is_acquired() { |
440 | ffi::Py_INCREF(op:obj.as_ptr()) |
441 | } else { |
442 | POOL.register_incref(obj); |
443 | } |
444 | } |
445 | |
446 | /// Registers a Python object pointer inside the release pool, to have its reference count decreased |
447 | /// the next time the GIL is acquired in pyo3. |
448 | /// |
449 | /// If the GIL is held, the reference count will be decreased immediately instead of being queued |
450 | /// for later. |
451 | /// |
452 | /// # Safety |
453 | /// The object must be an owned Python reference. |
454 | pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) { |
455 | if gil_is_acquired() { |
456 | ffi::Py_DECREF(op:obj.as_ptr()) |
457 | } else { |
458 | POOL.register_decref(obj); |
459 | } |
460 | } |
461 | |
462 | /// Registers an owned object inside the GILPool, to be released when the GILPool drops. |
463 | /// |
464 | /// # Safety |
465 | /// The object must be an owned Python reference. |
466 | pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull<ffi::PyObject>) { |
467 | debug_assert!(gil_is_acquired()); |
468 | // Ignores the error in case this function called from `atexit`. |
469 | let _ = OWNED_OBJECTS.try_with(|owned_objects: &RefCell>>| { |
470 | #[cfg (debug_assertions)] |
471 | owned_objects.borrow_mut().push(obj); |
472 | #[cfg (not(debug_assertions))] |
473 | // SAFETY: This is not re-entrant. |
474 | unsafe { |
475 | (*owned_objects.get()).push(obj); |
476 | } |
477 | }); |
478 | } |
479 | |
480 | /// Increments pyo3's internal GIL count - to be called whenever GILPool or GILGuard is created. |
481 | #[inline (always)] |
482 | fn increment_gil_count() { |
483 | // Ignores the error in case this function called from `atexit`. |
484 | let _ = GIL_COUNT.try_with(|c: &Cell| { |
485 | let current: isize = c.get(); |
486 | if current < 0 { |
487 | LockGIL::bail(current); |
488 | } |
489 | c.set(val:current + 1); |
490 | }); |
491 | } |
492 | |
493 | /// Decrements pyo3's internal GIL count - to be called whenever GILPool or GILGuard is dropped. |
494 | #[inline (always)] |
495 | fn decrement_gil_count() { |
496 | // Ignores the error in case this function called from `atexit`. |
497 | let _ = GIL_COUNT.try_with(|c: &Cell| { |
498 | let current: isize = c.get(); |
499 | debug_assert!( |
500 | current > 0, |
501 | "Negative GIL count detected. Please report this error to the PyO3 repo as a bug." |
502 | ); |
503 | c.set(val:current - 1); |
504 | }); |
505 | } |
506 | |
507 | #[cfg (test)] |
508 | mod tests { |
509 | use super::{gil_is_acquired, GILPool, GIL_COUNT, OWNED_OBJECTS, POOL}; |
510 | use crate::{ffi, gil, PyObject, Python, ToPyObject}; |
511 | #[cfg (not(target_arch = "wasm32" ))] |
512 | use parking_lot::{const_mutex, Condvar, Mutex}; |
513 | use std::ptr::NonNull; |
514 | |
515 | fn get_object(py: Python<'_>) -> PyObject { |
516 | // Convenience function for getting a single unique object, using `new_pool` so as to leave |
517 | // the original pool state unchanged. |
518 | let pool = unsafe { py.new_pool() }; |
519 | let py = pool.python(); |
520 | |
521 | let obj = py.eval("object()" , None, None).unwrap(); |
522 | obj.to_object(py) |
523 | } |
524 | |
525 | fn owned_object_count() -> usize { |
526 | #[cfg (debug_assertions)] |
527 | let len = OWNED_OBJECTS.with(|owned_objects| owned_objects.borrow().len()); |
528 | #[cfg (not(debug_assertions))] |
529 | let len = OWNED_OBJECTS.with(|owned_objects| unsafe { (*owned_objects.get()).len() }); |
530 | len |
531 | } |
532 | |
533 | fn pool_inc_refs_does_not_contain(obj: &PyObject) -> bool { |
534 | !POOL |
535 | .pointer_ops |
536 | .lock() |
537 | .0 |
538 | .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) }) |
539 | } |
540 | |
541 | fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool { |
542 | !POOL |
543 | .pointer_ops |
544 | .lock() |
545 | .1 |
546 | .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) }) |
547 | } |
548 | |
549 | #[cfg (not(target_arch = "wasm32" ))] |
550 | fn pool_dirty_with( |
551 | inc_refs: Vec<NonNull<ffi::PyObject>>, |
552 | dec_refs: Vec<NonNull<ffi::PyObject>>, |
553 | ) -> bool { |
554 | *POOL.pointer_ops.lock() == (inc_refs, dec_refs) |
555 | } |
556 | |
557 | #[test ] |
558 | fn test_owned() { |
559 | Python::with_gil(|py| { |
560 | let obj = get_object(py); |
561 | let obj_ptr = obj.as_ptr(); |
562 | // Ensure that obj does not get freed |
563 | let _ref = obj.clone_ref(py); |
564 | |
565 | unsafe { |
566 | { |
567 | let pool = py.new_pool(); |
568 | gil::register_owned(pool.python(), NonNull::new_unchecked(obj.into_ptr())); |
569 | |
570 | assert_eq!(owned_object_count(), 1); |
571 | assert_eq!(ffi::Py_REFCNT(obj_ptr), 2); |
572 | } |
573 | { |
574 | let _pool = py.new_pool(); |
575 | assert_eq!(owned_object_count(), 0); |
576 | assert_eq!(ffi::Py_REFCNT(obj_ptr), 1); |
577 | } |
578 | } |
579 | }) |
580 | } |
581 | |
582 | #[test ] |
583 | fn test_owned_nested() { |
584 | Python::with_gil(|py| { |
585 | let obj = get_object(py); |
586 | // Ensure that obj does not get freed |
587 | let _ref = obj.clone_ref(py); |
588 | let obj_ptr = obj.as_ptr(); |
589 | |
590 | unsafe { |
591 | { |
592 | let _pool = py.new_pool(); |
593 | assert_eq!(owned_object_count(), 0); |
594 | |
595 | gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr())); |
596 | |
597 | assert_eq!(owned_object_count(), 1); |
598 | assert_eq!(ffi::Py_REFCNT(obj_ptr), 2); |
599 | { |
600 | let _pool = py.new_pool(); |
601 | let obj = get_object(py); |
602 | gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr())); |
603 | assert_eq!(owned_object_count(), 2); |
604 | } |
605 | assert_eq!(owned_object_count(), 1); |
606 | } |
607 | { |
608 | assert_eq!(owned_object_count(), 0); |
609 | assert_eq!(ffi::Py_REFCNT(obj_ptr), 1); |
610 | } |
611 | } |
612 | }); |
613 | } |
614 | |
615 | #[test ] |
616 | fn test_pyobject_drop_with_gil_decreases_refcnt() { |
617 | Python::with_gil(|py| { |
618 | let obj = get_object(py); |
619 | |
620 | // Create a reference to drop with the GIL. |
621 | let reference = obj.clone_ref(py); |
622 | |
623 | assert_eq!(obj.get_refcnt(py), 2); |
624 | assert!(pool_inc_refs_does_not_contain(&obj)); |
625 | |
626 | // With the GIL held, reference cound will be decreased immediately. |
627 | drop(reference); |
628 | |
629 | assert_eq!(obj.get_refcnt(py), 1); |
630 | assert!(pool_dec_refs_does_not_contain(&obj)); |
631 | }); |
632 | } |
633 | |
634 | #[test ] |
635 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
636 | fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() { |
637 | let obj = Python::with_gil(|py| { |
638 | let obj = get_object(py); |
639 | // Create a reference to drop without the GIL. |
640 | let reference = obj.clone_ref(py); |
641 | |
642 | assert_eq!(obj.get_refcnt(py), 2); |
643 | assert!(pool_inc_refs_does_not_contain(&obj)); |
644 | |
645 | // Drop reference in a separate thread which doesn't have the GIL. |
646 | std::thread::spawn(move || drop(reference)).join().unwrap(); |
647 | |
648 | // The reference count should not have changed (the GIL has always |
649 | // been held by this thread), it is remembered to release later. |
650 | assert_eq!(obj.get_refcnt(py), 2); |
651 | assert!(pool_dirty_with( |
652 | vec![], |
653 | vec![NonNull::new(obj.as_ptr()).unwrap()] |
654 | )); |
655 | obj |
656 | }); |
657 | |
658 | // Next time the GIL is acquired, the reference is released |
659 | Python::with_gil(|py| { |
660 | assert_eq!(obj.get_refcnt(py), 1); |
661 | let non_null = unsafe { NonNull::new_unchecked(obj.as_ptr()) }; |
662 | assert!(!POOL.pointer_ops.lock().0.contains(&non_null)); |
663 | assert!(!POOL.pointer_ops.lock().1.contains(&non_null)); |
664 | }); |
665 | } |
666 | |
667 | #[test ] |
668 | fn test_gil_counts() { |
669 | // Check with_gil and GILPool both increase counts correctly |
670 | let get_gil_count = || GIL_COUNT.with(|c| c.get()); |
671 | |
672 | assert_eq!(get_gil_count(), 0); |
673 | Python::with_gil(|_| { |
674 | assert_eq!(get_gil_count(), 1); |
675 | |
676 | let pool = unsafe { GILPool::new() }; |
677 | assert_eq!(get_gil_count(), 2); |
678 | |
679 | let pool2 = unsafe { GILPool::new() }; |
680 | assert_eq!(get_gil_count(), 3); |
681 | |
682 | drop(pool); |
683 | assert_eq!(get_gil_count(), 2); |
684 | |
685 | Python::with_gil(|_| { |
686 | // nested with_gil doesn't update gil count |
687 | assert_eq!(get_gil_count(), 2); |
688 | }); |
689 | assert_eq!(get_gil_count(), 2); |
690 | |
691 | drop(pool2); |
692 | assert_eq!(get_gil_count(), 1); |
693 | }); |
694 | assert_eq!(get_gil_count(), 0); |
695 | } |
696 | |
697 | #[test ] |
698 | fn test_allow_threads() { |
699 | assert!(!gil_is_acquired()); |
700 | |
701 | Python::with_gil(|py| { |
702 | assert!(gil_is_acquired()); |
703 | |
704 | py.allow_threads(move || { |
705 | assert!(!gil_is_acquired()); |
706 | |
707 | Python::with_gil(|_| assert!(gil_is_acquired())); |
708 | |
709 | assert!(!gil_is_acquired()); |
710 | }); |
711 | |
712 | assert!(gil_is_acquired()); |
713 | }); |
714 | |
715 | assert!(!gil_is_acquired()); |
716 | } |
717 | |
718 | #[test ] |
719 | fn test_allow_threads_updates_refcounts() { |
720 | Python::with_gil(|py| { |
721 | // Make a simple object with 1 reference |
722 | let obj = get_object(py); |
723 | assert!(obj.get_refcnt(py) == 1); |
724 | // Clone the object without the GIL to use internal tracking |
725 | let escaped_ref = py.allow_threads(|| obj.clone()); |
726 | // But after the block the refcounts are updated |
727 | assert!(obj.get_refcnt(py) == 2); |
728 | drop(escaped_ref); |
729 | assert!(obj.get_refcnt(py) == 1); |
730 | drop(obj); |
731 | }); |
732 | } |
733 | |
734 | #[test ] |
735 | fn dropping_gil_does_not_invalidate_references() { |
736 | // Acquiring GIL for the second time should be safe - see #864 |
737 | Python::with_gil(|py| { |
738 | let obj = Python::with_gil(|_| py.eval("object()" , None, None).unwrap()); |
739 | |
740 | // After gil2 drops, obj should still have a reference count of one |
741 | assert_eq!(obj.get_refcnt(), 1); |
742 | }) |
743 | } |
744 | |
745 | #[test ] |
746 | fn test_clone_with_gil() { |
747 | Python::with_gil(|py| { |
748 | let obj = get_object(py); |
749 | let count = obj.get_refcnt(py); |
750 | |
751 | // Cloning with the GIL should increase reference count immediately |
752 | #[allow (clippy::redundant_clone)] |
753 | let c = obj.clone(); |
754 | assert_eq!(count + 1, c.get_refcnt(py)); |
755 | }) |
756 | } |
757 | |
758 | #[cfg (not(target_arch = "wasm32" ))] |
759 | struct Event { |
760 | set: Mutex<bool>, |
761 | wait: Condvar, |
762 | } |
763 | |
764 | #[cfg (not(target_arch = "wasm32" ))] |
765 | impl Event { |
766 | const fn new() -> Self { |
767 | Self { |
768 | set: const_mutex(false), |
769 | wait: Condvar::new(), |
770 | } |
771 | } |
772 | |
773 | fn set(&self) { |
774 | *self.set.lock() = true; |
775 | self.wait.notify_all(); |
776 | } |
777 | |
778 | fn wait(&self) { |
779 | let mut set = self.set.lock(); |
780 | while !*set { |
781 | self.wait.wait(&mut set); |
782 | } |
783 | } |
784 | } |
785 | |
786 | #[test ] |
787 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
788 | fn test_clone_without_gil() { |
789 | use crate::{Py, PyAny}; |
790 | use std::{sync::Arc, thread}; |
791 | |
792 | // Some events for synchronizing |
793 | static GIL_ACQUIRED: Event = Event::new(); |
794 | static OBJECT_CLONED: Event = Event::new(); |
795 | static REFCNT_CHECKED: Event = Event::new(); |
796 | |
797 | Python::with_gil(|py| { |
798 | let obj: Arc<Py<PyAny>> = Arc::new(get_object(py)); |
799 | let thread_obj = Arc::clone(&obj); |
800 | |
801 | let count = obj.get_refcnt(py); |
802 | println!( |
803 | "1: The object has been created and its reference count is {}" , |
804 | count |
805 | ); |
806 | |
807 | let handle = thread::spawn(move || { |
808 | Python::with_gil(move |py| { |
809 | println!("3. The GIL has been acquired on another thread." ); |
810 | GIL_ACQUIRED.set(); |
811 | |
812 | // Wait while the main thread registers obj in POOL |
813 | OBJECT_CLONED.wait(); |
814 | println!("5. Checking refcnt" ); |
815 | assert_eq!(thread_obj.get_refcnt(py), count); |
816 | |
817 | REFCNT_CHECKED.set(); |
818 | }) |
819 | }); |
820 | |
821 | let cloned = py.allow_threads(|| { |
822 | println!("2. The GIL has been released." ); |
823 | |
824 | // Wait until the GIL has been acquired on the thread. |
825 | GIL_ACQUIRED.wait(); |
826 | |
827 | println!("4. The other thread is now hogging the GIL, we clone without it held" ); |
828 | // Cloning without GIL should not update reference count |
829 | let cloned = Py::clone(&*obj); |
830 | OBJECT_CLONED.set(); |
831 | cloned |
832 | }); |
833 | |
834 | REFCNT_CHECKED.wait(); |
835 | |
836 | println!("6. The main thread has acquired the GIL again and processed the pool." ); |
837 | |
838 | // Total reference count should be one higher |
839 | assert_eq!(obj.get_refcnt(py), count + 1); |
840 | |
841 | // Clone dropped |
842 | drop(cloned); |
843 | // Ensure refcount of the arc is 1 |
844 | handle.join().unwrap(); |
845 | |
846 | // Overall count is now back to the original, and should be no pending change |
847 | assert_eq!(Arc::try_unwrap(obj).unwrap().get_refcnt(py), count); |
848 | }); |
849 | } |
850 | |
851 | #[test ] |
852 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
853 | fn test_clone_in_other_thread() { |
854 | use crate::Py; |
855 | use std::{sync::Arc, thread}; |
856 | |
857 | // Some events for synchronizing |
858 | static OBJECT_CLONED: Event = Event::new(); |
859 | |
860 | let (obj, count, ptr) = Python::with_gil(|py| { |
861 | let obj = Arc::new(get_object(py)); |
862 | let count = obj.get_refcnt(py); |
863 | let thread_obj = Arc::clone(&obj); |
864 | |
865 | // Start a thread which does not have the GIL, and clone it |
866 | let t = thread::spawn(move || { |
867 | // Cloning without GIL should not update reference count |
868 | #[allow (clippy::redundant_clone)] |
869 | let _ = Py::clone(&*thread_obj); |
870 | OBJECT_CLONED.set(); |
871 | }); |
872 | |
873 | OBJECT_CLONED.wait(); |
874 | assert_eq!(count, obj.get_refcnt(py)); |
875 | |
876 | t.join().unwrap(); |
877 | let ptr = NonNull::new(obj.as_ptr()).unwrap(); |
878 | |
879 | // The pointer should appear once in the incref pool, and once in the |
880 | // decref pool (for the clone being created and also dropped) |
881 | assert!(POOL.pointer_ops.lock().0.contains(&ptr)); |
882 | assert!(POOL.pointer_ops.lock().1.contains(&ptr)); |
883 | |
884 | (obj, count, ptr) |
885 | }); |
886 | |
887 | Python::with_gil(|py| { |
888 | // Acquiring the gil clears the pool |
889 | assert!(!POOL.pointer_ops.lock().0.contains(&ptr)); |
890 | assert!(!POOL.pointer_ops.lock().1.contains(&ptr)); |
891 | |
892 | // Overall count is still unchanged |
893 | assert_eq!(count, obj.get_refcnt(py)); |
894 | }); |
895 | } |
896 | |
897 | #[test ] |
898 | fn test_update_counts_does_not_deadlock() { |
899 | // update_counts can run arbitrary Python code during Py_DECREF. |
900 | // if the locking is implemented incorrectly, it will deadlock. |
901 | |
902 | Python::with_gil(|py| { |
903 | let obj = get_object(py); |
904 | |
905 | unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) { |
906 | // This line will implicitly call update_counts |
907 | // -> and so cause deadlock if update_counts is not handling recursion correctly. |
908 | let pool = GILPool::new(); |
909 | |
910 | // Rebuild obj so that it can be dropped |
911 | PyObject::from_owned_ptr( |
912 | pool.python(), |
913 | ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _, |
914 | ); |
915 | } |
916 | |
917 | let ptr = obj.into_ptr(); |
918 | |
919 | let capsule = |
920 | unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) }; |
921 | |
922 | POOL.register_decref(NonNull::new(capsule).unwrap()); |
923 | |
924 | // Updating the counts will call decref on the capsule, which calls capsule_drop |
925 | POOL.update_counts(py); |
926 | }) |
927 | } |
928 | } |
929 | |