Skip to content

Commit 9ecf016

Browse files
committed
[GR-75391] [GR-75392] Fix XDecrefPointerNode and PyGILState_Ensure/Release.
PullRequest: graalpython/4466
2 parents c2d28cf + 692248a commit 9ecf016

12 files changed

Lines changed: 292 additions & 143 deletions

File tree

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

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ typedef struct {
100100
// defined in 'unicodeobject.c'
101101
void unicode_dealloc(PyObject *unicode);
102102

103-
NO_INLINE void
103+
Py_LOCAL_SYMBOL NO_INLINE void
104104
graalpy_dealloc_stack_grow(PyThreadState *tstate)
105105
{
106106
size_t old_capacity;
@@ -119,7 +119,7 @@ graalpy_dealloc_stack_grow(PyThreadState *tstate)
119119
}
120120
}
121121

122-
void
122+
Py_LOCAL_SYMBOL void
123123
graalpy_dealloc_stack_push(PyThreadState *tstate, PyObject *op)
124124
{
125125
assert(tstate != NULL);
@@ -129,7 +129,7 @@ graalpy_dealloc_stack_push(PyThreadState *tstate, PyObject *op)
129129
tstate->graalpy_deallocating.items[tstate->graalpy_deallocating.len++] = op;
130130
}
131131

132-
void
132+
Py_LOCAL_SYMBOL void
133133
graalpy_dealloc_stack_pop(PyThreadState *tstate, PyObject *op)
134134
{
135135
assert(tstate != NULL);
@@ -574,26 +574,12 @@ void initialize_hashes();
574574
void _PyFloat_InitState(PyInterpreterState* state);
575575

576576
/*
577-
* This is used to allow Truffle to enter/leave the context on native threads
578-
* that were not created by NFI/Truffle/Java and thus not previously attached
579-
* to the context. See e.g. PyGILState_Ensure. This is used by some C
580-
* extensions to allow calling Python APIs from natively created threads. This
581-
* poses a problem if multiple contexts use the same library, since we cannot
582-
* know which context should be entered. CPython has the same problem (see
583-
* https://docs.python.org/3/c-api/init.html#bugs-and-caveats), in particular
584-
* the following quote:
585-
*
586-
* Furthermore, extensions (such as ctypes) using these APIs to allow calling
587-
* of Python code from non-Python created threads will probably be broken
588-
* when using sub-interpreters.
589-
*
590-
* If we try to use the same libpython for multiple contexts, we can only
591-
* behave in a similar (likely broken) way as CPython: natively created threads
592-
* that use the PyGIL_* APIs to allow calling into Python will attach to the
593-
* first interpreter that initialized the C API (and thus set the
594-
* TRUFFLE_CONTEXT pointer) only.
577+
* These functions allow Truffle to enter/leave the context on native threads
578+
* that were not created by Truffle/Java and thus do not have a previously
579+
* entered polyglot context. See e.g. PyGILState_Ensure.
595580
*/
596-
Py_LOCAL_SYMBOL TruffleContext* TRUFFLE_CONTEXT;
581+
Py_LOCAL_SYMBOL graalpy_attach_native_thread_func graalpy_attach_native_thread = NULL;
582+
Py_LOCAL_SYMBOL graalpy_detach_native_thread_func graalpy_detach_native_thread = NULL;
597583

598584
/*
599585
* This is only set during VM shutdown, so on the native side can only be used
@@ -603,10 +589,14 @@ Py_LOCAL_SYMBOL TruffleContext* TRUFFLE_CONTEXT;
603589
*/
604590
Py_LOCAL_SYMBOL int8_t *_graalpy_finalizing = NULL;
605591

606-
PyAPI_FUNC(PyThreadState **) initialize_graal_capi(void **builtin_closures, GCState *gc, PyThreadState *tstate) {
592+
PyAPI_FUNC(PyThreadState **) initialize_graal_capi(void **builtin_closures, GCState *gc,
593+
PyThreadState *tstate, graalpy_attach_native_thread_func attach_native_thread,
594+
graalpy_detach_native_thread_func detach_native_thread) {
607595
clock_t t = clock();
608596

609597
_PyGC_InitState(gc);
598+
graalpy_attach_native_thread = attach_native_thread;
599+
graalpy_detach_native_thread = detach_native_thread;
610600

611601
/*
612602
* Initializing all these global fields with pointers to different contexts

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@
6565
#error "don't know how to declare thread local variable"
6666
#endif
6767

68+
typedef int (*graalpy_attach_native_thread_func)(void);
69+
typedef void (*graalpy_detach_native_thread_func)(void);
70+
71+
#define GRAALPY_ATTACH_NATIVE_FAILED (-1)
72+
#define GRAALPY_ATTACH_NATIVE_OWNED 1
73+
#define GRAALPY_ATTACH_NATIVE_FOREIGN 2
74+
75+
extern graalpy_attach_native_thread_func graalpy_attach_native_thread;
76+
extern graalpy_detach_native_thread_func graalpy_detach_native_thread;
77+
6878
#ifdef MS_WINDOWS
6979
// define the below, otherwise windows' sdk defines complex to _complex and breaks us
7080
#define _COMPLEX_DEFINED
@@ -168,9 +178,9 @@ extern THREAD_LOCAL Py_LOCAL_SYMBOL PyThreadState *tstate_current;
168178
extern Py_LOCAL_SYMBOL int8_t *_graalpy_finalizing;
169179
#define graalpy_finalizing (_graalpy_finalizing != NULL && *_graalpy_finalizing)
170180

171-
void graalpy_dealloc_stack_grow(PyThreadState *tstate);
172-
void graalpy_dealloc_stack_push(PyThreadState *tstate, PyObject *op);
173-
void graalpy_dealloc_stack_pop(PyThreadState *tstate, PyObject *op);
181+
Py_LOCAL_SYMBOL void graalpy_dealloc_stack_grow(PyThreadState *tstate);
182+
Py_LOCAL_SYMBOL void graalpy_dealloc_stack_push(PyThreadState *tstate, PyObject *op);
183+
Py_LOCAL_SYMBOL void graalpy_dealloc_stack_pop(PyThreadState *tstate, PyObject *op);
174184

175185
#if (__linux__ && __GNU_LIBRARY__)
176186
#include <stdlib.h>

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

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
#include "capi.h"
3232
#include <trufflenfi.h>
3333

34-
extern TruffleContext* TRUFFLE_CONTEXT;
3534
static THREAD_LOCAL int graalpy_attached_thread = 0;
3635
static THREAD_LOCAL int graalpy_gilstate_counter = 0;
3736

@@ -2290,22 +2289,22 @@ PyGILState_Check(void)
22902289

22912290
return (tstate == gilstate_tss_get(runtime));
22922291
#else // GraalPy change
2293-
int attached = 0;
22942292
/*
22952293
* PyGILState_Check is allowed to be called from a new thread that didn't yet setup the GIL.
2296-
* If we don't attach the thread ourselves, the upcall will work because NFI will attach
2297-
* the thread automatically, but it won't create the context which would then break
2298-
* subsequent PyGILState_Ensure.
2294+
* We must attach it before calling into Java so the upcall has an entered polyglot context.
22992295
*/
2300-
if (TRUFFLE_CONTEXT) {
2301-
if ((*TRUFFLE_CONTEXT)->getTruffleEnv(TRUFFLE_CONTEXT) == NULL) {
2302-
(*TRUFFLE_CONTEXT)->attachCurrentThread(TRUFFLE_CONTEXT);
2303-
attached = 1;
2296+
int attached = 0;
2297+
if (graalpy_attach_native_thread) {
2298+
attached = graalpy_attach_native_thread();
2299+
if (attached == GRAALPY_ATTACH_NATIVE_FAILED) {
2300+
return 0;
23042301
}
2302+
} else {
2303+
return 0;
23052304
}
23062305
int ret = GraalPyPrivate_GILState_Check();
2307-
if (attached) {
2308-
(*TRUFFLE_CONTEXT)->detachCurrentThread(TRUFFLE_CONTEXT);
2306+
if (attached == GRAALPY_ATTACH_NATIVE_OWNED && graalpy_detach_native_thread) {
2307+
graalpy_detach_native_thread();
23092308
}
23102309
return ret;
23112310
#endif // GraalPy change
@@ -2362,13 +2361,18 @@ PyGILState_Ensure(void)
23622361

23632362
return has_gil ? PyGILState_LOCKED : PyGILState_UNLOCKED;
23642363
#else // GraalPy change
2365-
if (TRUFFLE_CONTEXT) {
2366-
if ((*TRUFFLE_CONTEXT)->getTruffleEnv(TRUFFLE_CONTEXT) == NULL) {
2367-
(*TRUFFLE_CONTEXT)->attachCurrentThread(TRUFFLE_CONTEXT);
2364+
if (!graalpy_attach_native_thread) {
2365+
Py_FatalError("PyGILState_Ensure called before GraalPy C API initialization");
2366+
}
2367+
if (!graalpy_attached_thread) {
2368+
int attached = graalpy_attach_native_thread();
2369+
if (attached == GRAALPY_ATTACH_NATIVE_FAILED) {
2370+
Py_FatalError("Could not attach native thread to the polyglot context");
2371+
} else if (attached == GRAALPY_ATTACH_NATIVE_OWNED) {
23682372
graalpy_attached_thread = 1;
23692373
}
2370-
graalpy_gilstate_counter++;
23712374
}
2375+
graalpy_gilstate_counter++;
23722376
return GraalPyPrivate_GILState_Ensure() ? PyGILState_UNLOCKED : PyGILState_LOCKED;
23732377
#endif // GraalPy change
23742378
}
@@ -2428,20 +2432,20 @@ PyGILState_Release(PyGILState_STATE oldstate)
24282432
if (oldstate == PyGILState_UNLOCKED) {
24292433
GraalPyPrivate_GILState_Release();
24302434
}
2431-
if (TRUFFLE_CONTEXT) {
2432-
graalpy_gilstate_counter--;
2433-
if (graalpy_gilstate_counter == 0 && graalpy_attached_thread) {
2434-
GraalPyPrivate_BeforeThreadDetach();
2435-
(*TRUFFLE_CONTEXT)->detachCurrentThread(TRUFFLE_CONTEXT);
2436-
graalpy_attached_thread = 0;
2437-
/*
2438-
* The thread state on the Java-side is cleared in GraalPyPrivate_BeforeThreadDetach.
2439-
* As part of that the tstate_current pointer should have been set to NULL to make
2440-
* sure to fetch a fresh pointer the next time we attach. Just to be sure, we clear
2441-
* it here too:
2442-
*/
2443-
tstate_current = NULL;
2435+
graalpy_gilstate_counter--;
2436+
if (graalpy_gilstate_counter == 0 && graalpy_attached_thread) {
2437+
GraalPyPrivate_BeforeThreadDetach();
2438+
if (graalpy_detach_native_thread) {
2439+
graalpy_detach_native_thread();
24442440
}
2441+
graalpy_attached_thread = 0;
2442+
/*
2443+
* The thread state on the Java-side is cleared in GraalPyPrivate_BeforeThreadDetach.
2444+
* As part of that the tstate_current pointer should have been set to NULL to make
2445+
* sure to fetch a fresh pointer the next time we attach. Just to be sure, we clear
2446+
* it here too:
2447+
*/
2448+
tstate_current = NULL;
24452449
}
24462450
#endif // GraalPy change
24472451
}

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

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2026, 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
@@ -167,36 +167,64 @@ class TestPyList(CPyExtTestCase):
167167
test_PyList_SetItem = CPyExtFunction(
168168
_reference_setitem,
169169
lambda: (
170-
(4, 0, 0),
171-
(4, 3, 5),
170+
(4, 0, 0, False),
171+
(4, 3, 5, False),
172+
(4, 0, 0, True),
173+
(4, 3, 5, True),
172174
),
173-
code='''PyObject* wrap_PyList_SetItem(Py_ssize_t capacity, Py_ssize_t idx, PyObject* new_item) {
175+
code='''PyObject* wrap_PyList_SetItem(Py_ssize_t capacity, Py_ssize_t idx, PyObject* new_item, int init_with_none) {
174176
PyObject *newList = PyList_New(capacity);
175177
Py_ssize_t i;
176178
for (i = 0; i < capacity; i++) {
177-
if (i == idx) {
178-
Py_INCREF(new_item);
179-
PyList_SetItem(newList, i, new_item);
180-
} else {
179+
if (init_with_none || i != idx) {
181180
Py_INCREF(Py_None);
182181
PyList_SetItem(newList, i, Py_None);
183182
}
184183
}
184+
Py_INCREF(new_item);
185+
PyList_SetItem(newList, idx, new_item);
185186
return newList;
186187
}
187188
''',
188189
resultspec="O",
189-
argspec='nnO',
190-
arguments=["Py_ssize_t capacity", "Py_ssize_t size", "PyObject* new_item"],
190+
argspec='nnOp',
191+
arguments=["Py_ssize_t capacity", "Py_ssize_t size", "PyObject* new_item", "int init_with_none"],
191192
callfunction="wrap_PyList_SetItem",
192193
cmpfunc=unhandled_error_compare
193194
)
194195

196+
test_native_list_setitem_decrefs_none = CPyExtFunction(
197+
lambda args: [args[0]],
198+
lambda: (
199+
("new item",),
200+
),
201+
code='''PyObject* wrap_native_list_setitem_decrefs_none(PyObject* new_item) {
202+
PyObject *list = PyList_New(1);
203+
if (list == NULL) {
204+
return NULL;
205+
}
206+
Py_INCREF(Py_None);
207+
PyList_SET_ITEM(list, 0, Py_None);
208+
if (PySequence_SetItem(list, 0, new_item) < 0) {
209+
Py_DECREF(list);
210+
return NULL;
211+
}
212+
return list;
213+
}
214+
''',
215+
resultspec="O",
216+
argspec='O',
217+
arguments=["PyObject* new_item"],
218+
callfunction="wrap_native_list_setitem_decrefs_none",
219+
cmpfunc=unhandled_error_compare
220+
)
221+
195222
test_PyList_SET_ITEM = CPyExtFunction(
196223
_wrap_list_fun(_reference_SET_ITEM),
197224
lambda: (
198225
([1,2,3,4], 0, _reference_SET_ITEM),
199226
([1,2,3,4], 3, _reference_SET_ITEM),
227+
([None], 0, 0),
200228
),
201229
code='''PyObject* wrap_PyList_SET_ITEM(PyObject* op, Py_ssize_t idx, PyObject* newitem) {
202230
Py_INCREF(newitem);

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ class TestPyThread(CPyExtTestCase):
8282
)
8383

8484

85-
# TODO(native-access) support ENV - thread attach/detach
86-
@unittest.skipIf(True, "Needs pthread")
85+
@unittest.skipIf(sys.platform == 'win32', "Needs pthread")
8786
class TestNativeThread(unittest.TestCase):
8887
def test_register_new_thread(self):
8988
TestThread = CPyExtType(

0 commit comments

Comments
 (0)