Skip to content

Commit 81d6738

Browse files
committed
Refactor C API context exit/finalization
1 parent a646396 commit 81d6738

File tree

4 files changed

+185
-96
lines changed

4 files changed

+185
-96
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ public RootCallTarget getDescriptorCallTarget(BuiltinMethodDescriptor descriptor
10041004
@Override
10051005
protected void exitContext(PythonContext context, ExitMode exitMode, int exitCode) {
10061006
if (context.getCApiContext() != null) {
1007-
context.getCApiContext().finalizeCapi();
1007+
context.getCApiContext().exitCApiContext();
10081008
}
10091009
if (!PythonOptions.WITHOUT_PLATFORM_ACCESS && !ImageInfo.inImageBuildtimeCode()) {
10101010
// Reset signal handlers back to what they were

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions;
8282
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.HandleContext;
8383
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonNode;
84-
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonObjectReference;
8584
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.ToPythonWrapperNode;
8685
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.CheckFunctionResultNode;
8786
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext;
@@ -124,6 +123,7 @@
124123
import com.oracle.truffle.api.TruffleFile;
125124
import com.oracle.truffle.api.TruffleLanguage.Env;
126125
import com.oracle.truffle.api.TruffleLogger;
126+
import com.oracle.truffle.api.TruffleSafepoint;
127127
import com.oracle.truffle.api.frame.VirtualFrame;
128128
import com.oracle.truffle.api.interop.ArityException;
129129
import com.oracle.truffle.api.interop.InteropLibrary;
@@ -429,19 +429,18 @@ public PythonAbstractObjectNativeWrapper getSingletonNativeWrapper(PythonAbstrac
429429
* must therefore be explicitly free'd. This method modifies the
430430
* {@link HandleContext#nativeStubLookup stub lookup table} but runs not guest code.
431431
*/
432-
private void freeSingletonNativeWrappers() {
432+
private void freeSingletonNativeWrappers(HandleContext handleContext) {
433433
CompilerAsserts.neverPartOfCompilation();
434434
// TODO(fa): this should not require the GIL (GR-51314)
435435
assert getContext().ownsGil();
436-
HandleContext nativeContext = getContext().nativeContext;
437436
for (int i = 0; i < singletonNativePtrs.length; i++) {
438437
PythonAbstractObjectNativeWrapper singletonNativeWrapper = singletonNativePtrs[i];
439438
singletonNativePtrs[i] = null;
440439
assert singletonNativeWrapper != null;
441440
assert getSingletonNativeWrapperIdx(singletonNativeWrapper.getDelegate()) != -1;
442441
assert !singletonNativeWrapper.isNative() || singletonNativeWrapper.getRefCount() == IMMORTAL_REFCNT;
443442
if (singletonNativeWrapper.ref != null) {
444-
CApiTransitions.nativeStubLookupRemove(nativeContext, singletonNativeWrapper.ref);
443+
CApiTransitions.nativeStubLookupRemove(handleContext, singletonNativeWrapper.ref);
445444
}
446445
PyTruffleObjectFree.releaseNativeWrapperUncached(singletonNativeWrapper);
447446
}
@@ -485,7 +484,7 @@ Object getOrCreateSmallInts() {
485484
* must therefore be explicitly free'd. This method modifies the
486485
* {@link HandleContext#nativeStubLookup stub lookup table} but runs not guest code.
487486
*/
488-
private void freeSmallInts() {
487+
private void freeSmallInts(HandleContext handleContext) {
489488
CompilerAsserts.neverPartOfCompilation();
490489
// TODO(fa): this should not require the GIL (GR-51314)
491490
assert getContext().ownsGil();
@@ -495,12 +494,11 @@ private void freeSmallInts() {
495494
FreeNode.executeUncached(nativeSmallIntsArray);
496495
nativeSmallIntsArray = null;
497496
}
498-
HandleContext nativeContext = getContext().nativeContext;
499497
for (PrimitiveNativeWrapper wrapper : primitiveNativeWrapperCache) {
500498
assert wrapper.isIntLike() && CApiGuards.isSmallLong(wrapper.getLong());
501499
assert !wrapper.isNative() || wrapper.getRefCount() == IMMORTAL_REFCNT;
502500
if (wrapper.ref != null) {
503-
CApiTransitions.nativeStubLookupRemove(nativeContext, wrapper.ref);
501+
CApiTransitions.nativeStubLookupRemove(handleContext, wrapper.ref);
504502
}
505503
PyTruffleObjectFree.releaseNativeWrapperUncached(wrapper);
506504
}
@@ -897,28 +895,63 @@ private void addNativeFinalizer(Env env, Object resetFunctionPointerArray) {
897895
}
898896
}
899897

900-
@TruffleBoundary
901-
@SuppressWarnings("try")
902-
public void finalizeCapi() {
898+
/**
899+
* This method is called to exit the context assuming a
900+
* {@link com.oracle.truffle.api.TruffleLanguage.ExitMode#NATURAL natural exit}. This means, it
901+
* is allowed to run guest code. Hence, we deallocate any reachable native object here since
902+
* they may have custom {@code tp_dealloc} functions.
903+
*/
904+
public void exitCApiContext() {
905+
CompilerAsserts.neverPartOfCompilation();
906+
/*
907+
* Polling the native reference queue is the only task we can do here because deallocating
908+
* objects may run arbitrary guest code that can again call into the interpreter.
909+
*/
910+
CApiTransitions.pollReferenceQueue();
911+
/*
912+
* Deallocating native storages and objects may run arbitrary guest code. So, we need to
913+
* ensure that the GIL is held.
914+
*/
915+
try (GilNode.UncachedAcquire ignored = GilNode.uncachedAcquire()) {
916+
CApiTransitions.deallocateNativeWeakRefs(getContext());
917+
}
918+
}
919+
920+
public void finalizeCApi() {
921+
CompilerAsserts.neverPartOfCompilation();
922+
HandleContext handleContext = getContext().nativeContext;
903923
/*
904924
* Disable reference queue polling because during finalization, we will free any known
905925
* allocated resources (e.g. native object stubs). Calling
906926
* 'CApiTransitions.pollReferenceQueue' could then lead to a double-free.
907927
*/
908-
CApiTransitions.disableReferenceQueuePolling(getContext().nativeContext);
909-
// TODO(fa): remove GIL acquisition (GR-51314)
910-
try (GilNode.UncachedAcquire gil = GilNode.uncachedAcquire()) {
911-
freeSmallInts();
912-
freeSingletonNativeWrappers();
913-
/*
914-
* Clear all remaining native object stubs. This must be done after the small int and
915-
* the singleton wrappers were cleared because they might also end up in the lookup
916-
* table and may otherwise be double-free'd.
917-
*/
918-
freeNativeObjectStubs();
919-
}
920-
if (pyDateTimeCAPICapsule != null) {
921-
PyDateTimeCAPIWrapper.destroyWrapper(pyDateTimeCAPICapsule);
928+
CApiTransitions.disableReferenceQueuePolling(handleContext);
929+
930+
TruffleSafepoint sp = TruffleSafepoint.getCurrent();
931+
boolean prev = sp.setAllowActions(false);
932+
try {
933+
// TODO(fa): remove GIL acquisition (GR-51314)
934+
try (GilNode.UncachedAcquire ignored = GilNode.uncachedAcquire()) {
935+
freeSmallInts(handleContext);
936+
freeSingletonNativeWrappers(handleContext);
937+
/*
938+
* Clear all remaining native object stubs. This must be done after the small int
939+
* and the singleton wrappers were cleared because they might also end up in the
940+
* lookup table and may otherwise be double-freed.
941+
*/
942+
CApiTransitions.freeNativeObjectStubs(handleContext);
943+
CApiTransitions.freeClassReplacements(handleContext);
944+
CApiTransitions.freeNativeStorages(handleContext);
945+
}
946+
if (pyDateTimeCAPICapsule != null) {
947+
PyDateTimeCAPIWrapper.destroyWrapper(pyDateTimeCAPICapsule);
948+
}
949+
// free all allocated PyMethodDef structures
950+
for (Object pyMethodDefPointer : methodDefinitions.values()) {
951+
PyMethodDefHelper.free(pyMethodDefPointer);
952+
}
953+
} finally {
954+
sp.setAllowActions(prev);
922955
}
923956
if (nativeFinalizerShutdownHook != null) {
924957
try {
@@ -929,10 +962,6 @@ public void finalizeCapi() {
929962
}
930963
}
931964
pyCFunctionWrappers.clear();
932-
// free all allocated PyMethodDef structures
933-
for (Object pyMethodDefPointer : methodDefinitions.values()) {
934-
PyMethodDefHelper.free(pyMethodDefPointer);
935-
}
936965
/*
937966
* If the static symbol cache is not null, then it is guaranteed that this context instance
938967
* was the exclusive user of it. We can now reset the state such that other contexts created
@@ -946,21 +975,6 @@ public void finalizeCapi() {
946975
}
947976
}
948977

949-
/**
950-
* Deallocates any native object stub that is still reachable via the
951-
* {@link HandleContext#nativeStubLookup lookup table}. This method modifies the
952-
* {@link HandleContext#nativeStubLookup stub lookup table} but runs not guest code.
953-
*/
954-
private void freeNativeObjectStubs() {
955-
HandleContext nativeContext = getContext().nativeContext;
956-
for (PythonObjectReference ref : nativeContext.nativeStubLookup) {
957-
if (ref != null) {
958-
CApiTransitions.nativeStubLookupRemove(nativeContext, ref);
959-
CApiTransitions.freeNativeStub(ref);
960-
}
961-
}
962-
}
963-
964978
@TruffleBoundary
965979
public Object initCApiModule(Node location, Object sharedLibrary, TruffleString initFuncName, ModuleSpec spec, InteropLibrary llvmInteropLib, CheckFunctionResultNode checkFunctionResultNode)
966980
throws UnsupportedMessageException, ArityException, UnsupportedTypeException, ImportException {

0 commit comments

Comments
 (0)