Author: Ronan Lamy <[email protected]>
Branch:
Changeset: r94113:37ea4d5f0500
Date: 2018-03-23 12:13 +0100
http://bitbucket.org/pypy/pypy/changeset/37ea4d5f0500/
Log: Merged cpyext-tls-operror2 into default
diff --git a/pypy/module/cpyext/frameobject.py
b/pypy/module/cpyext/frameobject.py
--- a/pypy/module/cpyext/frameobject.py
+++ b/pypy/module/cpyext/frameobject.py
@@ -82,10 +82,10 @@
def PyTraceBack_Here(space, w_frame):
from pypy.interpreter.pytraceback import record_application_traceback
state = space.fromcache(State)
- if state.operror is None:
+ if state.get_exception() is None:
return -1
frame = space.interp_w(PyFrame, w_frame)
- record_application_traceback(space, state.operror, frame, 0)
+ record_application_traceback(space, state.get_exception(), frame, 0)
return 0
@cpython_api([PyObject], rffi.INT_real, error=CANNOT_FAIL)
diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py
--- a/pypy/module/cpyext/pyerrors.py
+++ b/pypy/module/cpyext/pyerrors.py
@@ -31,9 +31,10 @@
@cpython_api([], PyObject, result_borrowed=True)
def PyErr_Occurred(space):
state = space.fromcache(State)
- if state.operror is None:
+ operror = state.get_exception()
+ if operror is None:
return None
- return state.operror.w_type # borrowed ref
+ return operror.w_type # borrowed ref
@cpython_api([], lltype.Void)
def PyErr_Clear(space):
diff --git a/pypy/module/cpyext/state.py b/pypy/module/cpyext/state.py
--- a/pypy/module/cpyext/state.py
+++ b/pypy/module/cpyext/state.py
@@ -2,11 +2,18 @@
from rpython.rtyper.lltypesystem import rffi, lltype
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter import executioncontext
+from pypy.interpreter.executioncontext import ExecutionContext
from rpython.rtyper.annlowlevel import llhelper
from rpython.rlib.rdynload import DLLHANDLE
from rpython.rlib import rawrefcount
import sys
+
+# Keep track of exceptions raised in cpyext for a particular execution
+# context.
+ExecutionContext.cpyext_operror = None
+
+
class State:
def __init__(self, space):
self.space = space
@@ -18,7 +25,8 @@
def reset(self):
from pypy.module.cpyext.modsupport import PyMethodDef
- self.operror = None
+ ec = self.space.getexecutioncontext()
+ ec.cpyext_operror = None
self.new_method_def = lltype.nullptr(PyMethodDef)
# When importing a package, use this to keep track
@@ -37,17 +45,24 @@
def set_exception(self, operror):
self.clear_exception()
- self.operror = operror
+ ec = self.space.getexecutioncontext()
+ ec.cpyext_operror = operror
def clear_exception(self):
"""Clear the current exception state, and return the operror."""
- operror = self.operror
- self.operror = None
+ ec = self.space.getexecutioncontext()
+ operror = ec.cpyext_operror
+ ec.cpyext_operror = None
return operror
+ def get_exception(self):
+ ec = self.space.getexecutioncontext()
+ return ec.cpyext_operror
+
@specialize.arg(1)
def check_and_raise_exception(self, always=False):
- operror = self.operror
+ ec = self.space.getexecutioncontext()
+ operror = ec.cpyext_operror
if operror:
self.clear_exception()
raise operror
diff --git a/pypy/module/cpyext/test/test_api.py
b/pypy/module/cpyext/test/test_api.py
--- a/pypy/module/cpyext/test/test_api.py
+++ b/pypy/module/cpyext/test/test_api.py
@@ -39,7 +39,7 @@
raise Exception("%s is not callable" % (f,))
f(*args)
state = space.fromcache(State)
- operror = state.operror
+ operror = state.get_exception()
if not operror:
raise Exception("DID NOT RAISE")
if getattr(space, 'w_' + expected_exc.__name__) is not operror.w_type:
diff --git a/pypy/module/cpyext/test/test_pyerrors.py
b/pypy/module/cpyext/test/test_pyerrors.py
--- a/pypy/module/cpyext/test/test_pyerrors.py
+++ b/pypy/module/cpyext/test/test_pyerrors.py
@@ -52,7 +52,8 @@
api.PyErr_SetObject(space.w_ValueError, space.wrap("a value"))
assert api.PyErr_Occurred() is space.w_ValueError
state = space.fromcache(State)
- assert space.eq_w(state.operror.get_w_value(space),
+ operror = state.get_exception()
+ assert space.eq_w(operror.get_w_value(space),
space.wrap("a value"))
api.PyErr_Clear()
@@ -60,12 +61,14 @@
def test_SetNone(self, space, api):
api.PyErr_SetNone(space.w_KeyError)
state = space.fromcache(State)
- assert space.eq_w(state.operror.w_type, space.w_KeyError)
- assert space.eq_w(state.operror.get_w_value(space), space.w_None)
+ operror = state.get_exception()
+ assert space.eq_w(operror.w_type, space.w_KeyError)
+ assert space.eq_w(operror.get_w_value(space), space.w_None)
api.PyErr_Clear()
api.PyErr_NoMemory()
- assert space.eq_w(state.operror.w_type, space.w_MemoryError)
+ operror = state.get_exception()
+ assert space.eq_w(operror.w_type, space.w_MemoryError)
api.PyErr_Clear()
def test_Warning(self, space, api, capfd):
@@ -437,3 +440,59 @@
'''),
])
raises(SystemError, module.oops)
+
+ def test_error_thread_race(self):
+ # Check race condition: thread 0 returns from cpyext with error set,
+ # after thread 1 has set an error but before it returns.
+ module = self.import_extension('foo', [
+ ("emit_error", "METH_VARARGS",
+ '''
+ PyThreadState *save = NULL;
+ PyGILState_STATE gilsave;
+
+ /* NB. synchronization due to GIL */
+ static volatile int flag = 0;
+ int id;
+
+ if (!PyArg_ParseTuple(args, "i", &id))
+ return NULL;
+
+ /* Proceed in thread 1 first */
+ save = PyEval_SaveThread();
+ while (id == 0 && flag == 0);
+ gilsave = PyGILState_Ensure();
+
+ PyErr_Format(PyExc_ValueError, "%d", id);
+
+ /* Proceed in thread 0 first */
+ if (id == 1) flag = 1;
+ PyGILState_Release(gilsave);
+ while (id == 1 && flag == 1);
+ PyEval_RestoreThread(save);
+
+ if (id == 0) flag = 0;
+ return NULL;
+ '''
+ ),
+ ])
+
+ import threading
+
+ failures = []
+
+ def worker(arg):
+ try:
+ module.emit_error(arg)
+ failures.append(True)
+ except Exception as exc:
+ if str(exc) != str(arg):
+ failures.append(exc)
+
+ threads = [threading.Thread(target=worker, args=(j,))
+ for j in (0, 1)]
+ for t in threads:
+ t.start()
+ for t in threads:
+ t.join()
+
+ assert not failures
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit