Skip to content

Commit 3634831

Browse files
committed
Implement PyContext_XXX CExt APIs
1 parent 72a756a commit 3634831

File tree

6 files changed

+270
-11
lines changed

6 files changed

+270
-11
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Copyright (c) 2024, 2024, 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 contextvars
41+
from . import CPyExtType
42+
from ..util import assert_raises
43+
44+
45+
ContextHelper = CPyExtType(
46+
'TestContextvarsContextHelper',
47+
'''
48+
static PyObject* context_enter(PyObject* unused, PyObject* args) {
49+
PyObject *ctx;
50+
if (!PyArg_ParseTuple(args, "O", &ctx))
51+
return NULL;
52+
PyContext_Enter(ctx);
53+
Py_RETURN_NONE;
54+
}
55+
static PyObject* context_exit(PyObject* unused, PyObject* args) {
56+
PyObject *ctx;
57+
if (!PyArg_ParseTuple(args, "O", &ctx))
58+
return NULL;
59+
PyContext_Exit(ctx);
60+
Py_RETURN_NONE;
61+
}
62+
static PyObject* context_copy(PyObject* unused, PyObject* args) {
63+
PyObject *ctx;
64+
if (!PyArg_ParseTuple(args, "O", &ctx))
65+
return NULL;
66+
return PyContext_Copy(ctx);
67+
}
68+
static PyObject* context_new(PyObject* unused, PyObject* args) {
69+
return PyContext_New();
70+
}
71+
static PyObject* context_copy_current(PyObject* unused, PyObject* args) {
72+
return PyContext_CopyCurrent();
73+
}
74+
''',
75+
tp_methods='''
76+
{"enter", (PyCFunction)context_enter, METH_VARARGS | METH_STATIC, ""},
77+
{"exit", (PyCFunction)context_exit, METH_VARARGS | METH_STATIC, ""},
78+
{"copy", (PyCFunction)context_copy, METH_VARARGS | METH_STATIC, ""},
79+
{"new", (PyCFunction)context_new, METH_VARARGS | METH_STATIC, ""},
80+
{"copy_current", (PyCFunction)context_copy_current, METH_VARARGS | METH_STATIC, ""}
81+
'''
82+
)
83+
84+
85+
def test_cext_context_management():
86+
v = contextvars.ContextVar('test1', default='default value')
87+
assert v.get() == 'default value'
88+
89+
token = v.set('new value')
90+
91+
assert v.get() == 'new value'
92+
current_copy = ContextHelper.copy_current()
93+
94+
assert v.get() == 'new value'
95+
assert current_copy.run(v.get) == 'new value'
96+
97+
current_copy.run(v.set, 'newer value')
98+
assert v.get() == 'new value'
99+
assert current_copy.run(v.get) == 'newer value'
100+
101+
ContextHelper.enter(current_copy)
102+
try:
103+
assert v.get() == 'newer value'
104+
assert_raises(RuntimeError, current_copy.run, v.get, err_check='cannot enter context')
105+
token_in_copy = v.set('newer value 2')
106+
finally:
107+
ContextHelper.exit(current_copy)
108+
109+
assert v.get() == 'new value'
110+
assert current_copy.run(v.get) == 'newer value 2'
111+
112+
v.reset(token)
113+
assert v.get() == 'default value'
114+
assert current_copy.run(v.get) == 'newer value 2'
115+
116+
copy_of_copy = ContextHelper.copy(current_copy)
117+
current_copy.run(v.reset, token_in_copy)
118+
assert v.get() == 'default value'
119+
assert current_copy.run(v.get) == 'newer value'
120+
assert copy_of_copy.run(v.get) == 'newer value 2'
121+
122+
new_ctx = ContextHelper.new()
123+
assert new_ctx.run(v.get) == 'default value'

graalpython/com.oracle.graal.python.test/src/tests/util.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,18 @@
4343
def storage_to_native(s):
4444
if sys.implementation.name == 'graalpy':
4545
assert hasattr(__graalpython__, 'storage_to_native'), "Needs to be run with --python.EnableDebuggingBuiltins"
46-
__graalpython__.storage_to_native(s)
46+
__graalpython__.storage_to_native(s)
47+
48+
49+
def assert_raises(err, fn, *args, err_check=None, **kwargs):
50+
raised = False
51+
try:
52+
fn(*args, **kwargs)
53+
except err as e:
54+
raised = True
55+
if err_check:
56+
if isinstance(err_check, str):
57+
assert err_check in str(e), f"Substring '{err_check}' not found in '{str(e)}'"
58+
else:
59+
assert err_check(e)
60+
assert raised

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@
5151
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
5252
import com.oracle.graal.python.builtins.PythonBuiltins;
5353
import com.oracle.graal.python.builtins.objects.contextvars.PContextVar;
54+
import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext;
55+
import com.oracle.graal.python.lib.PyContextCopyCurrent;
5456
import com.oracle.graal.python.nodes.ErrorMessages;
5557
import com.oracle.graal.python.nodes.PRaiseNode;
5658
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
5759
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
5860
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryClinicBuiltinNode;
5961
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
6062
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
61-
import com.oracle.graal.python.runtime.PythonContext;
6263
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
6364
import com.oracle.truffle.api.dsl.Bind;
6465
import com.oracle.truffle.api.dsl.Cached;
@@ -81,10 +82,10 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
8182
@GenerateNodeFactory
8283
public abstract static class GetDefaultEncodingNode extends PythonBuiltinNode {
8384
@Specialization
84-
protected Object copyCtx(
85-
@Cached PythonObjectFactory factory) {
86-
PythonContext.PythonThreadState threadState = getContext().getThreadState(getLanguage());
87-
return factory.copyContextVarsContext(threadState.getContextVarsContext());
85+
protected static PContextVarsContext copyCtx(
86+
@Bind("this") Node inliningTarget,
87+
@Cached PyContextCopyCurrent copyCurrent) {
88+
return copyCurrent.execute(inliningTarget);
8889
}
8990
}
9091

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct;
4444
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored;
4545
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.ConstCharPtrAsTruffleString;
46+
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int;
4647
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Pointer;
4748
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject;
4849
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer;
@@ -55,9 +56,12 @@
5556
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin;
5657
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiNullaryBuiltinNode;
5758
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode;
59+
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode;
5860
import com.oracle.graal.python.builtins.objects.PNone;
5961
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.PRaiseNativeNode;
6062
import com.oracle.graal.python.builtins.objects.contextvars.PContextVar;
63+
import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext;
64+
import com.oracle.graal.python.lib.PyContextCopyCurrent;
6165
import com.oracle.graal.python.nodes.ErrorMessages;
6266
import com.oracle.graal.python.nodes.PRaiseNode;
6367
import com.oracle.graal.python.nodes.call.CallNode;
@@ -141,4 +145,57 @@ static Object doGeneric(Object var, Object val,
141145
return factory.createContextVarsToken(pvar, oldValue);
142146
}
143147
}
148+
149+
@CApiBuiltin(ret = PyObjectTransfer, call = Direct)
150+
abstract static class PyContext_CopyCurrent extends CApiNullaryBuiltinNode {
151+
@Specialization
152+
static Object doGeneric(
153+
@Bind("this") Node inliningTarget,
154+
@Cached PyContextCopyCurrent copyCurrent) {
155+
return copyCurrent.execute(inliningTarget);
156+
}
157+
}
158+
159+
@CApiBuiltin(ret = PyObjectTransfer, args = {PyObject}, call = Direct)
160+
abstract static class PyContext_Copy extends CApiUnaryBuiltinNode {
161+
@Specialization
162+
static Object doGeneric(PContextVarsContext context,
163+
@Cached PythonObjectFactory factory) {
164+
return factory.copyContextVarsContext(context);
165+
}
166+
}
167+
168+
@CApiBuiltin(ret = PyObjectTransfer, call = Direct)
169+
abstract static class PyContext_New extends CApiNullaryBuiltinNode {
170+
@Specialization
171+
static Object doGeneric(
172+
@Cached PythonObjectFactory factory) {
173+
return factory.createContextVarsContext();
174+
}
175+
}
176+
177+
@CApiBuiltin(ret = Int, args = {PyObject}, call = Direct)
178+
abstract static class PyContext_Enter extends CApiUnaryBuiltinNode {
179+
@Specialization
180+
static Object doGeneric(PContextVarsContext context,
181+
@Bind("this") Node inliningTarget,
182+
@Cached PRaiseNode.Lazy raiseNode) {
183+
PythonLanguage language = PythonLanguage.get(inliningTarget);
184+
PythonContext.PythonThreadState threadState = getContext(inliningTarget).getThreadState(language);
185+
context.enter(inliningTarget, threadState, raiseNode);
186+
return 0;
187+
}
188+
}
189+
190+
@CApiBuiltin(ret = Int, args = {PyObject}, call = Direct)
191+
abstract static class PyContext_Exit extends CApiUnaryBuiltinNode {
192+
@Specialization
193+
static Object doGeneric(PContextVarsContext context,
194+
@Bind("this") Node inliningTarget) {
195+
PythonLanguage language = PythonLanguage.get(inliningTarget);
196+
PythonContext.PythonThreadState threadState = getContext(inliningTarget).getThreadState(language);
197+
context.leave(threadState);
198+
return 0;
199+
}
200+
}
144201
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -695,11 +695,6 @@ public final class CApiFunction {
695695
@CApiBuiltin(name = "PyConfig_SetString", ret = PYSTATUS, args = {PYCONFIG_PTR, WCHAR_T_PTR_LIST, CONST_WCHAR_PTR}, call = NotImplemented)
696696
@CApiBuiltin(name = "PyConfig_SetWideStringList", ret = PYSTATUS, args = {PYCONFIG_PTR, PYWIDESTRINGLIST_PTR, Py_ssize_t, WCHAR_T_PTR_LIST}, call = NotImplemented)
697697
@CApiBuiltin(name = "PyContextVar_Reset", ret = Int, args = {PyObject, PyObject}, call = NotImplemented)
698-
@CApiBuiltin(name = "PyContext_Copy", ret = PyObject, args = {PyObject}, call = NotImplemented)
699-
@CApiBuiltin(name = "PyContext_CopyCurrent", ret = PyObject, args = {}, call = NotImplemented)
700-
@CApiBuiltin(name = "PyContext_Enter", ret = Int, args = {PyObject}, call = NotImplemented)
701-
@CApiBuiltin(name = "PyContext_Exit", ret = Int, args = {PyObject}, call = NotImplemented)
702-
@CApiBuiltin(name = "PyContext_New", ret = PyObject, args = {}, call = NotImplemented)
703698
@CApiBuiltin(name = "PyCoro_New", ret = PyObject, args = {PyFrameObject, PyObject, PyObject}, call = NotImplemented)
704699
@CApiBuiltin(name = "PyDescr_NewMember", ret = PyObject, args = {PyTypeObject, PyMemberDef}, call = NotImplemented)
705700
@CApiBuiltin(name = "PyDescr_NewMethod", ret = PyObject, args = {PyTypeObject, PyMethodDef}, call = NotImplemented)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.lib;
42+
43+
import com.oracle.graal.python.PythonLanguage;
44+
import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext;
45+
import com.oracle.graal.python.nodes.PNodeWithContext;
46+
import com.oracle.graal.python.runtime.PythonContext;
47+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
48+
import com.oracle.truffle.api.dsl.Cached;
49+
import com.oracle.truffle.api.dsl.GenerateCached;
50+
import com.oracle.truffle.api.dsl.GenerateInline;
51+
import com.oracle.truffle.api.dsl.Specialization;
52+
import com.oracle.truffle.api.nodes.Node;
53+
54+
/**
55+
* Equivalent of CPython's {@code PyContext_CopyCurrent}.
56+
*/
57+
@GenerateInline
58+
@GenerateCached(false)
59+
public abstract class PyContextCopyCurrent extends PNodeWithContext {
60+
public abstract PContextVarsContext execute(Node inliningTarget);
61+
62+
@Specialization
63+
static PContextVarsContext doIt(Node inliningTarget,
64+
@Cached(inline = false) PythonObjectFactory factory) {
65+
PythonLanguage language = PythonLanguage.get(inliningTarget);
66+
PythonContext.PythonThreadState threadState = PythonContext.get(inliningTarget).getThreadState(language);
67+
return factory.copyContextVarsContext(threadState.getContextVarsContext());
68+
}
69+
}

0 commit comments

Comments
 (0)