@@ -417,96 +417,82 @@ public static int pollReferenceQueue() {
417
417
int count = 0 ;
418
418
long start = 0 ;
419
419
NativeObjectReferenceArrayWrapper referencesToBeFreed = handleContext .referencesToBeFreed ;
420
- PythonContext .PythonThreadState threadState = context .getThreadState (context .getLanguage ());
421
- /*
422
- * There can be an active exception. Since we might be calling arbitary python, we
423
- * need to stash it.
424
- */
425
- Object savedException = CExtCommonNodes .ReadAndClearNativeException .executeUncached (threadState );
426
- try {
427
- while (true ) {
428
- Object entry = queue .poll ();
429
- if (entry == null ) {
430
- if (count > 0 ) {
431
- assert handleContext .referenceQueuePollActive ;
432
- releaseNativeObjects (referencesToBeFreed );
433
- handleContext .referenceQueuePollActive = false ;
434
- LOGGER .fine ("collected " + count + " references from native reference queue in " + ((System .nanoTime () - start ) / 1000000 ) + "ms" );
435
- }
436
- return manuallyCollected ;
437
- }
438
- if (count == 0 ) {
439
- assert !handleContext .referenceQueuePollActive ;
440
- handleContext .referenceQueuePollActive = true ;
441
- start = System .nanoTime ();
442
- } else {
420
+ while (true ) {
421
+ Object entry = queue .poll ();
422
+ if (entry == null ) {
423
+ if (count > 0 ) {
443
424
assert handleContext .referenceQueuePollActive ;
425
+ releaseNativeObjects (context , referencesToBeFreed );
426
+ handleContext .referenceQueuePollActive = false ;
427
+ LOGGER .fine ("collected " + count + " references from native reference queue in " + ((System .nanoTime () - start ) / 1000000 ) + "ms" );
444
428
}
445
- count ++;
446
- LOGGER .fine (() -> PythonUtils .formatJString ("releasing %s, no remaining managed references" , entry ));
447
- if (entry instanceof PythonObjectReference reference ) {
448
- if (HandlePointerConverter .pointsToPyHandleSpace (reference .pointer )) {
449
- assert nativeStubLookupGet (handleContext , reference .pointer , reference .handleTableIndex ) != null : Long .toHexString (reference .pointer );
450
- LOGGER .finer (() -> PythonUtils .formatJString ("releasing native stub lookup for managed object %x => %s" , reference .pointer , reference ));
451
- nativeStubLookupRemove (handleContext , reference );
429
+ return manuallyCollected ;
430
+ }
431
+ if (count == 0 ) {
432
+ assert !handleContext .referenceQueuePollActive ;
433
+ handleContext .referenceQueuePollActive = true ;
434
+ start = System .nanoTime ();
435
+ } else {
436
+ assert handleContext .referenceQueuePollActive ;
437
+ }
438
+ count ++;
439
+ LOGGER .fine (() -> PythonUtils .formatJString ("releasing %s, no remaining managed references" , entry ));
440
+ if (entry instanceof PythonObjectReference reference ) {
441
+ if (HandlePointerConverter .pointsToPyHandleSpace (reference .pointer )) {
442
+ assert nativeStubLookupGet (handleContext , reference .pointer , reference .handleTableIndex ) != null : Long .toHexString (reference .pointer );
443
+ LOGGER .finer (() -> PythonUtils .formatJString ("releasing native stub lookup for managed object %x => %s" , reference .pointer , reference ));
444
+ nativeStubLookupRemove (handleContext , reference );
445
+ /*
446
+ * We may only free native object stubs if their reference count is
447
+ * zero. We cannot free other structs (e.g. PyDateTime_CAPI) because we
448
+ * don't know if they are still used from native code. Those must be
449
+ * free'd at context finalization.
450
+ */
451
+ long stubPointer = HandlePointerConverter .pointerToStub (reference .pointer );
452
+ long newRefCount = subNativeRefCount (stubPointer , MANAGED_REFCNT );
453
+ if (newRefCount == 0 ) {
454
+ LOGGER .finer (() -> PythonUtils .formatJString ("No more references for %s (refcount->0): freeing native stub" , reference ));
455
+ freeNativeStub (reference );
456
+ } else {
457
+ LOGGER .finer (() -> PythonUtils .formatJString ("Some native references to %s remain (refcount=%d): not freeing native stub yet" , reference , newRefCount ));
452
458
/*
453
- * We may only free native object stubs if their reference count is
454
- * zero. We cannot free other structs (e.g. PyDateTime_CAPI) because
455
- * we don't know if they are still used from native code. Those must
456
- * be free'd at context finalization.
459
+ * In this case, the object is no longer referenced from managed but
460
+ * still from native code (since the reference count is greater 0).
461
+ * This case is possible if there are reference cycles that include
462
+ * managed objects. We overwrite field 'CFields.GraalPyObject__id'
463
+ * to avoid incorrect reuse of the ID which could resolve to another
464
+ * object.
457
465
*/
458
- long stubPointer = HandlePointerConverter .pointerToStub (reference .pointer );
459
- long newRefCount = subNativeRefCount (stubPointer , MANAGED_REFCNT );
460
- if (newRefCount == 0 ) {
461
- LOGGER .finer (() -> PythonUtils .formatJString ("No more references for %s (refcount->0): freeing native stub" , reference ));
462
- freeNativeStub (reference );
463
- } else {
464
- LOGGER .finer (() -> PythonUtils .formatJString ("Some native references to %s remain (refcount=%d): not freeing native stub yet" , reference , newRefCount ));
465
- /*
466
- * In this case, the object is no longer referenced from managed
467
- * but still from native code (since the reference count is
468
- * greater 0). This case is possible if there are reference
469
- * cycles that include managed objects. We overwrite field
470
- * 'CFields.GraalPyObject__id' to avoid incorrect reuse of the
471
- * ID which could resolve to another object.
472
- */
473
- CStructAccess .WriteIntNode .writeUncached (stubPointer , CFields .GraalPyObject__handle_table_index , 0 );
474
- // this can only happen if the object is a GC object
475
- assert reference .gc ;
476
- /*
477
- * Since the managed object is already dead (only the native
478
- * object stub is still alive), we need to remove the object
479
- * from its current GC list. Otherwise, the Python GC would try
480
- * to traverse the object on the next collection which would
481
- * lead to a crash.
482
- */
483
- GCListRemoveNode .executeUncached (stubPointer );
484
- }
485
- } else {
486
- assert nativeLookupGet (handleContext , reference .pointer ) != null : Long .toHexString (reference .pointer );
487
- LOGGER .finer (() -> PythonUtils .formatJString ("releasing native stub lookup for managed object with replacement %x => %s" , reference .pointer , reference ));
488
- nativeLookupRemove (handleContext , reference .pointer );
489
- if (reference .isFreeAtCollection ()) {
490
- LOGGER .finer (() -> PythonUtils .formatJString ("freeing managed object %s replacement" , reference ));
491
- freeNativeStruct (reference );
492
- }
466
+ CStructAccess .WriteIntNode .writeUncached (stubPointer , CFields .GraalPyObject__handle_table_index , 0 );
467
+ // this can only happen if the object is a GC object
468
+ assert reference .gc ;
469
+ /*
470
+ * Since the managed object is already dead (only the native object
471
+ * stub is still alive), we need to remove the object from its
472
+ * current GC list. Otherwise, the Python GC would try to traverse
473
+ * the object on the next collection which would lead to a crash.
474
+ */
475
+ GCListRemoveNode .executeUncached (stubPointer );
493
476
}
494
- } else if (entry instanceof NativeObjectReference reference ) {
495
- LOGGER .finer (() -> PythonUtils .formatJString ("releasing native lookup for native object %x => %s" , reference .pointer , reference ));
477
+ } else {
478
+ assert nativeLookupGet (handleContext , reference .pointer ) != null : Long .toHexString (reference .pointer );
479
+ LOGGER .finer (() -> PythonUtils .formatJString ("releasing native stub lookup for managed object with replacement %x => %s" , reference .pointer , reference ));
496
480
nativeLookupRemove (handleContext , reference .pointer );
497
- processNativeObjectReference (reference , referencesToBeFreed );
498
- } else if (entry instanceof NativeStorageReference reference ) {
499
- handleContext .nativeStorageReferences .remove (reference );
500
- processNativeStorageReference (reference );
501
- } else if (entry instanceof PyCapsuleReference reference ) {
502
- handleContext .pyCapsuleReferences .remove (reference );
503
- processPyCapsuleReference (reference );
481
+ if (reference .isFreeAtCollection ()) {
482
+ LOGGER .finer (() -> PythonUtils .formatJString ("freeing managed object %s replacement" , reference ));
483
+ freeNativeStruct (reference );
484
+ }
504
485
}
505
- }
506
- } finally {
507
- CExtCommonNodes .ReadAndClearNativeException .executeUncached (threadState );
508
- if (savedException != PNone .NO_VALUE ) {
509
- CExtCommonNodes .TransformExceptionToNativeNode .executeUncached (savedException );
486
+ } else if (entry instanceof NativeObjectReference reference ) {
487
+ LOGGER .finer (() -> PythonUtils .formatJString ("releasing native lookup for native object %x => %s" , reference .pointer , reference ));
488
+ nativeLookupRemove (handleContext , reference .pointer );
489
+ processNativeObjectReference (reference , referencesToBeFreed );
490
+ } else if (entry instanceof NativeStorageReference reference ) {
491
+ handleContext .nativeStorageReferences .remove (reference );
492
+ processNativeStorageReference (reference );
493
+ } else if (entry instanceof PyCapsuleReference reference ) {
494
+ handleContext .pyCapsuleReferences .remove (reference );
495
+ processPyCapsuleReference (reference );
510
496
}
511
497
}
512
498
}
@@ -559,19 +545,32 @@ private static void processPyCapsuleReference(PyCapsuleReference reference) {
559
545
* element. This method may therefore run arbitrary guest code and strictly requires the GIL to
560
546
* be held at the time of invocation.
561
547
*/
562
- private static void releaseNativeObjects (NativeObjectReferenceArrayWrapper referencesToBeFreed ) {
548
+ private static void releaseNativeObjects (PythonContext context , NativeObjectReferenceArrayWrapper referencesToBeFreed ) {
563
549
if (!referencesToBeFreed .isEmpty ()) {
564
550
/*
565
551
* This needs the GIL because this will call the native objects' destructors which can
566
552
* be arbitrary guest code.
567
553
*/
568
554
assert PythonContext .get (null ).ownsGil ();
569
- LOGGER .fine (() -> PythonUtils .formatJString ("releasing %d NativeObjectReference instances" , referencesToBeFreed .getArraySize ()));
570
- Object array = AllocateNode .allocUncached (referencesToBeFreed .getArraySize () * Long .BYTES );
571
- CStructAccess .WriteLongNode .getUncached ().writeLongArray (array , referencesToBeFreed .getArray (), (int ) referencesToBeFreed .getArraySize (), 0 , 0 );
572
- PCallCapiFunction .callUncached (NativeCAPISymbol .FUN_BULK_DEALLOC , array , referencesToBeFreed .getArraySize ());
573
- FreeNode .executeUncached (array );
574
- referencesToBeFreed .reset ();
555
+ PythonContext .PythonThreadState threadState = context .getThreadState (context .getLanguage ());
556
+ /*
557
+ * There can be an active exception. Since we might be calling arbitary python, we need
558
+ * to stash it.
559
+ */
560
+ Object savedException = CExtCommonNodes .ReadAndClearNativeException .executeUncached (threadState );
561
+ try {
562
+ LOGGER .fine (() -> PythonUtils .formatJString ("releasing %d NativeObjectReference instances" , referencesToBeFreed .getArraySize ()));
563
+ Object array = AllocateNode .allocUncached (referencesToBeFreed .getArraySize () * Long .BYTES );
564
+ CStructAccess .WriteLongNode .getUncached ().writeLongArray (array , referencesToBeFreed .getArray (), (int ) referencesToBeFreed .getArraySize (), 0 , 0 );
565
+ PCallCapiFunction .callUncached (NativeCAPISymbol .FUN_BULK_DEALLOC , array , referencesToBeFreed .getArraySize ());
566
+ FreeNode .executeUncached (array );
567
+ referencesToBeFreed .reset ();
568
+ } finally {
569
+ CExtCommonNodes .ReadAndClearNativeException .executeUncached (threadState );
570
+ if (savedException != PNone .NO_VALUE ) {
571
+ CExtCommonNodes .TransformExceptionToNativeNode .executeUncached (savedException );
572
+ }
573
+ }
575
574
}
576
575
}
577
576
0 commit comments