Commit 1a60a177 authored by Mark Florisson's avatar Mark Florisson

Fix #281 (Possible race condition w/ nogil exception propagation)

parent 5db23f0e
...@@ -134,6 +134,12 @@ class FunctionState(object): ...@@ -134,6 +134,12 @@ class FunctionState(object):
self.temp_counter = 0 self.temp_counter = 0
self.closure_temps = None self.closure_temps = None
# This is used for the error indicator, which needs to be local to the
# function. It used to be global, which relies on the GIL being held.
# However, exceptions may need to be propagated through 'nogil'
# sections, in which case we introduce a race condition.
self.should_declare_error_indicator = False
# labels # labels
def new_label(self, name=None): def new_label(self, name=None):
...@@ -1435,10 +1441,12 @@ class CCodeWriter(object): ...@@ -1435,10 +1441,12 @@ class CCodeWriter(object):
return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos))) return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos)))
def set_error_info(self, pos): def set_error_info(self, pos):
self.funcstate.should_declare_error_indicator = True
if self.c_line_in_traceback: if self.c_line_in_traceback:
cinfo = " %s = %s;" % (Naming.clineno_cname, Naming.line_c_macro) cinfo = " %s = %s;" % (Naming.clineno_cname, Naming.line_c_macro)
else: else:
cinfo = "" cinfo = ""
return "%s = %s[%s]; %s = %s;%s" % ( return "%s = %s[%s]; %s = %s;%s" % (
Naming.filename_cname, Naming.filename_cname,
Naming.filetable_cname, Naming.filetable_cname,
...@@ -1478,6 +1486,20 @@ class CCodeWriter(object): ...@@ -1478,6 +1486,20 @@ class CCodeWriter(object):
def put_finish_refcount_context(self): def put_finish_refcount_context(self):
self.putln("__Pyx_RefNannyFinishContext();") self.putln("__Pyx_RefNannyFinishContext();")
def put_add_traceback(self, qualified_name):
"""
Build a Python traceback for propagating exceptions.
qualified_name should be the qualified name of the function
"""
format_tuple = (
qualified_name,
Naming.clineno_cname,
Naming.lineno_cname,
Naming.filename_cname,
)
self.putln('__Pyx_AddTraceback("%s", %s, %s, %s);' % format_tuple)
def put_trace_declarations(self): def put_trace_declarations(self):
self.putln('__Pyx_TraceDeclarations'); self.putln('__Pyx_TraceDeclarations');
......
...@@ -1755,7 +1755,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1755,7 +1755,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if code.label_used(code.error_label): if code.label_used(code.error_label):
code.put_label(code.error_label) code.put_label(code.error_label)
# This helps locate the offending name. # This helps locate the offending name.
code.putln('__Pyx_AddTraceback("%s");' % self.full_module_name); code.put_add_traceback(self.full_module_name)
code.error_label = old_error_label code.error_label = old_error_label
code.putln("bad:") code.putln("bad:")
code.putln("return -1;") code.putln("return -1;")
...@@ -1868,7 +1868,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1868,7 +1868,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
for cname, type in code.funcstate.all_managed_temps(): for cname, type in code.funcstate.all_managed_temps():
code.put_xdecref(cname, type) code.put_xdecref(cname, type)
code.putln('if (%s) {' % env.module_cname) code.putln('if (%s) {' % env.module_cname)
code.putln('__Pyx_AddTraceback("init %s");' % env.qualified_name) code.put_add_traceback("init %s" % env.qualified_name)
env.use_utility_code(Nodes.traceback_utility_code) env.use_utility_code(Nodes.traceback_utility_code)
code.put_decref_clear(env.module_cname, py_object_type, nanny=False) code.put_decref_clear(env.module_cname, py_object_type, nanny=False)
code.putln('} else if (!PyErr_Occurred()) {') code.putln('} else if (!PyErr_Occurred()) {')
......
...@@ -1474,7 +1474,7 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1474,7 +1474,7 @@ class FuncDefNode(StatNode, BlockNode):
# TODO: Fix exception tracing (though currently unused by cProfile). # TODO: Fix exception tracing (though currently unused by cProfile).
# code.globalstate.use_utility_code(get_exception_tuple_utility_code) # code.globalstate.use_utility_code(get_exception_tuple_utility_code)
# code.put_trace_exception() # code.put_trace_exception()
code.putln('__Pyx_AddTraceback("%s");' % self.entry.qualified_name) code.put_add_traceback(self.entry.qualified_name)
else: else:
warning(self.entry.pos, "Unraisable exception in function '%s'." \ warning(self.entry.pos, "Unraisable exception in function '%s'." \
% self.entry.qualified_name, 0) % self.entry.qualified_name, 0)
...@@ -1563,6 +1563,12 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1563,6 +1563,12 @@ class FuncDefNode(StatNode, BlockNode):
# ----- Go back and insert temp variable declarations # ----- Go back and insert temp variable declarations
tempvardecl_code.put_temp_declarations(code.funcstate) tempvardecl_code.put_temp_declarations(code.funcstate)
if code.funcstate.should_declare_error_indicator:
tempvardecl_code.putln("int %s;" % Naming.lineno_cname)
tempvardecl_code.putln("const char *%s;" % Naming.filename_cname)
if code.c_line_in_traceback:
tempvardecl_code.putln("int %s;" % Naming.clineno_cname)
# ----- Python version # ----- Python version
code.exit_cfunc_scope() code.exit_cfunc_scope()
if self.py_func: if self.py_func:
...@@ -2457,7 +2463,7 @@ class DefNode(FuncDefNode): ...@@ -2457,7 +2463,7 @@ class DefNode(FuncDefNode):
code.put_var_xdecref_clear(self.starstar_arg.entry) code.put_var_xdecref_clear(self.starstar_arg.entry)
else: else:
code.put_var_decref_clear(self.starstar_arg.entry) code.put_var_decref_clear(self.starstar_arg.entry)
code.putln('__Pyx_AddTraceback("%s");' % self.entry.qualified_name) code.put_add_traceback(self.entry.qualified_name)
# The arguments are put into the closure one after the # The arguments are put into the closure one after the
# other, so when type errors are found, all references in # other, so when type errors are found, all references in
# the closure instance must be properly ref-counted to # the closure instance must be properly ref-counted to
...@@ -5272,7 +5278,7 @@ class ExceptClauseNode(Node): ...@@ -5272,7 +5278,7 @@ class ExceptClauseNode(Node):
exc_vars = [code.funcstate.allocate_temp(py_object_type, exc_vars = [code.funcstate.allocate_temp(py_object_type,
manage_ref=True) manage_ref=True)
for i in xrange(3)] for i in xrange(3)]
code.putln('__Pyx_AddTraceback("%s");' % self.function_name) code.put_add_traceback(self.function_name)
# We always have to fetch the exception value even if # We always have to fetch the exception value even if
# there is no target, because this also normalises the # there is no target, because this also normalises the
# exception and stores it in the thread state. # exception and stores it in the thread state.
...@@ -7125,15 +7131,22 @@ requires=[raise_double_keywords_utility_code]) ...@@ -7125,15 +7131,22 @@ requires=[raise_double_keywords_utility_code])
#------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------
traceback_utility_code = UtilityCode( traceback_utility_code = UtilityCode(
proto = """ proto = """
static void __Pyx_AddTraceback(const char *funcname); /*proto*/ static void __Pyx_AddTraceback(const char *funcname, int %(CLINENO)s,
""", int %(LINENO)s, const char *%(FILENAME)s); /*proto*/
impl = """ """ % {
'FILENAME': Naming.filename_cname,
'LINENO': Naming.lineno_cname,
'CLINENO': Naming.clineno_cname,
},
impl = """
#include "compile.h" #include "compile.h"
#include "frameobject.h" #include "frameobject.h"
#include "traceback.h" #include "traceback.h"
static void __Pyx_AddTraceback(const char *funcname) { static void __Pyx_AddTraceback(const char *funcname, int %(CLINENO)s,
int %(LINENO)s, const char *%(FILENAME)s) {
PyObject *py_srcfile = 0; PyObject *py_srcfile = 0;
PyObject *py_funcname = 0; PyObject *py_funcname = 0;
PyObject *py_globals = 0; PyObject *py_globals = 0;
......
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