Skip to content

Commit 64e3159

Browse files
committed
Do not use NFI to call finalizeCAPI in VM shutdown hook
1 parent cdca2b3 commit 64e3159

File tree

2 files changed

+71
-16
lines changed
  • graalpython
    • com.oracle.graal.python.cext/src
    • com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi

2 files changed

+71
-16
lines changed

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ PyAPI_FUNC(Py_ssize_t) PyTruffle_bulk_DEALLOC(intptr_t ptrArray[], int64_t len)
456456

457457
/** to be used from Java code only and only at exit; calls _Py_Dealloc */
458458
PyAPI_FUNC(Py_ssize_t) PyTruffle_shutdown_bulk_DEALLOC(intptr_t ptrArray[], int64_t len) {
459-
/* some objects depends on others which might get deallocated in the process
459+
/* some objects depends on others which might get deallocated in the process
460460
of an earlier deallocation of the other object. To avoid double deallocations,
461461
we, temporarly, make all objects immortal artificially */
462462
for (int i = 0; i < len; i++) {
@@ -883,12 +883,41 @@ So we rebind them to no-ops when exiting.
883883
Py_ssize_t nop_GraalPy_get_PyObject_ob_refcnt(PyObject* obj) {
884884
return 100; // large dummy refcount
885885
}
886+
886887
void nop_GraalPy_set_PyObject_ob_refcnt(PyObject* obj, Py_ssize_t refcnt) {
887888
// do nothing
888889
}
889-
PyAPI_FUNC(void) finalizeCAPI() {
890-
GraalPy_get_PyObject_ob_refcnt = nop_GraalPy_get_PyObject_ob_refcnt;
891-
GraalPy_set_PyObject_ob_refcnt = nop_GraalPy_set_PyObject_ob_refcnt;
890+
891+
/*
892+
* This array contains pairs of variable address and "reset value".
893+
* The variable location is usually the address of a function pointer variable
894+
* and the reset value is a new value to set at VM shutdown.
895+
* For further explanation why this is required, see Java method
896+
* 'CApiContext.ensureCapiWasLoaded'.
897+
*
898+
* Array format: [ var_addr, reset_val, var_addr1, reset_val1, ..., NULL ]
899+
*
900+
* ATTENTION: If the structure of the array's content is changed, method
901+
* 'CApiContext.addNativeFinalizer' *MUST BE* adopted.
902+
*
903+
* ATTENTION: the array is expected to be NULL-terminated !
904+
*
905+
*/
906+
static int64_t reset_func_ptrs[] = {
907+
&GraalPy_get_PyObject_ob_refcnt,
908+
nop_GraalPy_get_PyObject_ob_refcnt,
909+
&GraalPy_set_PyObject_ob_refcnt,
910+
nop_GraalPy_set_PyObject_ob_refcnt,
911+
/* sentinel (required) */
912+
NULL
913+
};
914+
915+
/*
916+
* This function is called from Java during C API initialization to get the
917+
* pointer to array 'reset_func_pts'.
918+
*/
919+
PyAPI_FUNC(int64_t *) GraalPy_get_finalize_capi_pointer_array() {
920+
return reset_func_ptrs;
892921
}
893922

894923
static void unimplemented(const char* name) {

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

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
import com.oracle.truffle.api.TruffleLogger;
109109
import com.oracle.truffle.api.frame.VirtualFrame;
110110
import com.oracle.truffle.api.interop.ArityException;
111-
import com.oracle.truffle.api.interop.InteropException;
112111
import com.oracle.truffle.api.interop.InteropLibrary;
113112
import com.oracle.truffle.api.interop.TruffleObject;
114113
import com.oracle.truffle.api.interop.UnknownIdentifierException;
@@ -125,6 +124,8 @@
125124
import com.oracle.truffle.api.strings.TruffleString;
126125
import com.oracle.truffle.nfi.api.SignatureLibrary;
127126

127+
import sun.misc.Unsafe;
128+
128129
public final class CApiContext extends CExtContext {
129130

130131
public static final String LOGGER_CAPI_NAME = "capi";
@@ -593,10 +594,11 @@ public static CApiContext ensureCapiWasLoaded(Node node, PythonContext context,
593594
* is terminated by a signal, the context exit is skipped. For that case we set
594595
* up the shutdown hook.
595596
*/
596-
Object finalizeFunction = U.readMember(capiLibrary, "finalizeCAPI");
597-
Object finalizeSignature = env.parseInternal(Source.newBuilder(J_NFI_LANGUAGE, "():VOID", "exec").build()).call();
597+
Object finalizeFunction = U.readMember(capiLibrary, "GraalPy_get_finalize_capi_pointer_array");
598+
Object finalizeSignature = env.parseInternal(Source.newBuilder(J_NFI_LANGUAGE, "():POINTER", "exec").build()).call();
599+
Object resetFunctionPointerArray = SignatureLibrary.getUncached().call(finalizeSignature, finalizeFunction);
598600
try {
599-
cApiContext.addNativeFinalizer(env, finalizeFunction, finalizeSignature);
601+
cApiContext.addNativeFinalizer(env, resetFunctionPointerArray);
600602
} catch (RuntimeException e) {
601603
// This can happen when other languages restrict multithreading
602604
LOGGER.warning(() -> "didn't register a native finalizer due to: " + e.getMessage());
@@ -622,16 +624,40 @@ public static CApiContext ensureCapiWasLoaded(Node node, PythonContext context,
622624
return context.getCApiContext();
623625
}
624626

625-
private void addNativeFinalizer(Env env, Object finalizeFunction, Object finalizeSignature) {
626-
nativeFinalizerRunnable = () -> {
627+
/**
628+
* Registers a VM shutdown hook, that resets certain C API function to NOP functions to avoid
629+
* segfaults due to improper Python context shutdown. In particular, the shutdown hook reads
630+
* pairs of {@code variable address} and {@code reset value} provided in a native array and
631+
* writes the {@code reset value} to the {@code variable address}. Currently, this is used to
632+
* change the incref and decref upcall functions which would crash if they are called after the
633+
* Python context was closed. The format of the array is:
634+
*
635+
* <pre>
636+
* [ var_addr, reset_val, var_addr1, reset_val1, ..., NULL ]
637+
* </pre>
638+
*/
639+
private void addNativeFinalizer(Env env, Object resetFunctionPointerArray) {
640+
final Unsafe unsafe = getContext().getUnsafe();
641+
InteropLibrary lib = InteropLibrary.getUncached(resetFunctionPointerArray);
642+
if (!lib.isNull(resetFunctionPointerArray) && lib.isPointer(resetFunctionPointerArray)) {
627643
try {
628-
SignatureLibrary.getUncached().call(finalizeSignature, finalizeFunction);
629-
} catch (InteropException e) {
630-
throw CompilerDirectives.shouldNotReachHere(e);
644+
long lresetFunctionPointerArray = lib.asPointer(resetFunctionPointerArray);
645+
nativeFinalizerRunnable = () -> {
646+
long resetFunctionPointerLocation;
647+
long curElemAddr = lresetFunctionPointerArray;
648+
while ((resetFunctionPointerLocation = unsafe.getLong(curElemAddr)) != 0) {
649+
curElemAddr += Long.BYTES;
650+
long replacingFunctionPointer = unsafe.getLong(curElemAddr);
651+
unsafe.putAddress(resetFunctionPointerLocation, replacingFunctionPointer);
652+
curElemAddr += Long.BYTES;
653+
}
654+
};
655+
nativeFinalizerShutdownHook = env.newTruffleThreadBuilder(nativeFinalizerRunnable).build();
656+
Runtime.getRuntime().addShutdownHook(nativeFinalizerShutdownHook);
657+
} catch (UnsupportedMessageException e) {
658+
throw new RuntimeException(e);
631659
}
632-
};
633-
nativeFinalizerShutdownHook = env.newTruffleThreadBuilder(nativeFinalizerRunnable).build();
634-
Runtime.getRuntime().addShutdownHook(nativeFinalizerShutdownHook);
660+
}
635661
}
636662

637663
@TruffleBoundary

0 commit comments

Comments
 (0)