mirror of
https://github.com/python/cpython.git
synced 2025-08-30 05:35:08 +00:00
bpo-28699: fix abnormal behaviour of pools in multiprocessing.pool (GH-693)
an exception raised at the very first of an iterable would cause pools behave abnormally (swallow the exception or hang)
This commit is contained in:
parent
ec1f5df46e
commit
794623bdb2
3 changed files with 117 additions and 25 deletions
|
@ -1755,6 +1755,8 @@ class CountedObject(object):
|
|||
class SayWhenError(ValueError): pass
|
||||
|
||||
def exception_throwing_generator(total, when):
|
||||
if when == -1:
|
||||
raise SayWhenError("Somebody said when")
|
||||
for i in range(total):
|
||||
if i == when:
|
||||
raise SayWhenError("Somebody said when")
|
||||
|
@ -1833,6 +1835,32 @@ class _TestPool(BaseTestCase):
|
|||
except multiprocessing.TimeoutError:
|
||||
self.fail("pool.map_async with chunksize stalled on null list")
|
||||
|
||||
def test_map_handle_iterable_exception(self):
|
||||
if self.TYPE == 'manager':
|
||||
self.skipTest('test not appropriate for {}'.format(self.TYPE))
|
||||
|
||||
# SayWhenError seen at the very first of the iterable
|
||||
with self.assertRaises(SayWhenError):
|
||||
self.pool.map(sqr, exception_throwing_generator(1, -1), 1)
|
||||
# again, make sure it's reentrant
|
||||
with self.assertRaises(SayWhenError):
|
||||
self.pool.map(sqr, exception_throwing_generator(1, -1), 1)
|
||||
|
||||
with self.assertRaises(SayWhenError):
|
||||
self.pool.map(sqr, exception_throwing_generator(10, 3), 1)
|
||||
|
||||
class SpecialIterable:
|
||||
def __iter__(self):
|
||||
return self
|
||||
def __next__(self):
|
||||
raise SayWhenError
|
||||
def __len__(self):
|
||||
return 1
|
||||
with self.assertRaises(SayWhenError):
|
||||
self.pool.map(sqr, SpecialIterable(), 1)
|
||||
with self.assertRaises(SayWhenError):
|
||||
self.pool.map(sqr, SpecialIterable(), 1)
|
||||
|
||||
def test_async(self):
|
||||
res = self.pool.apply_async(sqr, (7, TIMEOUT1,))
|
||||
get = TimingWrapper(res.get)
|
||||
|
@ -1863,6 +1891,13 @@ class _TestPool(BaseTestCase):
|
|||
if self.TYPE == 'manager':
|
||||
self.skipTest('test not appropriate for {}'.format(self.TYPE))
|
||||
|
||||
# SayWhenError seen at the very first of the iterable
|
||||
it = self.pool.imap(sqr, exception_throwing_generator(1, -1), 1)
|
||||
self.assertRaises(SayWhenError, it.__next__)
|
||||
# again, make sure it's reentrant
|
||||
it = self.pool.imap(sqr, exception_throwing_generator(1, -1), 1)
|
||||
self.assertRaises(SayWhenError, it.__next__)
|
||||
|
||||
it = self.pool.imap(sqr, exception_throwing_generator(10, 3), 1)
|
||||
for i in range(3):
|
||||
self.assertEqual(next(it), i*i)
|
||||
|
@ -1889,6 +1924,17 @@ class _TestPool(BaseTestCase):
|
|||
if self.TYPE == 'manager':
|
||||
self.skipTest('test not appropriate for {}'.format(self.TYPE))
|
||||
|
||||
# SayWhenError seen at the very first of the iterable
|
||||
it = self.pool.imap_unordered(sqr,
|
||||
exception_throwing_generator(1, -1),
|
||||
1)
|
||||
self.assertRaises(SayWhenError, it.__next__)
|
||||
# again, make sure it's reentrant
|
||||
it = self.pool.imap_unordered(sqr,
|
||||
exception_throwing_generator(1, -1),
|
||||
1)
|
||||
self.assertRaises(SayWhenError, it.__next__)
|
||||
|
||||
it = self.pool.imap_unordered(sqr,
|
||||
exception_throwing_generator(10, 3),
|
||||
1)
|
||||
|
@ -1970,7 +2016,7 @@ class _TestPool(BaseTestCase):
|
|||
except Exception as e:
|
||||
exc = e
|
||||
else:
|
||||
raise AssertionError('expected RuntimeError')
|
||||
self.fail('expected RuntimeError')
|
||||
self.assertIs(type(exc), RuntimeError)
|
||||
self.assertEqual(exc.args, (123,))
|
||||
cause = exc.__cause__
|
||||
|
@ -1984,6 +2030,17 @@ class _TestPool(BaseTestCase):
|
|||
sys.excepthook(*sys.exc_info())
|
||||
self.assertIn('raise RuntimeError(123) # some comment',
|
||||
f1.getvalue())
|
||||
# _helper_reraises_exception should not make the error
|
||||
# a remote exception
|
||||
with self.Pool(1) as p:
|
||||
try:
|
||||
p.map(sqr, exception_throwing_generator(1, -1), 1)
|
||||
except Exception as e:
|
||||
exc = e
|
||||
else:
|
||||
self.fail('expected SayWhenError')
|
||||
self.assertIs(type(exc), SayWhenError)
|
||||
self.assertIs(exc.__cause__, None)
|
||||
|
||||
@classmethod
|
||||
def _test_wrapped_exception(cls):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue