python/cpython · error · TypeError

A future is required for destination argument

Error message

A future is required for destination argument

What it means

The destination-side validation of _chain_future(): the object that will receive the source's result must be an asyncio.Future or concurrent.futures.Future. Because chaining copies state into destination, a non-future destination has no set_result/set_exception API to copy into.

Source

Thrown at Lib/asyncio/futures.py:384

        dest.cancel()
    elif exception is not None:
        dest.set_exception(_convert_future_exc(exception))
    else:
        dest.set_result(result)

def _chain_future(source, destination):
    """Chain two futures so that when one completes, so does the other.

    The result (or exception) of source will be copied to destination.
    If destination is cancelled, source gets cancelled too.
    Compatible with both asyncio.Future and concurrent.futures.Future.
    """
    if not isfuture(source) and not isinstance(source,
                                               concurrent.futures.Future):
        raise TypeError('A future is required for source argument')
    if not isfuture(destination) and not isinstance(destination,
                                                    concurrent.futures.Future):
        raise TypeError('A future is required for destination argument')
    source_loop = _get_loop(source) if isfuture(source) else None
    dest_loop = _get_loop(destination) if isfuture(destination) else None

    def _set_state(future, other):
        if isfuture(future):
            _copy_future_state(other, future)
        else:
            _set_concurrent_future_state(future, other)

    def _call_check_cancel(destination):
        if destination.cancelled():
            if source_loop is None or source_loop is events._get_running_loop():
                source.cancel()
            else:
                source_loop.call_soon_threadsafe(source.cancel)

    def _call_set_state(source):
        if (destination.cancelled() and

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Create the destination future first (loop.create_future() or executor.submit(...))
  2. Prefer public APIs: asyncio.wrap_future(source) already builds a correct destination
  3. Double-check argument order: _chain_future(source, destination)

Example fix

# before
asyncio.futures._chain_future(task, result_callback)  # callback is not a future

# after
dest = loop.create_future()
dest.add_done_callback(result_callback)
asyncio.futures._chain_future(task, dest)
Defensive patterns

Strategy: type-guard

Validate before calling

import asyncio, concurrent.futures
ok = asyncio.isfuture(dst) or isinstance(dst, concurrent.futures.Future)

Type guard

def is_chainable_destination(obj) -> bool:
    import asyncio, concurrent.futures
    return asyncio.isfuture(obj) or isinstance(obj, concurrent.futures.Future)

Prevention

When it happens

Trigger: asyncio.futures._chain_future(task, some_coroutine); wrap_future implemented by hand with the arguments swapped; passing a list/None/callback as destination in custom bridging code.

Common situations: Argument order confusion in private API use; building a concurrent.futures bridge where the destination executor future was never created (submit() result forgotten); tests passing Mock objects.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/73396fd3b1ffbd92. Report an issue: GitHub.