@@ -282,7 +282,8 @@ impl Drop for GILGuard {
282
282
#[ cfg( not( pyo3_disable_reference_pool) ) ]
283
283
/// Thread-safe storage for objects which were dec_ref while the GIL was not held.
284
284
struct PendingDecRef {
285
- obj : NonNull < ffi:: PyObject > ,
285
+ objs : [ NonNull < ffi:: PyObject > ; 32 ] ,
286
+ cnt : usize ,
286
287
next : * mut PendingDecRef ,
287
288
}
288
289
@@ -291,17 +292,57 @@ static POOL: AtomicPtr<PendingDecRef> = AtomicPtr::new(null_mut());
291
292
292
293
#[ cfg( not( pyo3_disable_reference_pool) ) ]
293
294
fn enqueue_decref ( obj : NonNull < ffi:: PyObject > ) {
295
+ let old_top = POOL . swap ( null_mut ( ) , Ordering :: AcqRel ) ;
296
+
297
+ let mut curr = old_top;
298
+
299
+ while !curr. is_null ( ) {
300
+ // SAFETY: We have exclusive ownership of the full stack starting at `old_top`.
301
+ let val = unsafe { & mut * curr } ;
302
+
303
+ if val. cnt < val. objs . len ( ) {
304
+ val. objs [ val. cnt ] = obj;
305
+ val. cnt += 1 ;
306
+
307
+ let mut last_next = & mut val. next ;
308
+
309
+ while !last_next. is_null ( ) {
310
+ last_next = unsafe { & mut ( * * last_next) . next } ;
311
+ }
312
+
313
+ POOL . fetch_update ( Ordering :: AcqRel , Ordering :: Relaxed , |new_top| {
314
+ * last_next = new_top;
315
+ Some ( old_top)
316
+ } )
317
+ . unwrap ( ) ;
318
+ return ;
319
+ }
320
+
321
+ curr = val. next ;
322
+ }
323
+
324
+ // There is no existing stack or it has no unused capacity remaining,
325
+ // hence we allocate a new block and prepend it locally before publishing.
326
+ let mut objs = [ NonNull :: dangling ( ) ; 32 ] ;
327
+ objs[ 0 ] = obj;
328
+
294
329
let val = PendingDecRef {
295
- obj,
296
- next : null_mut ( ) ,
330
+ objs,
331
+ cnt : 1 ,
332
+ next : old_top,
297
333
} ;
298
334
299
335
let top = Box :: into_raw ( Box :: new ( val) ) ;
300
336
301
- let next = unsafe { & mut ( * top) . next } ;
337
+ // SAFETY: We just allocated `top` using `Box::new`.
338
+ let mut last_next = unsafe { & mut ( * top) . next } ;
339
+
340
+ while !last_next. is_null ( ) {
341
+ last_next = unsafe { & mut ( * * last_next) . next } ;
342
+ }
302
343
303
344
POOL . fetch_update ( Ordering :: AcqRel , Ordering :: Relaxed , |new_top| {
304
- * next = new_top;
345
+ * last_next = new_top;
305
346
Some ( top)
306
347
} )
307
348
. unwrap ( ) ;
@@ -314,7 +355,9 @@ fn update_counts(_py: Python<'_>) {
314
355
// SAFETY: Was enqueued using `Box::into_raw`.
315
356
let val = unsafe { Box :: from_raw ( top) } ;
316
357
317
- unsafe { ffi:: Py_DECREF ( val. obj . as_ptr ( ) ) } ;
358
+ for obj in & val. objs [ ..val. cnt ] {
359
+ unsafe { ffi:: Py_DECREF ( obj. as_ptr ( ) ) } ;
360
+ }
318
361
319
362
top = val. next ;
320
363
}
@@ -581,11 +624,13 @@ mod tests {
581
624
// SAFETY: Was enqueued using `Box::into_raw`.
582
625
let val = unsafe { Box :: from_raw ( top) } ;
583
626
584
- if val. obj . as_ptr ( ) == obj. as_ptr ( ) {
585
- found = true ;
586
- }
627
+ for obj1 in & val. objs [ ..val. cnt ] {
628
+ if obj1. as_ptr ( ) == obj. as_ptr ( ) {
629
+ found = true ;
630
+ }
587
631
588
- unsafe { ffi:: Py_DECREF ( val. obj . as_ptr ( ) ) } ;
632
+ unsafe { ffi:: Py_DECREF ( obj1. as_ptr ( ) ) } ;
633
+ }
589
634
590
635
top = val. next ;
591
636
}
0 commit comments