diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/concurrent/futures/_base.py | 5 | ||||
-rw-r--r-- | Lib/test/test_concurrent_futures.py | 16 |
2 files changed, 20 insertions, 1 deletions
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 8f155f0..6001e3b 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -404,7 +404,10 @@ class Future(object): if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]: self._done_callbacks.append(fn) return - fn(self) + try: + fn(self) + except Exception: + LOGGER.exception('exception calling callback for %r', self) def result(self, timeout=None): """Return the result of the call that the future represents. diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py index 3c963df..212ccd8 100644 --- a/Lib/test/test_concurrent_futures.py +++ b/Lib/test/test_concurrent_futures.py @@ -1087,6 +1087,22 @@ class FutureTests(BaseTestCase): f.add_done_callback(fn) self.assertTrue(was_cancelled) + def test_done_callback_raises_already_succeeded(self): + with test.support.captured_stderr() as stderr: + def raising_fn(callback_future): + raise Exception('doh!') + + f = Future() + + # Set the result first to simulate a future that runs instantly, + # effectively allowing the callback to be run immediately. + f.set_result(5) + f.add_done_callback(raising_fn) + + self.assertIn('exception calling callback for', stderr.getvalue()) + self.assertIn('doh!', stderr.getvalue()) + + def test_repr(self): self.assertRegex(repr(PENDING_FUTURE), '<Future at 0x[0-9a-f]+ state=pending>') |