mirror of
https://github.com/python/cpython.git
synced 2025-07-23 11:15:24 +00:00
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.
This commit is contained in:
parent
bb9474f1fb
commit
0a28c0d12e
6 changed files with 59 additions and 6 deletions
|
@ -380,6 +380,11 @@ The :class:`Future` class encapsulates the asynchronous execution of a callable.
|
||||||
This method should only be used by :class:`Executor` implementations and
|
This method should only be used by :class:`Executor` implementations and
|
||||||
unit tests.
|
unit tests.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
This method raises
|
||||||
|
:exc:`concurrent.futures.InvalidStateError` if the :class:`Future` is
|
||||||
|
already done.
|
||||||
|
|
||||||
.. method:: set_exception(exception)
|
.. method:: set_exception(exception)
|
||||||
|
|
||||||
Sets the result of the work associated with the :class:`Future` to the
|
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.
|
||||||
This method should only be used by :class:`Executor` implementations and
|
This method should only be used by :class:`Executor` implementations and
|
||||||
unit tests.
|
unit tests.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
This method raises
|
||||||
|
:exc:`concurrent.futures.InvalidStateError` if the :class:`Future` is
|
||||||
|
already done.
|
||||||
|
|
||||||
Module Functions
|
Module Functions
|
||||||
----------------
|
----------------
|
||||||
|
@ -466,6 +475,13 @@ Exception classes
|
||||||
|
|
||||||
.. versionadded:: 3.7
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
.. exception:: InvalidStateError
|
||||||
|
|
||||||
|
Raised when an operation is performed on a future that is not allowed
|
||||||
|
in the current state.
|
||||||
|
|
||||||
|
.. versionadded:: 3.8
|
||||||
|
|
||||||
.. currentmodule:: concurrent.futures.thread
|
.. currentmodule:: concurrent.futures.thread
|
||||||
|
|
||||||
.. exception:: BrokenThreadPool
|
.. exception:: BrokenThreadPool
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
__all__ = ()
|
__all__ = ()
|
||||||
|
|
||||||
import concurrent.futures._base
|
import concurrent.futures
|
||||||
import reprlib
|
import reprlib
|
||||||
|
|
||||||
from . import format_helpers
|
from . import format_helpers
|
||||||
|
|
||||||
Error = concurrent.futures._base.Error
|
|
||||||
CancelledError = concurrent.futures.CancelledError
|
CancelledError = concurrent.futures.CancelledError
|
||||||
TimeoutError = concurrent.futures.TimeoutError
|
TimeoutError = concurrent.futures.TimeoutError
|
||||||
|
InvalidStateError = concurrent.futures.InvalidStateError
|
||||||
|
|
||||||
class InvalidStateError(Error):
|
|
||||||
"""The operation is not allowed in this state."""
|
|
||||||
|
|
||||||
|
|
||||||
# States for Future.
|
# States for Future.
|
||||||
|
|
|
@ -10,6 +10,7 @@ from concurrent.futures._base import (FIRST_COMPLETED,
|
||||||
ALL_COMPLETED,
|
ALL_COMPLETED,
|
||||||
CancelledError,
|
CancelledError,
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
|
InvalidStateError,
|
||||||
BrokenExecutor,
|
BrokenExecutor,
|
||||||
Future,
|
Future,
|
||||||
Executor,
|
Executor,
|
||||||
|
|
|
@ -53,6 +53,10 @@ class TimeoutError(Error):
|
||||||
"""The operation exceeded the given deadline."""
|
"""The operation exceeded the given deadline."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class InvalidStateError(Error):
|
||||||
|
"""The operation is not allowed in this state."""
|
||||||
|
pass
|
||||||
|
|
||||||
class _Waiter(object):
|
class _Waiter(object):
|
||||||
"""Provides the event that wait() and as_completed() block on."""
|
"""Provides the event that wait() and as_completed() block on."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -513,6 +517,8 @@ class Future(object):
|
||||||
Should only be used by Executor implementations and unit tests.
|
Should only be used by Executor implementations and unit tests.
|
||||||
"""
|
"""
|
||||||
with self._condition:
|
with self._condition:
|
||||||
|
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
|
||||||
|
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
||||||
self._result = result
|
self._result = result
|
||||||
self._state = FINISHED
|
self._state = FINISHED
|
||||||
for waiter in self._waiters:
|
for waiter in self._waiters:
|
||||||
|
@ -526,6 +532,8 @@ class Future(object):
|
||||||
Should only be used by Executor implementations and unit tests.
|
Should only be used by Executor implementations and unit tests.
|
||||||
"""
|
"""
|
||||||
with self._condition:
|
with self._condition:
|
||||||
|
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
|
||||||
|
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
||||||
self._exception = exception
|
self._exception = exception
|
||||||
self._state = FINISHED
|
self._state = FINISHED
|
||||||
for waiter in self._waiters:
|
for waiter in self._waiters:
|
||||||
|
|
|
@ -1206,6 +1206,34 @@ class FutureTests(BaseTestCase):
|
||||||
self.assertTrue(isinstance(f1.exception(timeout=5), OSError))
|
self.assertTrue(isinstance(f1.exception(timeout=5), OSError))
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
|
def test_multiple_set_result(self):
|
||||||
|
f = create_future(state=PENDING)
|
||||||
|
f.set_result(1)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
futures.InvalidStateError,
|
||||||
|
'FINISHED: <Future at 0x[0-9a-f]+ '
|
||||||
|
'state=finished returned int>'
|
||||||
|
):
|
||||||
|
f.set_result(2)
|
||||||
|
|
||||||
|
self.assertTrue(f.done())
|
||||||
|
self.assertEqual(f.result(), 1)
|
||||||
|
|
||||||
|
def test_multiple_set_exception(self):
|
||||||
|
f = create_future(state=PENDING)
|
||||||
|
e = ValueError()
|
||||||
|
f.set_exception(e)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
futures.InvalidStateError,
|
||||||
|
'FINISHED: <Future at 0x[0-9a-f]+ '
|
||||||
|
'state=finished raised ValueError>'
|
||||||
|
):
|
||||||
|
f.set_exception(Exception())
|
||||||
|
|
||||||
|
self.assertEqual(f.exception(), e)
|
||||||
|
|
||||||
|
|
||||||
@test.support.reap_threads
|
@test.support.reap_threads
|
||||||
def test_main():
|
def test_main():
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add ``InvalidStateError`` to :mod:`concurrent.futures`.
|
||||||
|
``Future.set_result`` and ``Future.set_exception`` now raise
|
||||||
|
``InvalidStateError`` if the futures are not pending or running. Patch by
|
||||||
|
Jason Haydaman.
|
Loading…
Add table
Add a link
Reference in a new issue