Commit 4f4841aa authored by Stefan Behnel's avatar Stefan Behnel

Make raise-statements inside of nogil blocks automatically acquire the GIL,...

Make raise-statements inside of nogil blocks automatically acquire the GIL, instead of requiring an explicit ``with gil`` block around them.
parent 08efed85
...@@ -8,6 +8,9 @@ Cython Changelog ...@@ -8,6 +8,9 @@ Cython Changelog
Features added Features added
-------------- --------------
* Raising exceptions from nogil code will automatically acquire the GIL, instead
of requiring an explicit ``with gil`` block.
* In CPython 3.6 and later, looking up globals in the module dict is almost * In CPython 3.6 and later, looking up globals in the module dict is almost
as fast as looking up C globals. as fast as looking up C globals.
...@@ -16,12 +19,12 @@ Features added ...@@ -16,12 +19,12 @@ Features added
* Some internal and 1-argument method calls are faster. * Some internal and 1-argument method calls are faster.
* The coverage plugin considers more C file extensions such as ``.cc`` and ``.cxx``.
(Github issue #2266)
* Modules that cimport many external extension types from other Cython modules * Modules that cimport many external extension types from other Cython modules
execute less import requests during module initialisation. execute less import requests during module initialisation.
* The coverage plugin considers more C file extensions such as ``.cc`` and ``.cxx``.
(Github issue #2266)
Bugs fixed Bugs fixed
---------- ----------
......
...@@ -2776,6 +2776,53 @@ class CreateClosureClasses(CythonTransform): ...@@ -2776,6 +2776,53 @@ class CreateClosureClasses(CythonTransform):
return node return node
class InjectGilHandling(VisitorTransform, SkipDeclarations):
"""
Allow certain Python operations inside of GIL blocks by implicitly acquiring the GIL.
"""
def __call__(self, root):
self.nogil = False
return super(InjectGilHandling, self).__call__(root)
# special node handling
def visit_RaiseStatNode(self, node):
"""Allow raising exceptions in nogil sections by wrapping them in a 'with gil' block."""
if self.nogil:
node = Nodes.GILStatNode(node.pos, state='gil', body=node)
return node
# nogil tracking
def visit_GILStatNode(self, node):
was_nogil = self.nogil
self.nogil = (node.state == 'nogil')
self.visitchildren(node)
self.nogil = was_nogil
return node
def visit_CFuncDefNode(self, node):
was_nogil = self.nogil
if isinstance(node.declarator, Nodes.CFuncDeclaratorNode):
self.nogil = node.declarator.nogil and not node.declarator.with_gil
self.visitchildren(node)
self.nogil = was_nogil
return node
def visit_ParallelRangeNode(self, node):
was_nogil = self.nogil
self.nogil = node.nogil
self.visitchildren(node)
self.nogil = was_nogil
return node
def visit_ExprNode(self, node):
# No special GIL handling inside of expressions for now.
return node
visit_Node = VisitorTransform.recurse_to_children
class GilCheck(VisitorTransform): class GilCheck(VisitorTransform):
""" """
Call `node.gil_check(env)` on each node to make sure we hold the Call `node.gil_check(env)` on each node to make sure we hold the
......
...@@ -141,7 +141,7 @@ def create_pipeline(context, mode, exclude_classes=()): ...@@ -141,7 +141,7 @@ def create_pipeline(context, mode, exclude_classes=()):
assert mode in ('pyx', 'py', 'pxd') assert mode in ('pyx', 'py', 'pxd')
from .Visitor import PrintTree from .Visitor import PrintTree
from .ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse from .ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
from .ParseTreeTransforms import ForwardDeclareTypes, AnalyseDeclarationsTransform from .ParseTreeTransforms import ForwardDeclareTypes, InjectGilHandling, AnalyseDeclarationsTransform
from .ParseTreeTransforms import AnalyseExpressionsTransform, FindInvalidUseOfFusedTypes from .ParseTreeTransforms import AnalyseExpressionsTransform, FindInvalidUseOfFusedTypes
from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from .ParseTreeTransforms import TrackNumpyAttributes, InterpretCompilerDirectives, TransformBuiltinMethods from .ParseTreeTransforms import TrackNumpyAttributes, InterpretCompilerDirectives, TransformBuiltinMethods
...@@ -194,6 +194,7 @@ def create_pipeline(context, mode, exclude_classes=()): ...@@ -194,6 +194,7 @@ def create_pipeline(context, mode, exclude_classes=()):
FlattenInListTransform(), FlattenInListTransform(),
DecoratorTransform(context), DecoratorTransform(context),
ForwardDeclareTypes(context), ForwardDeclareTypes(context),
InjectGilHandling(),
AnalyseDeclarationsTransform(context), AnalyseDeclarationsTransform(context),
AutoTestDictTransform(context), AutoTestDictTransform(context),
EmbedSignature(context), EmbedSignature(context),
......
...@@ -55,7 +55,7 @@ cdef object m(): ...@@ -55,7 +55,7 @@ cdef object m():
print obj print obj
del fred del fred
return obj return obj
raise obj raise obj # allowed!
if obj: if obj:
pass pass
while obj: while obj:
...@@ -155,7 +155,6 @@ _ERRORS = u""" ...@@ -155,7 +155,6 @@ _ERRORS = u"""
55:8: Python print statement not allowed without gil 55:8: Python print statement not allowed without gil
56:8: Deleting Python object not allowed without gil 56:8: Deleting Python object not allowed without gil
57:8: Returning Python object not allowed without gil 57:8: Returning Python object not allowed without gil
58:8: Raising exception not allowed without gil
59:11: Truth-testing Python object not allowed without gil 59:11: Truth-testing Python object not allowed without gil
61:14: Truth-testing Python object not allowed without gil 61:14: Truth-testing Python object not allowed without gil
63:8: For-loop using object bounds or target not allowed without gil 63:8: For-loop using object bounds or target not allowed without gil
......
# mode: run
# tag: nogil, withgil, exceptions
cdef void foo_nogil(int i) nogil except *:
if i != 0: raise ValueError("huhu !")
cdef void foo(int i) except * with gil: cdef void foo(int i) except * with gil:
if i != 0: raise ValueError if i != 0: raise ValueError
cdef int bar(int i) except? -1 with gil: cdef int bar(int i) except? -1 with gil:
if i != 0: raise ValueError if i != 0: raise ValueError
return 0 return 0
cdef int spam(int i) except? -1 with gil: cdef int spam(int i) except? -1 with gil:
if i != 0: raise TypeError if i != 0: raise TypeError
return -1 return -1
def test_foo_nogil():
"""
>>> test_foo_nogil()
"""
#
foo_nogil(0)
foo_nogil(0)
with nogil:
foo_nogil(0)
foo_nogil(0)
#
try:
with nogil:
foo_nogil(0)
finally:
pass
#
try:
with nogil:
foo_nogil(0)
with nogil:
foo_nogil(0)
finally:
pass
#
try:
with nogil:
foo_nogil(0)
with nogil:
foo_nogil(1)
except:
with nogil:
foo_nogil(0)
finally:
with nogil:
foo_nogil(0)
pass
#
try:
with nogil:
foo_nogil(0)
foo_nogil(0)
finally:
pass
#
try:
with nogil:
foo_nogil(0)
foo_nogil(1)
except:
with nogil:
foo_nogil(0)
finally:
with nogil:
foo_nogil(0)
pass
#
try:
with nogil:
foo_nogil(0)
try:
with nogil:
foo_nogil(1)
except:
with nogil:
foo_nogil(1)
finally:
with nogil:
foo_nogil(0)
pass
except:
with nogil:
foo_nogil(0)
finally:
with nogil:
foo_nogil(0)
pass
#
try:
with nogil:
foo_nogil(0)
try:
with nogil:
foo_nogil(1)
except:
with nogil:
foo_nogil(1)
finally:
with nogil:
foo_nogil(1)
pass
except:
with nogil:
foo_nogil(0)
finally:
with nogil:
foo_nogil(0)
pass
#
def test_foo(): def test_foo():
""" """
>>> test_foo() >>> test_foo()
...@@ -109,6 +220,7 @@ def test_foo(): ...@@ -109,6 +220,7 @@ def test_foo():
pass pass
# #
def test_bar(): def test_bar():
""" """
>>> test_bar() >>> test_bar()
......
...@@ -106,7 +106,7 @@ def test_boundscheck(x): ...@@ -106,7 +106,7 @@ def test_boundscheck(x):
## return y ## return y
def test_with_nogil(nogil): def test_with_nogil(nogil, should_raise=False):
""" """
>>> raised = [] >>> raised = []
>>> class nogil(object): >>> class nogil(object):
...@@ -121,14 +121,25 @@ def test_with_nogil(nogil): ...@@ -121,14 +121,25 @@ def test_with_nogil(nogil):
True True
>>> raised >>> raised
[None] [None]
>>> test_with_nogil(nogil(), should_raise=True)
Traceback (most recent call last):
ValueError: RAISED!
>>> raised[1] is None
False
""" """
result = False result = False
should_raise_bool = True if should_raise else False # help the type inference ...
with nogil: with nogil:
print("WORKS") print("WORKS")
with cython.nogil: with cython.nogil:
result = True result = True
if should_raise_bool:
raise ValueError("RAISED!")
return result return result
MyUnion = cython.union(n=cython.int, x=cython.double) MyUnion = cython.union(n=cython.int, x=cython.double)
MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion)
MyStruct2 = cython.typedef(MyStruct[2]) MyStruct2 = cython.typedef(MyStruct[2])
......
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