Reputation: 1167
I'm using gevent on Python 2.7 to do some multithreaded work. However, I am unable to catch exceptions that are raised in the spawned methods. I am using the .get()
method on the returned AsyncResult
object (returned when calling the .spawn()
method on the thread pool).
Per the gevent documentation: http://www.gevent.org/gevent.event.html#gevent.event.AsyncResult.get:
get(block=True, timeout=None)
Return the stored value or raise the exception.
If this instance already holds a value or an exception, return or raise it immediately.
However, instead of returning the exception, .get()
returns NoneType
. Additionally, in the console, the exception details are printed out. Instead, .get()
should return the Exception back.
Here is some sample code and corresponding output that showcases this:
from gevent import threadpool
def this_will_fail():
raise Exception('This will fail')
result_list = []
for i in range(1,5):
tpool = threadpool.ThreadPool(5)
result_list.append(tpool.spawn(this_will_fail))
tpool.join()
for result in result_list:
try:
returned_object = result.get()
print type(returned_object)
except Exception as e:
print 'This is not running for some reason...'
Output:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gevent/threadpool.py", line 193, in _worker
value = func(*args, **kwargs)
File "<stdin>", line 2, in this_will_fail
Exception: This will fail
(<ThreadPool at 0x107c79e50 0/1/5>, <function this_will_fail at 0x107c848c0>) failed with Exception
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gevent/threadpool.py", line 193, in _worker
value = func(*args, **kwargs)
File "<stdin>", line 2, in this_will_fail
Exception: This will fail
(<ThreadPool at 0x107c99210 0/1/5>, <function this_will_fail at 0x107c848c0>) failed with Exception
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gevent/threadpool.py", line 193, in _worker
value = func(*args, **kwargs)
File "<stdin>", line 2, in this_will_fail
Exception: This will fail
(<ThreadPool at 0x107c99410 0/1/5>, <function this_will_fail at 0x107c848c0>) failed with Exception
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/gevent/threadpool.py", line 193, in _worker
value = func(*args, **kwargs)
File "<stdin>", line 2, in this_will_fail
Exception: This will fail
(<ThreadPool at 0x107c99610 0/1/5>, <function this_will_fail at 0x107c848c0>) failed with Exception
<type 'NoneType'>
<type 'NoneType'>
<type 'NoneType'>
<type 'NoneType'>
Am I missing something obvious, or is this a bug in gevent?
EDIT: concurrent.futures
doesn't have this problem. While that is an acceptable solution for my use case, I would still like to understand why gevent doesn't properly return the exception.
Upvotes: 0
Views: 657
Reputation: 26
Here we had the same problem with the gevent threadpool. We are using a solution which wrap the function to return exceptions as result, and raise it again when we want to use the result from the AsynResult.
class _ExceptionWrapper:
def __init__(self, exception, error_string, tb):
self.exception = exception
self.error_string = error_string
self.tb = tb
class wrap_errors(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
func = self.func
try:
return func(*args, **kwargs)
except:
return _ExceptionWrapper(*sys.exc_info())
def __str__(self):
return str(self.func)
def __repr__(self):
return repr(self.func)
def __getattr__(self, item):
return getattr(self.func, item)
def get_with_exception(g, block=True, timeout=None):
result = g._get(block, timeout)
if isinstance(result, _ExceptionWrapper):
# raise the exception using the caller context
raise result.error_string, None, result.tb
else:
return result
def spawn(fn, *args, **kwargs):
# wrap the function
fn = wrap_errors(fn)
g = threadpool.spawn(fn, *args, **kwargs)
# and the asynresult
g._get = g.get
g.get = types.MethodType(get_with_exception, g)
return g
Upvotes: 1