56
56
import static com .oracle .graal .python .builtins .objects .cext .hpy .GraalHPyNativeSymbol .GRAAL_HPY_CONTEXT_TO_NATIVE ;
57
57
58
58
import java .io .IOException ;
59
- import java .lang .ref .PhantomReference ;
60
59
import java .lang .ref .Reference ;
61
60
import java .lang .ref .ReferenceQueue ;
61
+ import java .lang .ref .WeakReference ;
62
62
import java .nio .file .Paths ;
63
63
import java .util .ArrayList ;
64
64
import java .util .Arrays ;
@@ -748,20 +748,15 @@ public static GraalHPyNativeSymbol getGetterFunctionName(LLVMType llvmType) {
748
748
749
749
/**
750
750
* The global reference queue is a list consisting of {@link GraalHPyHandleReference} objects.
751
- * It is used to keep those objects (which are phantom refs) alive until they are enqueued in
752
- * the corresponding reference queue. The list instance referenced by this variable is
753
- * exclusively owned by the main thread (i.e. the main thread may operate on the list without
751
+ * It is used to keep those objects (which are weak refs) alive until they are enqueued in the
752
+ * corresponding reference queue. The list instance referenced by this variable is exclusively
753
+ * owned by the main thread (i.e. the main thread may operate on the list without
754
754
* synchronization). The HPy reference cleaner thread (see
755
755
* {@link GraalHPyReferenceCleanerRunnable}) will consume this instance using an atomic
756
756
* {@code getAndSet} operation. At this point, the ownership is transferred to the cleaner
757
- * thread. In order to avoid that the list is consumed by the cleaner thread while the main
758
- * thread is mutating it, the main thread will temporarily set this variable to {@code null}
759
- * (see
760
- * {@link #createHandleReference(com.oracle.graal.python.builtins.objects.object.PythonObject, Object, Object)}
761
- * . Except of this situation, this variable will never be {@code null}. If the cleaner thread
762
- * tries to consume while it is {@code null}, it will spin until an instance is again available.
757
+ * thread.
763
758
*/
764
- public final AtomicReference <GraalHPyHandleReferenceList > references = new AtomicReference <>(new GraalHPyHandleReferenceList () );
759
+ public final AtomicReference <GraalHPyHandleReference > references = new AtomicReference <>(null );
765
760
private ReferenceQueue <Object > nativeSpaceReferenceQueue ;
766
761
@ CompilationFinal private RootCallTarget referenceCleanerCallTarget ;
767
762
private Thread hpyReferenceCleanerThread ;
@@ -813,7 +808,7 @@ private RootCallTarget getReferenceCleanerCallTarget() {
813
808
static final class GraalHPyReferenceCleanerRunnable implements Runnable {
814
809
private static final TruffleLogger LOGGER = PythonLanguage .getLogger (GraalHPyReferenceCleanerRunnable .class );
815
810
private final ReferenceQueue <?> referenceQueue ;
816
- private GraalHPyHandleReferenceList cleanerList ;
811
+ private GraalHPyHandleReference cleanerList ;
817
812
818
813
GraalHPyReferenceCleanerRunnable (ReferenceQueue <?> referenceQueue ) {
819
814
this .referenceQueue = referenceQueue ;
@@ -860,36 +855,26 @@ public void run() {
860
855
* will be replaced by an empty list (which will then be owned by the main
861
856
* thread).
862
857
*/
863
- GraalHPyHandleReferenceList refList ;
864
- GraalHPyHandleReferenceList emptyRefList = new GraalHPyHandleReferenceList ();
858
+ GraalHPyHandleReference refList ;
865
859
do {
866
860
/*
867
861
* If 'refList' is null then the main is currently updating it. So, we need
868
862
* to repeat until we get something. The written empty list will just be
869
863
* lost.
870
864
*/
871
- refList = hPyContext .references .getAndSet (emptyRefList );
865
+ refList = hPyContext .references .getAndSet (null );
872
866
} while (refList == null );
873
867
874
- /*
875
- * Merge the received reference list into the existing one or just take it if
876
- * there wasn't one before.
877
- */
878
- if (cleanerList == null ) {
879
- cleanerList = refList ;
880
- } else {
881
- cleanerList .append (refList );
882
- }
883
-
884
868
if (!refs .isEmpty ()) {
885
869
try {
886
- Object [] arguments = PArguments .create (2 );
870
+ Object [] arguments = PArguments .create (3 );
887
871
PArguments .setGlobals (arguments , dummyGlobals );
888
872
PArguments .setException (arguments , PException .NO_EXCEPTION );
889
873
PArguments .setCallerFrameInfo (arguments , PFrame .Reference .EMPTY );
890
874
PArguments .setArgument (arguments , 0 , refs .toArray (new GraalHPyHandleReference [0 ]));
891
- PArguments .setArgument (arguments , 1 , cleanerList );
892
- CallTargetInvokeNode .invokeUncached (callTarget , arguments );
875
+ PArguments .setArgument (arguments , 1 , refList );
876
+ PArguments .setArgument (arguments , 2 , cleanerList );
877
+ cleanerList = (GraalHPyHandleReference ) CallTargetInvokeNode .invokeUncached (callTarget , arguments );
893
878
} catch (PException e ) {
894
879
/*
895
880
* Since the cleaner thread is not running any Python code, we should
@@ -937,7 +922,8 @@ public Object execute(VirtualFrame frame) {
937
922
*/
938
923
939
924
GraalHPyHandleReference [] handleReferences = (GraalHPyHandleReference []) PArguments .getArgument (frame , 0 );
940
- GraalHPyHandleReferenceList refList = (GraalHPyHandleReferenceList ) PArguments .getArgument (frame , 1 );
925
+ GraalHPyHandleReference refList = (GraalHPyHandleReference ) PArguments .getArgument (frame , 1 );
926
+ GraalHPyHandleReference oldRefList = (GraalHPyHandleReference ) PArguments .getArgument (frame , 2 );
941
927
long startTime = 0 ;
942
928
long middleTime = 0 ;
943
929
final int n = handleReferences .length ;
@@ -952,9 +938,36 @@ public Object execute(VirtualFrame frame) {
952
938
if (CompilerDirectives .inInterpreter ()) {
953
939
com .oracle .truffle .api .nodes .LoopNode .reportLoopCount (this , n );
954
940
}
955
- // remove references from the global reference list such that they can die
941
+
942
+ // mark queued references as cleaned
956
943
for (int i = 0 ; i < n ; i ++) {
957
- refList .remove (handleReferences [i ]);
944
+ handleReferences [i ].cleaned = true ;
945
+ }
946
+
947
+ // remove marked references from the global reference list such that they can die
948
+ GraalHPyHandleReference prev = null ;
949
+ for (GraalHPyHandleReference cur = refList ; cur != null ; cur = cur .next ) {
950
+ if (cur .cleaned ) {
951
+ if (prev != null ) {
952
+ prev .next = cur .next ;
953
+ } else {
954
+ // new head
955
+ refList = cur .next ;
956
+ }
957
+ } else {
958
+ prev = cur ;
959
+ }
960
+ }
961
+
962
+ /*
963
+ * Merge the received reference list into the existing one or just take it if there
964
+ * wasn't one before.
965
+ */
966
+ if (prev != null ) {
967
+ // if prev exists, it now points to the tail
968
+ prev .next = oldRefList ;
969
+ } else {
970
+ refList = oldRefList ;
958
971
}
959
972
960
973
if (loggable ) {
@@ -975,7 +988,7 @@ public Object execute(VirtualFrame frame) {
975
988
LOGGER .fine (() -> "Count duration: " + countDuration );
976
989
LOGGER .fine (() -> "Duration: " + duration );
977
990
}
978
- return PNone . NONE ;
991
+ return refList ;
979
992
}
980
993
981
994
@ Override
@@ -2197,68 +2210,15 @@ int pop() {
2197
2210
}
2198
2211
2199
2212
/**
2200
- * A simple doubly-linked list consisting of {@link GraalHPyHandleReference} objects which are
2201
- * also the nodes of this list.<br>
2202
- * For a description on how this list is used, see {@link #references}.
2203
- */
2204
- static final class GraalHPyHandleReferenceList {
2205
- GraalHPyHandleReference head ;
2206
- GraalHPyHandleReference tail ;
2207
-
2208
- GraalHPyHandleReferenceList () {
2209
- }
2210
-
2211
- void insert (GraalHPyHandleReference ref ) {
2212
- if (tail == null ) {
2213
- assert head == null ;
2214
- tail = ref ;
2215
- }
2216
- if (head != null ) {
2217
- ref .next = head ;
2218
- head .prev = ref ;
2219
- }
2220
- head = ref ;
2221
- }
2222
-
2223
- void remove (GraalHPyHandleReference ref ) {
2224
- if (ref .next != null ) {
2225
- ref .next .prev = ref .prev ;
2226
- } else {
2227
- tail = ref .prev ;
2228
- }
2229
- if (ref .prev != null ) {
2230
- ref .prev .next = ref .next ;
2231
- } else {
2232
- head = ref .next ;
2233
- }
2234
- }
2235
-
2236
- void append (GraalHPyHandleReferenceList other ) {
2237
- if (other .head != null ) {
2238
- assert other .tail != null ;
2239
- if (head == null ) {
2240
- head = other .head ;
2241
- tail = other .tail ;
2242
- } else {
2243
- assert tail != null ;
2244
- tail .next = other .head ;
2245
- other .head .prev = tail ;
2246
- tail = other .tail ;
2247
- }
2248
- }
2249
- }
2250
- }
2251
-
2252
- /**
2253
- * A phantom reference to an object that has an associated HPy native space (
2213
+ * A weak reference to an object that has an associated HPy native space (
2254
2214
* {@link PythonHPyObject}).
2255
2215
*/
2256
- static final class GraalHPyHandleReference extends PhantomReference <Object > {
2216
+ static final class GraalHPyHandleReference extends WeakReference <Object > {
2257
2217
2258
2218
private final Object nativeSpace ;
2259
2219
private final Object destroyFunc ;
2260
2220
2261
- private GraalHPyHandleReference prev ;
2221
+ boolean cleaned ;
2262
2222
private GraalHPyHandleReference next ;
2263
2223
2264
2224
public GraalHPyHandleReference (Object referent , ReferenceQueue <Object > q , Object nativeSpace , Object destroyFunc ) {
@@ -2288,7 +2248,7 @@ public void setNext(GraalHPyHandleReference next) {
2288
2248
* Registers an HPy native space of a Python object.<br/>
2289
2249
* Use this method to register a native memory that is associated with a Python object in order
2290
2250
* to ensure that the native memory will be free'd when the owning Python object dies.<br/>
2291
- * This works by creating a phantom reference to the Python object, using a thread that
2251
+ * This works by creating a weak reference to the Python object, using a thread that
2292
2252
* concurrently polls the reference queue. If threading is allowed, cleaning will be done fully
2293
2253
* concurrent on a cleaner thread. If not, an async action will be scheduled to free the native
2294
2254
* memory. Hence, the destroy function could also be executed on the cleaner thread.
@@ -2301,20 +2261,10 @@ public void setNext(GraalHPyHandleReference next) {
2301
2261
@ TruffleBoundary
2302
2262
final void createHandleReference (PythonObject pythonObject , Object dataPtr , Object destroyFunc ) {
2303
2263
GraalHPyHandleReference newHead = new GraalHPyHandleReference (pythonObject , ensureReferenceQueue (), dataPtr , destroyFunc );
2304
-
2305
- /*
2306
- * Get the current list and set null such that the cleaner thread cannot consume it while
2307
- * the main thread is updating the list.
2308
- */
2309
- GraalHPyHandleReferenceList refList = references .getAndSet (null );
2310
- refList .insert (newHead );
2311
-
2312
- /*
2313
- * Restore list, i.e., make it available to reference cleaner thread for consumption. The
2314
- * cleaner thread may have updated the value in the meantime but only with an empty list.
2315
- * So, this can be ignored and the cleaner thread is able to deal with that.
2316
- */
2317
- references .set (refList );
2264
+ references .getAndAccumulate (newHead , (prev , x ) -> {
2265
+ x .next = prev ;
2266
+ return x ;
2267
+ });
2318
2268
}
2319
2269
2320
2270
private ReferenceQueue <Object > ensureReferenceQueue () {
0 commit comments