summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Martin <ABitMoreDepth@users.noreply.github.com>2019-05-22 21:29:02 (GMT)
committerAntoine Pitrou <antoine@python.org>2019-05-22 21:29:02 (GMT)
commit2a3a2ece502c05ea33c95dd0db497189e0354bfd (patch)
tree76436af78c3c003166a1e4c353feb03c67314928
parentd8a82e2897b735e2b7e9e086f1d709365a2ad72c (diff)
downloadcpython-2a3a2ece502c05ea33c95dd0db497189e0354bfd.zip
cpython-2a3a2ece502c05ea33c95dd0db497189e0354bfd.tar.gz
cpython-2a3a2ece502c05ea33c95dd0db497189e0354bfd.tar.bz2
bpo-33110: Catch errors raised when running add_done_callback on already completed futures (GH-13141)
Wrap the callback call within the `add_done_callback` function within concurrent.futures, in order to behave in an identical manner to callbacks added to a running future are triggered once it has completed.
-rw-r--r--Lib/concurrent/futures/_base.py5
-rw-r--r--Lib/test/test_concurrent_futures.py16
-rw-r--r--Misc/NEWS.d/next/Library/2019-05-06-22-34-47.bpo-33110.rSJSCh.rst1
3 files changed, 21 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>')
diff --git a/Misc/NEWS.d/next/Library/2019-05-06-22-34-47.bpo-33110.rSJSCh.rst b/Misc/NEWS.d/next/Library/2019-05-06-22-34-47.bpo-33110.rSJSCh.rst
new file mode 100644
index 0000000..f1e2460
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-06-22-34-47.bpo-33110.rSJSCh.rst
@@ -0,0 +1 @@
+Handle exceptions raised by functions added by concurrent.futures add_done_callback correctly when the Future has already completed.