Skip to content

Commit 0a28c0d

Browse files
jhaydamanasvetlov
authored andcommitted
bpo-33238: Add InvalidStateError to concurrent.futures. (GH-7056)
Future.set_result and Future.set_exception now raise InvalidStateError if the futures are not pending or running. This mirrors the behavior of asyncio.Future, and prevents AssertionErrors in asyncio.wrap_future when set_result is called multiple times.
1 parent bb9474f commit 0a28c0d

File tree

6 files changed

+59
-6
lines changed

6 files changed

+59
-6
lines changed

Doc/library/concurrent.futures.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ The :class:`Future` class encapsulates the asynchronous execution of a callable.
380380
This method should only be used by :class:`Executor` implementations and
381381
unit tests.
382382

383+
.. versionchanged:: 3.8
384+
This method raises
385+
:exc:`concurrent.futures.InvalidStateError` if the :class:`Future` is
386+
already done.
387+
383388
.. method:: set_exception(exception)
384389

385390
Sets the result of the work associated with the :class:`Future` to the
@@ -388,6 +393,10 @@ The :class:`Future` class encapsulates the asynchronous execution of a callable.
388393
This method should only be used by :class:`Executor` implementations and
389394
unit tests.
390395

396+
.. versionchanged:: 3.8
397+
This method raises
398+
:exc:`concurrent.futures.InvalidStateError` if the :class:`Future` is
399+
already done.
391400

392401
Module Functions
393402
----------------
@@ -466,6 +475,13 @@ Exception classes
466475

467476
.. versionadded:: 3.7
468477

478+
.. exception:: InvalidStateError
479+
480+
Raised when an operation is performed on a future that is not allowed
481+
in the current state.
482+
483+
.. versionadded:: 3.8
484+
469485
.. currentmodule:: concurrent.futures.thread
470486

471487
.. exception:: BrokenThreadPool

Lib/asyncio/base_futures.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
__all__ = ()
22

3-
import concurrent.futures._base
3+
import concurrent.futures
44
import reprlib
55

66
from . import format_helpers
77

8-
Error = concurrent.futures._base.Error
98
CancelledError = concurrent.futures.CancelledError
109
TimeoutError = concurrent.futures.TimeoutError
11-
12-
13-
class InvalidStateError(Error):
14-
"""The operation is not allowed in this state."""
10+
InvalidStateError = concurrent.futures.InvalidStateError
1511

1612

1713
# States for Future.

Lib/concurrent/futures/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ALL_COMPLETED,
1111
CancelledError,
1212
TimeoutError,
13+
InvalidStateError,
1314
BrokenExecutor,
1415
Future,
1516
Executor,

Lib/concurrent/futures/_base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class TimeoutError(Error):
5353
"""The operation exceeded the given deadline."""
5454
pass
5555

56+
class InvalidStateError(Error):
57+
"""The operation is not allowed in this state."""
58+
pass
59+
5660
class _Waiter(object):
5761
"""Provides the event that wait() and as_completed() block on."""
5862
def __init__(self):
@@ -513,6 +517,8 @@ def set_result(self, result):
513517
Should only be used by Executor implementations and unit tests.
514518
"""
515519
with self._condition:
520+
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
521+
raise InvalidStateError('{}: {!r}'.format(self._state, self))
516522
self._result = result
517523
self._state = FINISHED
518524
for waiter in self._waiters:
@@ -526,6 +532,8 @@ def set_exception(self, exception):
526532
Should only be used by Executor implementations and unit tests.
527533
"""
528534
with self._condition:
535+
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
536+
raise InvalidStateError('{}: {!r}'.format(self._state, self))
529537
self._exception = exception
530538
self._state = FINISHED
531539
for waiter in self._waiters:

Lib/test/test_concurrent_futures.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,34 @@ def notification():
12061206
self.assertTrue(isinstance(f1.exception(timeout=5), OSError))
12071207
t.join()
12081208

1209+
def test_multiple_set_result(self):
1210+
f = create_future(state=PENDING)
1211+
f.set_result(1)
1212+
1213+
with self.assertRaisesRegex(
1214+
futures.InvalidStateError,
1215+
'FINISHED: <Future at 0x[0-9a-f]+ '
1216+
'state=finished returned int>'
1217+
):
1218+
f.set_result(2)
1219+
1220+
self.assertTrue(f.done())
1221+
self.assertEqual(f.result(), 1)
1222+
1223+
def test_multiple_set_exception(self):
1224+
f = create_future(state=PENDING)
1225+
e = ValueError()
1226+
f.set_exception(e)
1227+
1228+
with self.assertRaisesRegex(
1229+
futures.InvalidStateError,
1230+
'FINISHED: <Future at 0x[0-9a-f]+ '
1231+
'state=finished raised ValueError>'
1232+
):
1233+
f.set_exception(Exception())
1234+
1235+
self.assertEqual(f.exception(), e)
1236+
12091237

12101238
@test.support.reap_threads
12111239
def test_main():
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add ``InvalidStateError`` to :mod:`concurrent.futures`.
2+
``Future.set_result`` and ``Future.set_exception`` now raise
3+
``InvalidStateError`` if the futures are not pending or running. Patch by
4+
Jason Haydaman.

0 commit comments

Comments
 (0)