Skip to content

Commit 33d13f7

Browse files
committed
Add C API init regression test
1 parent a680be5 commit 33d13f7

2 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
import os
41+
import subprocess
42+
import sys
43+
import textwrap
44+
import unittest
45+
46+
47+
@unittest.skipUnless(sys.implementation.name == "graalpy", "GraalPy-specific C API initialization test")
48+
@unittest.skipIf(os.name == "nt", "uses os.pipe() blocking semantics")
49+
class TestCApiInit(unittest.TestCase):
50+
def run_in_subprocess(self, code):
51+
python_args = [sys.executable, "--experimental-options", "--python.EnableDebuggingBuiltins"]
52+
if not __graalpython__.is_native:
53+
python_args += [f"--vm.Dpython.EnableBytecodeDSLInterpreter={str(__graalpython__.is_bytecode_dsl_interpreter).lower()}"]
54+
proc = subprocess.run(
55+
[*python_args, "-c", code],
56+
stdout=subprocess.PIPE,
57+
stderr=subprocess.PIPE,
58+
text=True,
59+
)
60+
if proc.returncode != 0:
61+
self.fail(
62+
"Subprocess failed with exit code {}\nstdout:\n{}\nstderr:\n{}".format(
63+
proc.returncode, proc.stdout, proc.stderr
64+
)
65+
)
66+
67+
def test_import_ctypes_while_other_thread_is_blocked_on_io(self):
68+
code = textwrap.dedent(
69+
"""
70+
import os
71+
import queue
72+
import threading
73+
74+
import __graalpython__
75+
76+
assert __graalpython__.get_capi_state() == "UNINITIALIZED"
77+
78+
read_fd, write_fd = os.pipe()
79+
about_to_block = threading.Event()
80+
import_started = threading.Event()
81+
import_done = threading.Event()
82+
errors = queue.Queue()
83+
84+
def blocked_thread():
85+
try:
86+
assert __graalpython__.get_capi_state() == "UNINITIALIZED"
87+
about_to_block.set()
88+
data = os.read(read_fd, 1)
89+
assert data == b"x"
90+
assert __graalpython__.get_capi_state() == "INITIALIZED"
91+
import ctypes
92+
assert ctypes.sizeof(ctypes.py_object) > 0
93+
except BaseException as e:
94+
errors.put(e)
95+
96+
def importing_thread():
97+
try:
98+
assert __graalpython__.get_capi_state() == "UNINITIALIZED"
99+
import_started.set()
100+
import _ctypes
101+
import ctypes
102+
assert __graalpython__.get_capi_state() == "INITIALIZED"
103+
assert _ctypes.sizeof(ctypes.py_object) > 0
104+
except BaseException as e:
105+
errors.put(e)
106+
finally:
107+
import_done.set()
108+
109+
blocked = threading.Thread(target=blocked_thread, daemon=True)
110+
importer = threading.Thread(target=importing_thread, daemon=True)
111+
try:
112+
blocked.start()
113+
assert about_to_block.wait(10), "blocked thread did not start"
114+
assert __graalpython__.get_capi_state() == "UNINITIALIZED"
115+
116+
importer.start()
117+
assert import_started.wait(10), "importing thread did not start"
118+
assert import_done.wait(20), "C API initialization did not finish"
119+
assert __graalpython__.get_capi_state() == "INITIALIZED"
120+
finally:
121+
try:
122+
os.write(write_fd, b"x")
123+
except OSError:
124+
pass
125+
blocked.join(20)
126+
importer.join(20)
127+
os.close(read_fd)
128+
os.close(write_fd)
129+
130+
assert not blocked.is_alive(), "blocked thread did not finish"
131+
assert not importer.is_alive(), "importing thread did not finish"
132+
133+
if not errors.empty():
134+
raise errors.get()
135+
"""
136+
)
137+
self.run_in_subprocess(code)
138+
139+
140+
if __name__ == "__main__":
141+
unittest.main()

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ public void postInitialize(Python3Core core) {
292292
if (!context.getOption(PythonOptions.EnableDebuggingBuiltins)) {
293293
mod.setAttribute(tsLiteral("dump_truffle_ast"), PNone.NO_VALUE);
294294
mod.setAttribute(tsLiteral("tdebug"), PNone.NO_VALUE);
295+
mod.setAttribute(tsLiteral("get_capi_state"), PNone.NO_VALUE);
295296
mod.setAttribute(tsLiteral("set_storage_strategy"), PNone.NO_VALUE);
296297
mod.setAttribute(tsLiteral("get_storage_strategy"), PNone.NO_VALUE);
297298
mod.setAttribute(tsLiteral("storage_to_native"), PNone.NO_VALUE);
@@ -1194,6 +1195,16 @@ Object doit() {
11941195
}
11951196
}
11961197

1198+
@Builtin(name = "get_capi_state", minNumOfPositionalArgs = 0)
1199+
@GenerateNodeFactory
1200+
abstract static class GetCApiStateNode extends PythonBuiltinNode {
1201+
@Specialization
1202+
@TruffleBoundary
1203+
Object doit() {
1204+
return toTruffleStringUncached(getContext().getCApiState().name());
1205+
}
1206+
}
1207+
11971208
@Builtin(name = "is_native_object", minNumOfPositionalArgs = 1)
11981209
@GenerateNodeFactory
11991210
abstract static class IsNativeObject extends PythonUnaryBuiltinNode {

0 commit comments

Comments
 (0)