Skip to main content

Lib/asyncio/futures.py

cpython 3.14 @ ab2d84fe1023/Lib/asyncio/futures.py

futures.py implements asyncio.Future, the primitive that bridges callback-based I/O with coroutine-based code. A Future wraps a single pending value that will be set exactly once. The file also exports isfuture, _future_repr_info, and wrap_future for interop with concurrent.futures.Future.

A Future passes through three states in one direction: PENDING to either FINISHED (via set_result or set_exception) or CANCELLED (via cancel). Once settled, the state never changes. Done callbacks registered with add_done_callback are scheduled onto the event loop with loop.call_soon rather than called inline, so they always execute after the current iteration of the event loop.

The __await__ method allows await future inside a coroutine. When the future is still pending it yields itself; the event loop captures that yielded value, registers a resume callback, and suspends the coroutine. When the future is finished, __await__ returns the result directly or re-raises the stored exception.

Map

LinesSymbolRolegopy
1-40Module header, state constantsPENDING = 0, CANCELLED = 1, FINISHED = 2; __all__ declaration; imports from events, exceptions, format_helpers.(not ported)
41-80isfuture / _future_repr_infoisfuture checks _asyncio_future_blocking; _future_repr_info builds the list of strings used by __repr__.(not ported)
81-180Future.__init__ / __repr__ / __del__Constructor binds the loop, sets _state = PENDING, initializes the callbacks list and _exception. __del__ logs a warning when a finished future with an exception is garbage-collected without the exception ever being retrieved.(not ported)
181-230cancel / cancelled / donecancel transitions PENDING -> CANCELLED and schedules callbacks; cancelled() and done() are simple state tests.(not ported)
231-270result / exceptionresult raises CancelledError if cancelled, InvalidStateError if pending, or the stored exception if finished with an error; otherwise returns the stored value. exception mirrors this without returning the value.(not ported)
271-310add_done_callback / remove_done_callback / _schedule_callbacksMaintains the _callbacks list; _schedule_callbacks drains it by passing each callback to loop.call_soon.(not ported)
311-350set_result / set_exception / __await__ / wrap_futureState-transition writers; __await__ is the coroutine protocol entry point; wrap_future wraps a concurrent.futures.Future by bridging its callback with loop.call_soon_threadsafe.(not ported)

Reading

State machine: cancel, set_result, set_exception (lines 181 to 310)

cpython 3.14 @ ab2d84fe1023/Lib/asyncio/futures.py#L181-310

class Future:
_state = _PENDING

def cancel(self, msg=None):
if self._state != _PENDING:
return False
self._state = _CANCELLED
self._cancel_message = msg
self.__schedule_callbacks()
return True

def set_result(self, result):
if self._state != _PENDING:
raise exceptions.InvalidStateError(
f'{self._state!r}: {self!r}')
self._result = result
self._state = _FINISHED
self.__schedule_callbacks()

def set_exception(self, exception):
if self._state != _PENDING:
raise exceptions.InvalidStateError(
f'{self._state!r}: {self!r}')
if isinstance(exception, type):
exception = exception()
if type(exception) is StopIteration:
raise TypeError(
'StopIteration interacts badly with generators '
'and cannot be raised into a Future')
self._exception = exception
self._exception_tb = exception.__traceback__
self._state = _FINISHED
self.__schedule_callbacks()
self.__log_traceback = True

All three methods are guarded by if self._state != _PENDING. This is the sole enforcement point for the one-way state machine: once a future is settled, calling any of the three methods raises InvalidStateError (or returns False for cancel). After the state transition, __schedule_callbacks is called to drain the callback list. Callbacks are not called directly here; they are handed to loop.call_soon, which queues them for the next iteration of the event loop. This guarantees that callbacks never execute synchronously on the stack of whoever called set_result.

set_exception rejects StopIteration explicitly. If a StopIteration propagated through await it would be misinterpreted by the generator machinery as a return value rather than an exception, silently swallowing the error. The check converts that silent bug into a hard TypeError.

result and exception retrieval (lines 231 to 270)

cpython 3.14 @ ab2d84fe1023/Lib/asyncio/futures.py#L231-270

def result(self):
if self._state == _CANCELLED:
exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED:
raise exceptions.InvalidStateError(
'Result is not ready.')
self.__log_traceback = False
if self._exception is not None:
raise self._exception.with_traceback(self._exception_tb)
return self._result

def exception(self):
if self._state == _CANCELLED:
exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED:
raise exceptions.InvalidStateError(
'Exception is not set.')
self.__log_traceback = False
return self._exception

result() clears the __log_traceback flag before re-raising. That flag is set by set_exception and checked in __del__: if a future is garbage-collected while __log_traceback is still True, the event loop emits a warning including the stored traceback. Calling result() or exception() counts as "acknowledging" the exception, which is why the flag is cleared here. This is the mechanism behind the "Future exception was never retrieved" warning seen in production asyncio applications.

__await__ coroutine protocol (lines 311 to 340)

cpython 3.14 @ ab2d84fe1023/Lib/asyncio/futures.py#L311-340

def __await__(self):
if not self.done():
self._asyncio_future_blocking = True
yield self # suspend: event loop will resume us
if not self.done():
raise RuntimeError(
"await wasn't used with future")
return self.result()

__iter__ = __await__

When a coroutine executes await some_future, Python calls some_future.__await__() and drives the resulting iterator. If the future is still pending, __await__ sets _asyncio_future_blocking = True and yields self back to the event loop. The event loop sees a yielded Future object (detected via isfuture), adds a done-callback that calls coro.send(None) when the future settles, and suspends the coroutine. When the future finishes, the callback resumes the coroutine which re-enters __await__, finds self.done() is True, and falls through to return self.result(). The return inside a generator body becomes the StopIteration.value that the coroutine runner extracts as the await expression's value.

__iter__ = __await__ makes Future usable with plain yield from as well, which was the protocol before async/await keywords existed. The _asyncio_future_blocking attribute is the handshake: the event loop checks it to distinguish a yielded Future from a yielded sub-generator.

gopy mirror

asyncio.Future is not yet ported to gopy. The state machine, callback scheduling, and coroutine protocol described here would map to a Go struct with a sync.Mutex-protected state field and a slice of callbacks forwarded to the event loop's run queue. The __await__ bridge is the most Go-specific piece: gopy coroutines are implemented as goroutines, so the yield/resume handshake would use a channel rather than generator suspension.

CPython 3.14 changes worth noting

Future.cancel grew the optional msg parameter in 3.9; the message is forwarded to CancelledError so callers can distinguish which future was cancelled. In 3.12, Future.__del__ was tightened to only log when the loop is still running, avoiding spurious warnings during interpreter shutdown. In 3.14, wrap_future correctly handles the case where the source concurrent.futures.Future is already cancelled at wrap time by immediately cancelling the returned asyncio.Future rather than registering a callback that would never fire.