python/cpython · error · ValueError
Set of Tasks/Futures is empty.
Error message
Set of Tasks/Futures is empty.
What it means
ValueError raised by asyncio.wait() when the collection of awaitables is empty. With nothing to wait on there is no meaningful event to suspend for, and returning immediately could mask logic errors, so the API refuses the call.
Source
Thrown at Lib/asyncio/tasks.py:422
async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED):
"""Wait for the Futures or Tasks given by fs to complete.
The fs iterable must not be empty.
Returns two sets of Future: (done, pending).
Usage:
done, pending = await asyncio.wait(fs)
Note: This does not raise TimeoutError! Futures that aren't done
when the timeout occurs are returned in the second set.
"""
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError('Set of Tasks/Futures is empty.')
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}')
fs = set(fs)
if any(coroutines.iscoroutine(f) for f in fs):
raise TypeError("Passing coroutines is forbidden, use tasks explicitly.")
loop = events.get_running_loop()
return await _wait(fs, timeout, return_when, loop)
def _release_waiter(waiter, *args):
if not waiter.done():
waiter.set_result(None)
async def wait_for(fut, timeout):View on GitHub (pinned to bc6749cc3b)
Solutions
- Guard the empty case before calling: if not tasks: return set(), set() (or skip the wait entirely)
- When draining dynamically produced work, loop while the workset is non-empty instead of calling wait unconditionally
- If you only need 'wait until all currently known tasks finish', prefer asyncio.gather(*tasks) which accepts zero args only via an empty call — validate the same way
Example fix
// before
done, pending = await asyncio.wait(running) # running can be []
// after
if running:
done, pending = await asyncio.wait(running)
else:
done, pending = set(), set() Defensive patterns
Strategy: validation
Validate before calling
async def wait_all(tasks):
tasks = list(tasks)
if not tasks:
return set(), set()
return await asyncio.wait(tasks) Type guard
null
Try / catch
null
Prevention
- Filter empty worksets before wait(); make the empty case explicit in callers
- Loop 'while tasks: ...' when draining dynamic worklists
- Write a unit test for the zero-task path of every fan-out helper
When it happens
Trigger: await asyncio.wait([]); asyncio.wait(tasks) where tasks was built from a filter that matched nothing (e.g. all work already completed, an empty queue drain, zero subscribers).
Common situations: Fan-out loops over dynamic worksets that occasionally empty out; batch pipelines after the final batch; code that assumed wait([]) blocks or returns (done=set(), pending=set()) without error.
Related errors
- expect a list of futures, not {type(fs).__name__}
- Unimplemented ioctl request
- profiling_trace: Placeholder pattern not found in {js_path.n
- offset must be a non-negative integer (got {!r})
- server_hostname is only meaningful with ssl
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/f2216f34a7a5ce90.
Report an issue: GitHub.