@@ -417,96 +417,82 @@ public static int pollReferenceQueue() {
417417 int count = 0 ;
418418 long start = 0 ;
419419 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 ) {
443424 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" );
444428 }
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 ));
452458 /*
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.
457465 */
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 );
493476 }
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 ));
496480 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+ }
504485 }
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 );
510496 }
511497 }
512498 }
@@ -559,19 +545,32 @@ private static void processPyCapsuleReference(PyCapsuleReference reference) {
559545 * element. This method may therefore run arbitrary guest code and strictly requires the GIL to
560546 * be held at the time of invocation.
561547 */
562- private static void releaseNativeObjects (NativeObjectReferenceArrayWrapper referencesToBeFreed ) {
548+ private static void releaseNativeObjects (PythonContext context , NativeObjectReferenceArrayWrapper referencesToBeFreed ) {
563549 if (!referencesToBeFreed .isEmpty ()) {
564550 /*
565551 * This needs the GIL because this will call the native objects' destructors which can
566552 * be arbitrary guest code.
567553 */
568554 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+ }
575574 }
576575 }
577576
0 commit comments