Commit 37a1b0ff authored by Jason Madden's avatar Jason Madden

Add original tracebacks when re-raising a greenlet exception.

Also some greenlet module cleanups.

Fixes #450. Fixes #528.
parent e4ef80dc
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details. # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
import sys import sys
from gevent.hub import greenlet, getcurrent, get_hub, GreenletExit, Waiter, PY3, iwait, wait, PYPY from gevent.hub import GreenletExit
from gevent.hub import PY3
from gevent.hub import PYPY
from gevent.hub import Waiter
from gevent.hub import get_hub
from gevent.hub import getcurrent
from gevent.hub import greenlet
from gevent.hub import iwait
from gevent.hub import reraise
from gevent.hub import wait
from gevent.timeout import Timeout from gevent.timeout import Timeout
from collections import deque from collections import deque
...@@ -72,39 +81,57 @@ class FailureSpawnedLink(SpawnedLink): ...@@ -72,39 +81,57 @@ class FailureSpawnedLink(SpawnedLink):
if not source.successful(): if not source.successful():
return SpawnedLink.__call__(self, source) return SpawnedLink.__call__(self, source)
class _lazy(object):
def __init__(self, func):
self.data = (func, func.__name__)
def __get__(self, inst, class_):
if inst is None:
return self
func, name = self.data
value = func(inst)
inst.__dict__[name]= value
return value
class Greenlet(greenlet): class Greenlet(greenlet):
"""A light-weight cooperatively-scheduled execution unit.""" """A light-weight cooperatively-scheduled execution unit."""
value = None
_exc_info = ()
_notifier = None
_start_event = None
args = ()
def __init__(self, run=None, *args, **kwargs): def __init__(self, run=None, *args, **kwargs):
hub = get_hub() hub = get_hub()
greenlet.__init__(self, parent=hub) greenlet.__init__(self, parent=hub)
if run is not None: if run is not None:
self._run = run self._run = run
self.args = args if args:
self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self._links = deque()
self.value = None @_lazy
self._exception = _NONE def _links(self):
self._notifier = None return deque()
self._start_event = None
def _raise_exception(self):
reraise(*self._exc_info)
@property @property
def loop(self): def loop(self):
# needed by killall # needed by killall
return self.parent.loop return self.parent.loop
if PY3: def __bool__(self):
def __bool__(self): return self._start_event is not None and self._exc_info is Greenlet._exc_info
return self._start_event is not None and self._exception is _NONE __nonzero__ = __bool__
else:
def __nonzero__(self):
return self._start_event is not None and self._exception is _NONE
if PYPY: if PYPY:
# oops - pypy's .dead relies on __nonzero__ which we overriden above # oops - pypy's .dead relies on __nonzero__ which we overriden above
@property @property
def dead(self): def dead(self):
return self._greenlet__started and not (self._greenlet__main or _continulet.is_pending(self)) return self._greenlet__started and not (self._greenlet__main or _continulet.is_pending(self))
...@@ -116,12 +143,12 @@ class Greenlet(greenlet): ...@@ -116,12 +143,12 @@ class Greenlet(greenlet):
def ready(self): def ready(self):
"""Return true if and only if the greenlet has finished execution.""" """Return true if and only if the greenlet has finished execution."""
return self.dead or self._exception is not _NONE return self.dead or self._exc_info
def successful(self): def successful(self):
"""Return true if and only if the greenlet has finished execution successfully, """Return true if and only if the greenlet has finished execution successfully,
that is, without raising an error.""" that is, without raising an error."""
return self._exception is None return self._exc_info and self._exc_info[1] is None
def __repr__(self): def __repr__(self):
classname = self.__class__.__name__ classname = self.__class__.__name__
...@@ -158,8 +185,7 @@ class Greenlet(greenlet): ...@@ -158,8 +185,7 @@ class Greenlet(greenlet):
"""Holds the exception instance raised by the function if the greenlet has finished with an error. """Holds the exception instance raised by the function if the greenlet has finished with an error.
Otherwise ``None``. Otherwise ``None``.
""" """
if self._exception is not _NONE: return self._exc_info[1] if self._exc_info else None
return self._exception
def throw(self, *args): def throw(self, *args):
"""Immediatelly switch into the greenlet and raise an exception in it. """Immediatelly switch into the greenlet and raise an exception in it.
...@@ -178,7 +204,7 @@ class Greenlet(greenlet): ...@@ -178,7 +204,7 @@ class Greenlet(greenlet):
try: try:
greenlet.throw(self, *args) greenlet.throw(self, *args)
finally: finally:
if self._exception is _NONE and self.dead: if self._exc_info is Greenlet._exc_info and self.dead:
# the greenlet was never switched to before and it will never be, _report_error was not called # the greenlet was never switched to before and it will never be, _report_error was not called
# the result was not set and the links weren't notified. let's do it here. # the result was not set and the links weren't notified. let's do it here.
# checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet # checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet
...@@ -260,33 +286,32 @@ class Greenlet(greenlet): ...@@ -260,33 +286,32 @@ class Greenlet(greenlet):
if self.ready(): if self.ready():
if self.successful(): if self.successful():
return self.value return self.value
else: self._raise_exception()
raise self._exception if not block:
if block: raise Timeout()
switch = getcurrent().switch
self.rawlink(switch) switch = getcurrent().switch
self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try: try:
t = Timeout.start_new(timeout) result = self.parent.switch()
try: assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, )
result = self.parent.switch() finally:
assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, ) t.cancel()
finally: except:
t.cancel() # unlinking in 'except' instead of finally is an optimization:
except: # if switch occurred normally then link was already removed in _notify_links
# unlinking in 'except' instead of finally is an optimization: # and there's no need to touch the links set.
# if switch occurred normally then link was already removed in _notify_links # Note, however, that if "Invalid switch" assert was removed and invalid switch
# and there's no need to touch the links set. # did happen, the link would remain, causing another invalid switch later in this greenlet.
# Note, however, that if "Invalid switch" assert was removed and invalid switch self.unlink(switch)
# did happen, the link would remain, causing another invalid switch later in this greenlet. raise
self.unlink(switch)
raise if self.ready():
if self.ready(): if self.successful():
if self.successful(): return self.value
return self.value self._raise_exception()
else:
raise self._exception
else:
raise Timeout
def join(self, timeout=None): def join(self, timeout=None):
"""Wait until the greenlet finishes or *timeout* expires. """Wait until the greenlet finishes or *timeout* expires.
...@@ -294,36 +319,36 @@ class Greenlet(greenlet): ...@@ -294,36 +319,36 @@ class Greenlet(greenlet):
""" """
if self.ready(): if self.ready():
return return
else:
switch = getcurrent().switch switch = getcurrent().switch
self.rawlink(switch) self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try: try:
t = Timeout.start_new(timeout) result = self.parent.switch()
try: assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
result = self.parent.switch() finally:
assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, ) t.cancel()
finally: except Timeout as ex:
t.cancel() self.unlink(switch)
except Timeout as ex: if ex is not t:
self.unlink(switch)
if ex is not t:
raise
except:
self.unlink(switch)
raise raise
except:
self.unlink(switch)
raise
def _report_result(self, result): def _report_result(self, result):
self._exception = None self._exc_info = (None, None, None)
self.value = result self.value = result
if self._links and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
def _report_error(self, exc_info): def _report_error(self, exc_info):
exception = exc_info[1] if isinstance(exc_info[1], GreenletExit):
if isinstance(exception, GreenletExit): self._report_result(exc_info[1])
self._report_result(exception)
return return
self._exception = exception
self._exc_info = exc_info
if self._links and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
...@@ -414,7 +439,10 @@ def joinall(greenlets, timeout=None, raise_error=False, count=None): ...@@ -414,7 +439,10 @@ def joinall(greenlets, timeout=None, raise_error=False, count=None):
else: else:
for obj in iwait(greenlets, timeout=timeout, count=count): for obj in iwait(greenlets, timeout=timeout, count=count):
if getattr(obj, 'exception', None) is not None: if getattr(obj, 'exception', None) is not None:
raise obj.exception if hasattr(obj, '_raise_exception'):
obj._raise_exception()
else:
raise obj.exception
def _killall3(greenlets, exception, waiter): def _killall3(greenlets, exception, waiter):
...@@ -473,6 +501,3 @@ def getfuncname(func): ...@@ -473,6 +501,3 @@ def getfuncname(func):
if funcname != '<lambda>': if funcname != '<lambda>':
return funcname return funcname
return repr(func) return repr(func)
_NONE = Exception("Neither exception nor value")
...@@ -103,7 +103,7 @@ def wrap_refcount(method): ...@@ -103,7 +103,7 @@ def wrap_refcount(method):
def report_diff(a, b): def report_diff(a, b):
diff_lines = [] diff_lines = []
for k, v in sorted(a.items()): for k, v in sorted(a.items(), key=lambda i: i[0].__name__):
if b[k] != v: if b[k] != v:
diff_lines.append("%s: %s != %s" % (k, v, b[k])) diff_lines.append("%s: %s != %s" % (k, v, b[k]))
......
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