40
40
*/
41
41
package com .oracle .graal .python .runtime ;
42
42
43
+ import java .lang .ref .PhantomReference ;
44
+ import java .lang .ref .Reference ;
45
+ import java .lang .ref .ReferenceQueue ;
43
46
import java .lang .ref .WeakReference ;
44
47
import java .util .Arrays ;
48
+ import java .util .concurrent .ConcurrentHashMap ;
45
49
import java .util .concurrent .ConcurrentLinkedQueue ;
50
+ import java .util .concurrent .ConcurrentMap ;
46
51
import java .util .concurrent .Executors ;
47
52
import java .util .concurrent .ScheduledExecutorService ;
48
53
import java .util .concurrent .ThreadFactory ;
64
69
import com .oracle .graal .python .util .Supplier ;
65
70
import com .oracle .truffle .api .CompilerAsserts ;
66
71
import com .oracle .truffle .api .CompilerDirectives ;
72
+ import com .oracle .truffle .api .CompilerDirectives .TruffleBoundary ;
67
73
import com .oracle .truffle .api .RootCallTarget ;
68
74
import com .oracle .truffle .api .TruffleLanguage ;
75
+ import com .oracle .truffle .api .TruffleLogger ;
69
76
import com .oracle .truffle .api .frame .VirtualFrame ;
70
77
import com .oracle .truffle .api .profiles .BranchProfile ;
71
78
@@ -127,6 +134,7 @@ public final void execute(PythonContext context) {
127
134
}
128
135
129
136
private final ScheduledExecutorService executorService = Executors .newScheduledThreadPool (4 , new ThreadFactory () {
137
+ @ Override
130
138
public Thread newThread (Runnable r ) {
131
139
Thread t = Executors .defaultThreadFactory ().newThread (r );
132
140
t .setDaemon (true );
@@ -147,6 +155,7 @@ public AsyncRunnable(Supplier<AsyncAction> actionSupplier) {
147
155
this .actionSupplier = actionSupplier ;
148
156
}
149
157
158
+ @ Override
150
159
public void run () {
151
160
AsyncAction asyncAction = actionSupplier .get ();
152
161
if (asyncAction != null ) {
@@ -292,4 +301,118 @@ private void processAsyncActions() {
292
301
public void shutdown () {
293
302
executorService .shutdownNow ();
294
303
}
304
+
305
+ public static class SharedFinalizer {
306
+ private static final TruffleLogger LOGGER = PythonLanguage .getLogger (SharedFinalizer .class );
307
+
308
+ private final PythonContext pythonContext ;
309
+ private final ReferenceQueue <Object > queue = new ReferenceQueue <>();
310
+
311
+ /**
312
+ * This is a Set of references to keep them alive after their gc collected referents.
313
+ */
314
+ private final ConcurrentMap <FinalizableReference , FinalizableReference > liveReferencesSet = new ConcurrentHashMap <>();
315
+
316
+ public SharedFinalizer (PythonContext context ) {
317
+ this .pythonContext = context ;
318
+ }
319
+
320
+ /**
321
+ * Finalizable references is a utility class for freeing resources that {@link Runtime#gc()}
322
+ * is unaware of, such as of heap allocation through native interface. Resources that can be
323
+ * freed with {@link Runtime#gc()} should not extend this class.
324
+ */
325
+ public abstract static class FinalizableReference extends PhantomReference <Object > {
326
+ private final Object reference ;
327
+ private boolean released ;
328
+
329
+ public FinalizableReference (Object referent , Object reference , SharedFinalizer sharedFinalizer ) {
330
+ super (referent , sharedFinalizer .queue );
331
+ assert reference != null ;
332
+ this .reference = reference ;
333
+ addLiveReference (sharedFinalizer , this );
334
+ }
335
+
336
+ /**
337
+ * We'll keep a reference for the FinalizableReference object until the async handler
338
+ * schedule the collect process.
339
+ */
340
+ @ TruffleBoundary
341
+ private static void addLiveReference (SharedFinalizer sharedFinalizer , FinalizableReference ref ) {
342
+ sharedFinalizer .liveReferencesSet .put (ref , ref );
343
+ }
344
+
345
+ /**
346
+ *
347
+ * @return the undelying reference which is usually a native pointer.
348
+ */
349
+ public final Object getReference () {
350
+ return reference ;
351
+ }
352
+
353
+ public final boolean isReleased () {
354
+ return released ;
355
+ }
356
+
357
+ /**
358
+ * Mark the FinalizableReference as freed in case it has been freed elsewhare. This will
359
+ * avoid double-freeing the reference.
360
+ */
361
+ public final void markReleased () {
362
+ this .released = true ;
363
+ }
364
+
365
+ /**
366
+ * This implements the proper way to free the allocated resources associated with the
367
+ * reference.
368
+ */
369
+ public abstract AsyncHandler .AsyncAction release ();
370
+ }
371
+
372
+ static class SharedFinalizerErrorCallback implements AsyncHandler .AsyncAction {
373
+
374
+ private final Exception exception ;
375
+ private final FinalizableReference referece ; // problematic reference
376
+
377
+ SharedFinalizerErrorCallback (FinalizableReference referece , Exception e ) {
378
+ this .exception = e ;
379
+ this .referece = referece ;
380
+ }
381
+
382
+ @ Override
383
+ public void execute (PythonContext context ) {
384
+ LOGGER .severe (String .format ("Error during async action for %s caused by %s" , referece .getClass ().getSimpleName (), exception .getMessage ()));
385
+ }
386
+ }
387
+
388
+ /**
389
+ * We register the Async action once on the first encounter of a creation of
390
+ * {@link FinalizableReference}. This will reduce unnecessary Async thread load when there
391
+ * isn't any enqueued references.
392
+ */
393
+ public void registerAsyncAction () {
394
+ pythonContext .registerAsyncAction (() -> {
395
+ Reference <? extends Object > reference = null ;
396
+ try {
397
+ reference = queue .remove ();
398
+ } catch (InterruptedException e ) {
399
+ Thread .currentThread ().interrupt ();
400
+ }
401
+ if (reference instanceof FinalizableReference ) {
402
+ FinalizableReference object = (FinalizableReference ) reference ;
403
+ try {
404
+ liveReferencesSet .remove (object );
405
+ if (object .isReleased ()) {
406
+ return null ;
407
+ }
408
+ return object .release ();
409
+ } catch (Exception e ) {
410
+ return new SharedFinalizerErrorCallback (object , e );
411
+ }
412
+ }
413
+ return null ;
414
+ });
415
+
416
+ }
417
+ }
295
418
}
0 commit comments