Skip to content

Commit 4c647d4

Browse files
committed
Implement PyThreadState_SetAsyncExc
1 parent ea82729 commit 4c647d4

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_pystate.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,47 @@ def test_PyThreadState_GetFrame(self):
6868
tp_methods='{"get_frame", (PyCFunction)get_frame, METH_NOARGS | METH_STATIC, NULL}',
6969
)
7070
assert Tester.get_frame() is sys._getframe(0)
71+
72+
# This seems to get the native extensions into some inconsistent state on GraalPy, giving:
73+
# refcnt below zero during managed adjustment for 0000aaae18fca780 (9 0000000000000009 - 10)
74+
def ignored_test_SetAsyncExc(self):
75+
SetAsyncExcCaller = CPyExtType(
76+
"SetAsyncExcCaller",
77+
"""
78+
static PyObject* trigger_ex(PyObject *cls, PyObject *args) {
79+
long thread_id;
80+
PyObject *ex;
81+
if (!PyArg_ParseTuple(args, "lO", &thread_id, &ex)) {
82+
return NULL;
83+
}
84+
PyThreadState_SetAsyncExc(thread_id, ex);
85+
return PyLong_FromLong(42);
86+
}
87+
""",
88+
tp_methods='{"trigger_ex", (PyCFunction)trigger_ex, METH_VARARGS | METH_STATIC, ""}',
89+
)
90+
91+
import threading
92+
start = threading.Barrier(2, timeout=20)
93+
94+
caught_ex = None
95+
def other_thread():
96+
try:
97+
start.wait() # ensure we are in the try, before raising
98+
r = 0
99+
for i in range(1, 1000000000):
100+
for j in range(i, 1000000000):
101+
r += j / i
102+
except Exception as e:
103+
nonlocal caught_ex
104+
caught_ex = e
105+
106+
107+
t = threading.Thread(target=other_thread)
108+
t.start()
109+
110+
start.wait()
111+
SetAsyncExcCaller.trigger_ex(t.ident, Exception("test my message"))
112+
t.join()
113+
114+
assert "test my message" in str(caught_ex)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextPyStateBuiltins.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@
5050
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_ssize_t;
5151
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Void;
5252

53+
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode;
5354
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin;
5455
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiNullaryBuiltinNode;
5556
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode;
5657
import com.oracle.graal.python.builtins.objects.PNone;
58+
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
5759
import com.oracle.graal.python.builtins.objects.cext.capi.PThreadState;
60+
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor;
5861
import com.oracle.graal.python.builtins.objects.dict.PDict;
5962
import com.oracle.graal.python.builtins.objects.frame.PFrame;
6063
import com.oracle.graal.python.builtins.objects.ints.PInt;
64+
import com.oracle.graal.python.builtins.objects.thread.PThread;
65+
import com.oracle.graal.python.nodes.PRaiseNode;
66+
import com.oracle.graal.python.nodes.PRootNode;
6167
import com.oracle.graal.python.nodes.frame.GetCurrentFrameRef;
6268
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
6369
import com.oracle.graal.python.nodes.util.CannotCastException;
@@ -67,12 +73,15 @@
6773
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
6874
import com.oracle.graal.python.util.OverflowException;
6975
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
76+
import com.oracle.truffle.api.ThreadLocalAction;
77+
import com.oracle.truffle.api.TruffleLogger;
7078
import com.oracle.truffle.api.dsl.Bind;
7179
import com.oracle.truffle.api.dsl.Cached;
7280
import com.oracle.truffle.api.dsl.Specialization;
7381
import com.oracle.truffle.api.interop.InteropLibrary;
7482
import com.oracle.truffle.api.library.CachedLibrary;
7583
import com.oracle.truffle.api.nodes.Node;
84+
import com.oracle.truffle.api.nodes.RootNode;
7685

7786
public final class PythonCextPyStateBuiltins {
7887

@@ -137,6 +146,43 @@ PDict get(@Cached PythonObjectFactory factory) {
137146
}
138147
}
139148

149+
@CApiBuiltin(ret = Int, args = {ArgDescriptor.UNSIGNED_LONG, ArgDescriptor.PyObjectTransfer}, call = Direct)
150+
abstract static class PyThreadState_SetAsyncExc extends CApiBinaryBuiltinNode {
151+
public static final TruffleLogger LOGGER = CApiContext.getLogger(PyThreadState_SetAsyncExc.class);
152+
153+
@Specialization
154+
@TruffleBoundary
155+
int doIt(long id, Object exceptionObject) {
156+
LOGGER.warning("The application uses PyThreadState_SetAsyncExc, which is not reliably supported by GraalPy");
157+
for (Thread thread : getContext().getThreads()) {
158+
if (PThread.getThreadId(thread) == id) {
159+
// We do not want to raise in some internal code, it could corrupt internal data
160+
// structures.
161+
ThreadLocalAction action = new ThreadLocalAction(true, false) {
162+
int missedCount = 0;
163+
164+
@Override
165+
protected void perform(Access access) {
166+
Node location = access.getLocation();
167+
if (location != null) {
168+
RootNode rootNode = location.getRootNode();
169+
if (rootNode instanceof PRootNode && !rootNode.isInternal()) {
170+
throw PRaiseNode.raiseExceptionObject(null, exceptionObject);
171+
}
172+
}
173+
if (missedCount++ < 20) {
174+
getContext().getEnv().submitThreadLocal(new Thread[]{thread}, this);
175+
}
176+
}
177+
};
178+
getContext().getEnv().submitThreadLocal(new Thread[]{thread}, action);
179+
return 1;
180+
}
181+
}
182+
return 0;
183+
}
184+
}
185+
140186
@CApiBuiltin(ret = PyFrameObjectTransfer, args = {PyThreadState}, call = Direct)
141187
abstract static class PyThreadState_GetFrame extends CApiUnaryBuiltinNode {
142188
@Specialization

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,6 @@ public final class CApiFunction {
879879
@CApiBuiltin(name = "PyThreadState_LeaveTracing", ret = Void, args = {PyThreadState}, call = NotImplemented)
880880
@CApiBuiltin(name = "PyThreadState_New", ret = PyThreadState, args = {PyInterpreterState}, call = NotImplemented)
881881
@CApiBuiltin(name = "PyThreadState_Next", ret = PyThreadState, args = {PyThreadState}, call = NotImplemented)
882-
@CApiBuiltin(name = "PyThreadState_SetAsyncExc", ret = Int, args = {UNSIGNED_LONG, PyObject}, call = NotImplemented)
883882
@CApiBuiltin(name = "PyThreadState_Swap", ret = PyThreadState, args = {PyThreadState}, call = NotImplemented)
884883
@CApiBuiltin(name = "PyThread_GetInfo", ret = PyObject, args = {}, call = NotImplemented)
885884
@CApiBuiltin(name = "PyThread_ReInitTLS", ret = Void, args = {}, call = NotImplemented)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -363,7 +363,7 @@ public void run() {
363363
* Some other thread will run and eventually get another gil release
364364
* request.
365365
*/
366-
ctx.getEnv().submitThreadLocal(new Thread[]{gilOwner}, new ThreadLocalAction(false, false) {
366+
ctx.getEnv().submitThreadLocal(new Thread[]{gilOwner}, new ThreadLocalAction(true, false) {
367367
@Override
368368
protected void perform(ThreadLocalAction.Access access) {
369369
// it may happen that we request a GIL release and no thread is

0 commit comments

Comments
 (0)