Commit 792d80ba authored by Stefan Behnel's avatar Stefan Behnel

Support the 'assert' statement in nogil sections, as long as it has a plain C...

Support the 'assert' statement in nogil sections, as long as it has a plain C condition and constant message (string) value.
parent 87f96651
......@@ -6469,6 +6469,7 @@ class ReraiseStatNode(StatNode):
UtilityCode.load_cached("ReRaiseException", "Exceptions.c"))
code.putln("__Pyx_ReraiseException(); %s" % code.error_goto(self.pos))
class AssertStatNode(StatNode):
# assert statement
#
......@@ -6490,9 +6491,6 @@ class AssertStatNode(StatNode):
self.value = value.coerce_to_pyobject(env)
return self
nogil_check = Node.gil_error
gil_message = "Raising exception"
def generate_execution_code(self, code):
code.putln("#ifndef CYTHON_WITHOUT_ASSERTIONS")
code.putln("if (unlikely(!Py_OptimizeFlag)) {")
......@@ -6500,6 +6498,11 @@ class AssertStatNode(StatNode):
self.cond.generate_evaluation_code(code)
code.putln(
"if (unlikely(!%s)) {" % self.cond.result())
in_nogil = not code.funcstate.gil_owned
if in_nogil:
# Apparently, evaluating condition and value does not require the GIL,
# but raising the exception now does.
code.put_ensure_gil()
if self.value:
self.value.generate_evaluation_code(code)
code.putln(
......@@ -6509,6 +6512,8 @@ class AssertStatNode(StatNode):
else:
code.putln(
"PyErr_SetNone(PyExc_AssertionError);")
if in_nogil:
code.put_release_ensured_gil()
code.putln(
code.error_goto(self.pos))
code.putln(
......
......@@ -549,14 +549,18 @@ You can release the GIL around a section of code using the
with nogil:
<code to be executed with the GIL released>
Code in the body of the with-statement must not raise exceptions or
manipulate Python objects in any way, and must not call anything that
manipulates Python objects without first re-acquiring the GIL. Cython
validates these operations at compile time, but cannot look into
external C functions, for example. They must be correctly declared
as requiring or not requiring the GIL (see below) in order to make
Code in the body of the with-statement must not manipulate Python objects
in any way, and must not call anything that manipulates Python objects without
first re-acquiring the GIL. Cython validates these operations at compile time,
but cannot look into external C functions, for example. They must be correctly
declared as requiring or not requiring the GIL (see below) in order to make
Cython's checks effective.
Since Cython 3.0, some simple Python statements can be used inside of ``nogil``
sections: ``raise``, ``assert`` and ``print`` (the Py2 statement, not the function).
Since they tend to be lone Python statements, Cython will automatically acquire
and release the GIL around them for convenience.
.. _gil:
Acquiring the GIL
......
# mode: error
# tag: assert
def nontrivial_assert_in_nogil(int a, obj):
with nogil:
# NOK
assert obj
assert a*obj
assert a, f"123{a}xyz"
# OK
assert a
assert a*a
assert a, "abc"
assert a, u"abc"
_ERRORS = """
7:15: Truth-testing Python object not allowed without gil
8:15: Converting to Python object not allowed without gil
8:16: Operation not allowed without gil
8:16: Truth-testing Python object not allowed without gil
9:18: String concatenation not allowed without gil
9:18: String formatting not allowed without gil
"""
......@@ -5,6 +5,8 @@
cimport cython
#### print
@cython.test_assert_path_exists(
"//GILStatNode",
"//GILStatNode//GILStatNode",
......@@ -23,6 +25,9 @@ def test_print_in_nogil_section(x):
"//GILStatNode",
"//GILStatNode//PrintStatNode",
)
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
cpdef int test_print_in_nogil_func(x) nogil except -1:
"""
>>> _ = test_print_in_nogil_func(123)
......@@ -31,6 +36,8 @@ cpdef int test_print_in_nogil_func(x) nogil except -1:
print f"--{x}--"
#### raise
@cython.test_assert_path_exists(
"//GILStatNode",
"//GILStatNode//GILStatNode",
......@@ -51,11 +58,82 @@ def test_raise_in_nogil_section(x):
"//GILStatNode",
"//GILStatNode//RaiseStatNode",
)
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
cpdef int test_raise_in_nogil_func(x) nogil except -1:
"""
>>> try: test_raise_in_nogil_func(123)
... except ValueError as exc: print(exc)
... else: print("NOT RAISED !")
--123--
>>> test_raise_in_nogil_func(123)
Traceback (most recent call last):
ValueError: --123--
"""
raise ValueError(f"--{x}--")
#### assert
@cython.test_assert_path_exists(
"//GILStatNode",
"//GILStatNode//AssertStatNode",
)
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
def assert_in_nogil_section(int x):
"""
>>> assert_in_nogil_section(123)
>>> assert_in_nogil_section(0)
Traceback (most recent call last):
AssertionError
"""
with nogil:
assert x
@cython.test_assert_path_exists(
"//GILStatNode",
"//GILStatNode//AssertStatNode",
)
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
def assert_in_nogil_section_ustring(int x):
"""
>>> assert_in_nogil_section_string(123)
>>> assert_in_nogil_section_string(0)
Traceback (most recent call last):
AssertionError: failed!
"""
with nogil:
assert x, u"failed!"
@cython.test_assert_path_exists(
"//GILStatNode",
"//GILStatNode//AssertStatNode",
)
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
def assert_in_nogil_section_string(int x):
"""
>>> assert_in_nogil_section_string(123)
>>> assert_in_nogil_section_string(0)
Traceback (most recent call last):
AssertionError: failed!
"""
with nogil:
assert x, "failed!"
@cython.test_fail_if_path_exists(
"//GILStatNode",
)
cpdef int assert_in_nogil_func(int x) nogil except -1:
"""
>>> _ = assert_in_nogil_func(123)
>>> assert_in_nogil_func(0)
Traceback (most recent call last):
AssertionError: failed!
"""
assert x, "failed!"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment