Skip to content

Commit 8608723

Browse files
committed
Context's shared finalizer
1 parent 2cc8347 commit 8608723

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ public void postInitialize() {
531531
builtin.postInitialize(this);
532532
}
533533

534+
getContext().getSharedFinalizer().registerAsyncAction();
534535
initialized = true;
535536
}
536537
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/hpy/GraalHPyContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,11 +865,11 @@ static final class GraalHPyHandleReference extends PhantomReference<Object> {
865865
private final Object nativeSpace;
866866
private final Object destroyFunc;
867867

868-
public GraalHPyHandleReference(int id, Object referent, ReferenceQueue<Object> q, Object nativeSpace, Object desctroyFunc) {
868+
public GraalHPyHandleReference(int id, Object referent, ReferenceQueue<Object> q, Object nativeSpace, Object destroyFunc) {
869869
super(referent, q);
870870
this.id = id;
871871
this.nativeSpace = nativeSpace;
872-
this.destroyFunc = desctroyFunc;
872+
this.destroyFunc = destroyFunc;
873873
}
874874

875875
public Object getNativeSpace() {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/AsyncHandler.java

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,14 @@
4040
*/
4141
package com.oracle.graal.python.runtime;
4242

43+
import java.lang.ref.PhantomReference;
44+
import java.lang.ref.Reference;
45+
import java.lang.ref.ReferenceQueue;
4346
import java.lang.ref.WeakReference;
4447
import java.util.Arrays;
48+
import java.util.concurrent.ConcurrentHashMap;
4549
import java.util.concurrent.ConcurrentLinkedQueue;
50+
import java.util.concurrent.ConcurrentMap;
4651
import java.util.concurrent.Executors;
4752
import java.util.concurrent.ScheduledExecutorService;
4853
import java.util.concurrent.ThreadFactory;
@@ -64,8 +69,10 @@
6469
import com.oracle.graal.python.util.Supplier;
6570
import com.oracle.truffle.api.CompilerAsserts;
6671
import com.oracle.truffle.api.CompilerDirectives;
72+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
6773
import com.oracle.truffle.api.RootCallTarget;
6874
import com.oracle.truffle.api.TruffleLanguage;
75+
import com.oracle.truffle.api.TruffleLogger;
6976
import com.oracle.truffle.api.frame.VirtualFrame;
7077
import com.oracle.truffle.api.profiles.BranchProfile;
7178

@@ -127,6 +134,7 @@ public final void execute(PythonContext context) {
127134
}
128135

129136
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4, new ThreadFactory() {
137+
@Override
130138
public Thread newThread(Runnable r) {
131139
Thread t = Executors.defaultThreadFactory().newThread(r);
132140
t.setDaemon(true);
@@ -147,6 +155,7 @@ public AsyncRunnable(Supplier<AsyncAction> actionSupplier) {
147155
this.actionSupplier = actionSupplier;
148156
}
149157

158+
@Override
150159
public void run() {
151160
AsyncAction asyncAction = actionSupplier.get();
152161
if (asyncAction != null) {
@@ -292,4 +301,118 @@ private void processAsyncActions() {
292301
public void shutdown() {
293302
executorService.shutdownNow();
294303
}
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+
}
295418
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ private static final class AtExitHook {
256256
// The context-local resources
257257
private final PosixResources resources;
258258
private final AsyncHandler handler;
259+
private final AsyncHandler.SharedFinalizer sharedFinalizer;
259260

260261
// decides if we run the async weakref callbacks and destructors
261262
private boolean gcEnabled = true;
@@ -272,6 +273,7 @@ public PythonContext(PythonLanguage language, TruffleLanguage.Env env, PythonCor
272273
this.env = env;
273274
this.resources = new PosixResources();
274275
this.handler = new AsyncHandler(this);
276+
this.sharedFinalizer = new AsyncHandler.SharedFinalizer(this);
275277
this.optionValues = PythonOptions.createOptionValuesStorage(env);
276278
this.resources.setEnv(env);
277279
this.in = env.in();
@@ -1089,4 +1091,8 @@ public boolean isGcEnabled() {
10891091
public void setGcEnabled(boolean flag) {
10901092
gcEnabled = flag;
10911093
}
1094+
1095+
public AsyncHandler.SharedFinalizer getSharedFinalizer() {
1096+
return sharedFinalizer;
1097+
}
10921098
}

0 commit comments

Comments
 (0)