Issue 9244: multiprocessing.pool: Worker crashes if result can't be encoded (original) (raw)
This looks pretty reasonable to my untrained eye. I successfully applied and ran the test suite.
To be clear, the errback change and the unpickleable result change are actually orthogonal, right? Anyway, I'm not really familiar with the protocol here, but assuming that you're open to code review:
- def apply_async(self, func, args=(), kwds={}, callback=None):
- def apply_async(self, func, args=(), kwds={}, callback=None,
error_callback=None): ''' Asynchronous equivalent of `apply()` builtin ''' assert self._state == RUN
result = ApplyResult(self._cache, callback)
result = ApplyResult(self._cache, callback, error_callback) self._taskqueue.put(([(result._job, None, func, args, kwds)], None)) return result
Sure. Why not add an error_callback for map_async as well?
- def init(self, cache, callback):
- def init(self, cache, callback, error_callback=None): self._cond = threading.Condition(threading.Lock()) self._job = job_counter.next() self._cache = cache self._ready = False self._callback = callback
self._errback = error_callback cache[self._job] = self
Any reason you chose to use a different internal name (errback versus error_callback)? It seems cleaner to me to be consistent about the name.
def sqr(x, wait=0.0): time.sleep(wait) return x*x + class _TestPool(BaseTestCase): def test_apply(self): @@ -1020,6 +1021,7 @@ class _TestPool(BaseTestCase): self.assertEqual(get(), 49) self.assertTimingAlmostEqual(get.elapsed, TIMEOUT1)
def test_async_timeout(self):
In general, I'm wary of nonessential whitespace changes... did you mean to include these?
scratchpad = [None]
def errback(exc):
scratchpad[0] = exc
res = p.apply_async(raising, error_callback=errback)
self.assertRaises(KeyError, res.get)
self.assertTrue(scratchpad[0])
self.assertIsInstance(scratchpad[0], KeyError)
p.close()
Using "assertTrue" seems misleading. "assertIsNotNone" is what you really mean, right? Although, I believe that's redundant, since presumably self.assertIsInstance(None, KeyError) will error out anyway (I haven't verified this).
- def test_unpickleable_result(self):
from multiprocessing.pool import MaybeEncodingError
p = multiprocessing.Pool(2)
# Make sure we don't lose pool processes because of encoding errors.
for iteration in xrange(20):
scratchpad = [None]
def errback(exc):
scratchpad[0] = exc
res = p.apply_async(unpickleable_result, error_callback=errback)
self.assertRaises(MaybeEncodingError, res.get)
wrapped = scratchpad[0]
self.assertTrue(wrapped)
Again, assertTrue is probably not what you want, and is probably redundant.
self.assertIsInstance(scratchpad[0], MaybeEncodingError)
Why use scratchpad[0] rather than wrapped?
self.assertIsNotNone(wrapped.exc)
self.assertIsNotNone(wrapped.value)
Under what circumstances would these be None? (Perhaps you want wrapped.exc != 'None'?) The initializer for MaybeEncodingError enforces the invariant that exc/value are strings, right?
class _TestPoolWorkerLifetime(BaseTestCase):
ALLOWED_TYPES = ('processes', )
Three line breaks there seems excessive.
To be clear, the errback change and the unpickleable result change are actually orthogonal, right?
Yes, it could be a separate issue. Jesse, do you think I should I open up a separate issue for this?
Why not add an error_callback for map_async as well?
That's a good idea!
Any reason you chose to use a different internal name (errback versus error_callback)? It seems cleaner to me to be consistent about the name.
It was actually a mistake. The argument was errback
before, so
it's just a leftover from the previous name.
In general, I'm wary of nonessential whitespace changes... did you mean to include these?
Of course not.
Using "assertTrue" seems misleading. "assertIsNotNone" is what really mean, right? Although, I believe that's redundant, since presumably self.assertIsInstance(None, KeyError) will error out anyway (I haven't verified this).
bool(KeyError("foo")) is True and bool(None) is False, so it works either way. It could theoretically result in a false negative if the exception class tested overrides nonzero, but that is unlikely to happen as the target always returns KeyError anyway (and the test below ensures it) It's just a habit of mine, unless I really want to test for Noneness, I just use assertTrue, but I'm not against changing it to assertIsNotNone either.
Under what circumstances would these be None? (Perhaps you want wrapped.exc != 'None'?) The initializer for MaybeEncodingError enforces the invariant that exc/value are strings right? It's just to test that these are actually set to something. Even an empty string passes with assertIsNone instead of assertTrue. Maybe it's better to test the values set, but I didn't bother.