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): ...@@ -6469,6 +6469,7 @@ class ReraiseStatNode(StatNode):
UtilityCode.load_cached("ReRaiseException", "Exceptions.c")) UtilityCode.load_cached("ReRaiseException", "Exceptions.c"))
code.putln("__Pyx_ReraiseException(); %s" % code.error_goto(self.pos)) code.putln("__Pyx_ReraiseException(); %s" % code.error_goto(self.pos))
class AssertStatNode(StatNode): class AssertStatNode(StatNode):
# assert statement # assert statement
# #
...@@ -6490,9 +6491,6 @@ class AssertStatNode(StatNode): ...@@ -6490,9 +6491,6 @@ class AssertStatNode(StatNode):
self.value = value.coerce_to_pyobject(env) self.value = value.coerce_to_pyobject(env)
return self return self
nogil_check = Node.gil_error
gil_message = "Raising exception"
def generate_execution_code(self, code): def generate_execution_code(self, code):
code.putln("#ifndef CYTHON_WITHOUT_ASSERTIONS") code.putln("#ifndef CYTHON_WITHOUT_ASSERTIONS")
code.putln("if (unlikely(!Py_OptimizeFlag)) {") code.putln("if (unlikely(!Py_OptimizeFlag)) {")
...@@ -6500,6 +6498,11 @@ class AssertStatNode(StatNode): ...@@ -6500,6 +6498,11 @@ class AssertStatNode(StatNode):
self.cond.generate_evaluation_code(code) self.cond.generate_evaluation_code(code)
code.putln( code.putln(
"if (unlikely(!%s)) {" % self.cond.result()) "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: if self.value:
self.value.generate_evaluation_code(code) self.value.generate_evaluation_code(code)
code.putln( code.putln(
...@@ -6509,6 +6512,8 @@ class AssertStatNode(StatNode): ...@@ -6509,6 +6512,8 @@ class AssertStatNode(StatNode):
else: else:
code.putln( code.putln(
"PyErr_SetNone(PyExc_AssertionError);") "PyErr_SetNone(PyExc_AssertionError);")
if in_nogil:
code.put_release_ensured_gil()
code.putln( code.putln(
code.error_goto(self.pos)) code.error_goto(self.pos))
code.putln( code.putln(
......
...@@ -549,14 +549,18 @@ You can release the GIL around a section of code using the ...@@ -549,14 +549,18 @@ You can release the GIL around a section of code using the
with nogil: with nogil:
<code to be executed with the GIL released> <code to be executed with the GIL released>
Code in the body of the with-statement must not raise exceptions or Code in the body of the with-statement must not manipulate Python objects
manipulate Python objects in any way, and must not call anything that in any way, and must not call anything that manipulates Python objects without
manipulates Python objects without first re-acquiring the GIL. Cython first re-acquiring the GIL. Cython validates these operations at compile time,
validates these operations at compile time, but cannot look into but cannot look into external C functions, for example. They must be correctly
external C functions, for example. They must be correctly declared declared as requiring or not requiring the GIL (see below) in order to make
as requiring or not requiring the GIL (see below) in order to make
Cython's checks effective. 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: .. _gil:
Acquiring the 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 @@ ...@@ -5,6 +5,8 @@
cimport cython cimport cython
#### print
@cython.test_assert_path_exists( @cython.test_assert_path_exists(
"//GILStatNode", "//GILStatNode",
"//GILStatNode//GILStatNode", "//GILStatNode//GILStatNode",
...@@ -23,6 +25,9 @@ def test_print_in_nogil_section(x): ...@@ -23,6 +25,9 @@ def test_print_in_nogil_section(x):
"//GILStatNode", "//GILStatNode",
"//GILStatNode//PrintStatNode", "//GILStatNode//PrintStatNode",
) )
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
cpdef int test_print_in_nogil_func(x) nogil except -1: cpdef int test_print_in_nogil_func(x) nogil except -1:
""" """
>>> _ = test_print_in_nogil_func(123) >>> _ = test_print_in_nogil_func(123)
...@@ -31,6 +36,8 @@ cpdef int test_print_in_nogil_func(x) nogil except -1: ...@@ -31,6 +36,8 @@ cpdef int test_print_in_nogil_func(x) nogil except -1:
print f"--{x}--" print f"--{x}--"
#### raise
@cython.test_assert_path_exists( @cython.test_assert_path_exists(
"//GILStatNode", "//GILStatNode",
"//GILStatNode//GILStatNode", "//GILStatNode//GILStatNode",
...@@ -51,11 +58,82 @@ def test_raise_in_nogil_section(x): ...@@ -51,11 +58,82 @@ def test_raise_in_nogil_section(x):
"//GILStatNode", "//GILStatNode",
"//GILStatNode//RaiseStatNode", "//GILStatNode//RaiseStatNode",
) )
@cython.test_fail_if_path_exists(
"//GILStatNode//GILStatNode",
)
cpdef int test_raise_in_nogil_func(x) nogil except -1: cpdef int test_raise_in_nogil_func(x) nogil except -1:
""" """
>>> try: test_raise_in_nogil_func(123) >>> test_raise_in_nogil_func(123)
... except ValueError as exc: print(exc) Traceback (most recent call last):
... else: print("NOT RAISED !") ValueError: --123--
--123--
""" """
raise ValueError(f"--{x}--") 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