Skip to content

Commit c79e20e

Browse files
committed
[GR-46577] Support calling python from natively created threads
PullRequest: graalpython/2835
2 parents b914212 + 89af5d5 commit c79e20e

File tree

12 files changed

+416
-328
lines changed

12 files changed

+416
-328
lines changed

graalpython/com.oracle.graal.python.cext/include/pystate.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
1+
/* Copyright (c) 2018, 2023, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2020 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -97,9 +97,8 @@ PyAPI_FUNC(PyFrameObject*) PyThreadState_GetFrame(PyThreadState *tstate);
9797
PyAPI_FUNC(uint64_t) PyThreadState_GetID(PyThreadState *tstate);
9898
#endif
9999

100-
typedef
101-
enum {PyGILState_LOCKED, PyGILState_UNLOCKED}
102-
PyGILState_STATE;
100+
/* GraalVM change: we need more state bits */
101+
typedef int PyGILState_STATE;
103102

104103

105104
/* Ensure that the current thread is ready to call the Python

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,14 +1729,6 @@ PyAPI_FUNC(PyObject*) PyFrozenSet_New(PyObject* a) {
17291729
PyAPI_FUNC(int) PyGILState_Check() {
17301730
return GraalPyGILState_Check();
17311731
}
1732-
#undef PyGILState_Ensure
1733-
PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure() {
1734-
return GraalPyGILState_Ensure();
1735-
}
1736-
#undef PyGILState_Release
1737-
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE a) {
1738-
GraalPyGILState_Release(a);
1739-
}
17401732
#undef PyImport_GetModuleDict
17411733
PyAPI_FUNC(PyObject*) PyImport_GetModuleDict() {
17421734
return GraalPyImport_GetModuleDict();
@@ -2213,6 +2205,14 @@ PyAPI_FUNC(int) PyTraceMalloc_Track(unsigned int a, uintptr_t b, size_t c) {
22132205
PyAPI_FUNC(int) PyTraceMalloc_Untrack(unsigned int a, uintptr_t b) {
22142206
return GraalPyTraceMalloc_Untrack(a, b);
22152207
}
2208+
#undef PyTruffleGILState_Ensure
2209+
PyAPI_FUNC(int) PyTruffleGILState_Ensure() {
2210+
return GraalPyTruffleGILState_Ensure();
2211+
}
2212+
#undef PyTruffleGILState_Release
2213+
PyAPI_FUNC(void) PyTruffleGILState_Release() {
2214+
GraalPyTruffleGILState_Release();
2215+
}
22162216
#undef PyTruffle_Debug
22172217
PyAPI_FUNC(int) PyTruffle_Debug(void* a) {
22182218
return GraalPyTruffle_Debug(a);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ typedef struct {
170170
BUILTIN(PyFrame_New, PyFrameObject*, PyThreadState*, PyCodeObject*, PyObject*, PyObject*) \
171171
BUILTIN(PyFrozenSet_New, PyObject*, PyObject*) \
172172
BUILTIN(PyGILState_Check, int) \
173-
BUILTIN(PyGILState_Ensure, PyGILState_STATE) \
174-
BUILTIN(PyGILState_Release, void, PyGILState_STATE) \
175173
BUILTIN(PyImport_GetModuleDict, PyObject*) \
176174
BUILTIN(PyImport_Import, PyObject*, PyObject*) \
177175
BUILTIN(PyImport_ImportModule, PyObject*, const char*) \
@@ -314,6 +312,8 @@ typedef struct {
314312
BUILTIN(PyTruffleErr_GetExcInfo, PyObject*) \
315313
BUILTIN(PyTruffleErr_WarnExplicit, PyObject*, PyObject*, PyObject*, PyObject*, int, PyObject*, PyObject*) \
316314
BUILTIN(PyTruffleFloat_AsDouble, double, PyObject*) \
315+
BUILTIN(PyTruffleGILState_Ensure, int) \
316+
BUILTIN(PyTruffleGILState_Release, void) \
317317
BUILTIN(PyTruffleHash_InitSecret, void, void*) \
318318
BUILTIN(PyTruffleLong_AsPrimitive, long, PyObject*, int, long) \
319319
BUILTIN(PyTruffleLong_FromString, PyObject*, const char*, int, int) \

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,15 @@ int PyState_RemoveModule(struct PyModuleDef* def) {
115115
// TODO(fa): implement
116116
return 0;
117117
}
118+
119+
// This function has a different implementation on NFI in capi_native.c
120+
PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure() {
121+
return GraalPyTruffleGILState_Ensure();
122+
}
123+
124+
// This function has a different implementation on NFI in capi_native.c
125+
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE state) {
126+
if (state) {
127+
GraalPyTruffleGILState_Release();
128+
}
129+
}

graalpython/com.oracle.graal.python.jni/src/capi_forwards.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ void unimplemented(const char* name) {
737737
#undef PyTruffleErr_WarnExplicit
738738
#undef PyTruffleFloat_AsDouble
739739
#undef PyTruffleFrame_New
740+
#undef PyTruffleGILState_Ensure
741+
#undef PyTruffleGILState_Release
740742
#undef PyTruffleHash_InitSecret
741743
#undef PyTruffleLong_AsPrimitive
742744
#undef PyTruffleLong_FromString
@@ -2564,18 +2566,11 @@ PyAPI_FUNC(int) PyGILState_Check() {
25642566
int result = (int) GraalPyGILState_Check();
25652567
return result;
25662568
}
2567-
PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure() {
2568-
PyGILState_STATE result = (PyGILState_STATE) GraalPyGILState_Ensure();
2569-
return result;
2570-
}
25712569
PyThreadState* (*__target__PyGILState_GetThisThreadState)() = NULL;
25722570
PyAPI_FUNC(PyThreadState*) PyGILState_GetThisThreadState() {
25732571
PyThreadState* result = (PyThreadState*) __target__PyGILState_GetThisThreadState();
25742572
return result;
25752573
}
2576-
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE a) {
2577-
GraalPyGILState_Release(a);
2578-
}
25792574
PyObject* (*__target__PyGen_New)(PyFrameObject*) = NULL;
25802575
PyAPI_FUNC(PyObject*) PyGen_New(PyFrameObject* a) {
25812576
PyObject* result = (PyObject*) __target__PyGen_New(a);
@@ -3955,6 +3950,13 @@ PyAPI_FUNC(int) PyTraceMalloc_Untrack(unsigned int a, uintptr_t b) {
39553950
PyAPI_FUNC(PyFrameObject*) PyTruffleFrame_New(PyThreadState* a, PyCodeObject* b, PyObject* c, PyObject* d) {
39563951
unimplemented("PyTruffleFrame_New"); exit(-1);
39573952
}
3953+
PyAPI_FUNC(int) PyTruffleGILState_Ensure() {
3954+
int result = (int) GraalPyTruffleGILState_Ensure();
3955+
return result;
3956+
}
3957+
PyAPI_FUNC(void) PyTruffleGILState_Release() {
3958+
GraalPyTruffleGILState_Release();
3959+
}
39583960
PyAPI_FUNC(int) PyTruffle_Debug(void* a) {
39593961
int result = (int) GraalPyTruffle_Debug(a);
39603962
return result;

graalpython/com.oracle.graal.python.jni/src/capi_native.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@
5050
#include <pycore_pymem.h>
5151
#include <pycore_moduleobject.h>
5252

53+
#include <trufflenfi.h>
54+
5355
#include <stdio.h>
5456
#include <stdint.h>
5557
#include <time.h>
5658

5759
#define MUST_INLINE __attribute__((always_inline)) inline
5860

61+
TruffleContext* TRUFFLE_CONTEXT;
62+
5963
#define PY_TYPE_OBJECTS(OBJECT) \
6064
OBJECT(PyAsyncGen_Type, async_generator) \
6165
OBJECT(PyBaseObject_Type, object) \
@@ -299,14 +303,16 @@ int initNativeForwardCalled = 0;
299303
/**
300304
* Returns 1 on success, 0 on error (if it was already initialized).
301305
*/
302-
PyAPI_FUNC(int) initNativeForward(void* (*getBuiltin)(int), void* (*getAPI)(const char*), void* (*getType)(const char*), void (*setTypeStore)(const char*, void*), void (*initialize_native_locations)(void*,void*,void*)) {
306+
PyAPI_FUNC(int) initNativeForward(TruffleEnv* env, void* (*getBuiltin)(int), void* (*getAPI)(const char*), void* (*getType)(const char*), void (*setTypeStore)(const char*, void*), void (*initialize_native_locations)(void*,void*,void*)) {
303307
if (initNativeForwardCalled) {
304308
return 0;
305309
}
306310
initNativeForwardCalled = 1;
307311
clock_t t;
308312
t = clock();
309313

314+
TRUFFLE_CONTEXT = (*env)->getTruffleContext(env);
315+
310316
#define SET_TYPE_OBJECT_STORE(NAME, TYPENAME) setTypeStore(#TYPENAME, (void*) &NAME);
311317
PY_TYPE_OBJECTS(SET_TYPE_OBJECT_STORE)
312318
#undef SET_TYPE_OBJECT_STORE
@@ -459,6 +465,31 @@ void finalizeCAPI() {
459465
GraalPy_set_PyObject_ob_refcnt = nop_GraalPy_set_PyObject_ob_refcnt;
460466
}
461467

468+
#define _PYGILSTATE_LOCKED 0x1
469+
#define _PYGILSTATE_ATTACHED 0x2
470+
471+
PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure() {
472+
int result = 0;
473+
if ((*TRUFFLE_CONTEXT)->getTruffleEnv(TRUFFLE_CONTEXT) == NULL) {
474+
(*TRUFFLE_CONTEXT)->attachCurrentThread(TRUFFLE_CONTEXT);
475+
result |= _PYGILSTATE_ATTACHED;
476+
}
477+
int locked = PyTruffleGILState_Ensure();
478+
if (locked) {
479+
result |= _PYGILSTATE_LOCKED;
480+
}
481+
return result;
482+
}
483+
484+
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE state) {
485+
if (state & _PYGILSTATE_LOCKED) {
486+
PyTruffleGILState_Release();
487+
}
488+
if (state & _PYGILSTATE_ATTACHED) {
489+
(*TRUFFLE_CONTEXT)->detachCurrentThread(TRUFFLE_CONTEXT);
490+
}
491+
}
492+
462493
PyObject* PyTuple_Pack(Py_ssize_t n, ...) {
463494
va_list vargs;
464495
va_start(vargs, n);

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -36,8 +36,9 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import threading
3940

40-
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
41+
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare, CPyExtType
4142

4243
__dir__ = __file__.rpartition("/")[0]
4344

@@ -83,3 +84,43 @@ def compile_module(self, name):
8384
callfunction="test_PyThread_tss_functions",
8485
cmpfunc=unhandled_error_compare
8586
)
87+
88+
89+
class TestNativeThread:
90+
def test_register_new_thread(self):
91+
TestThread = CPyExtType(
92+
name="TestThread",
93+
includes="#include <pthread.h>",
94+
code=r'''
95+
void* thread_entrypoint(void* arg) {
96+
PyObject* callable = (PyObject*)arg;
97+
PyGILState_STATE gstate;
98+
gstate = PyGILState_Ensure();
99+
if (!PyObject_CallNoArgs(callable)) {
100+
PyErr_WriteUnraisable(callable);
101+
}
102+
PyGILState_Release(gstate);
103+
return NULL;
104+
}
105+
PyObject* run_in_thread(PyObject* self, PyObject* callable) {
106+
Py_BEGIN_ALLOW_THREADS;
107+
pthread_t thread;
108+
pthread_create(&thread, NULL, thread_entrypoint, callable);
109+
pthread_join(thread, NULL);
110+
Py_END_ALLOW_THREADS;
111+
Py_RETURN_NONE;
112+
}
113+
''',
114+
tp_methods='{"run_in_thread", (PyCFunction)run_in_thread, METH_O | METH_STATIC, ""}'
115+
)
116+
117+
thread = None
118+
119+
def callable():
120+
nonlocal thread
121+
thread = threading.current_thread()
122+
123+
TestThread.run_in_thread(callable)
124+
125+
assert thread
126+
assert thread is not threading.current_thread()

0 commit comments

Comments
 (0)