{"record":{"id":"f2216f34a7a5ce90","repo":"python/cpython","slug":"set-of-tasks-futures-is-empty","errorCode":null,"errorMessage":"Set of Tasks/Futures is empty.","messagePattern":"Set of Tasks/Futures is empty\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/tasks.py","lineNumber":422,"sourceCode":"\nasync def wait(fs, *, timeout=None, return_when=ALL_COMPLETED):\n    \"\"\"Wait for the Futures or Tasks given by fs to complete.\n\n    The fs iterable must not be empty.\n\n    Returns two sets of Future: (done, pending).\n\n    Usage:\n\n        done, pending = await asyncio.wait(fs)\n\n    Note: This does not raise TimeoutError! Futures that aren't done\n    when the timeout occurs are returned in the second set.\n    \"\"\"\n    if futures.isfuture(fs) or coroutines.iscoroutine(fs):\n        raise TypeError(f\"expect a list of futures, not {type(fs).__name__}\")\n    if not fs:\n        raise ValueError('Set of Tasks/Futures is empty.')\n    if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):\n        raise ValueError(f'Invalid return_when value: {return_when}')\n\n    fs = set(fs)\n\n    if any(coroutines.iscoroutine(f) for f in fs):\n        raise TypeError(\"Passing coroutines is forbidden, use tasks explicitly.\")\n\n    loop = events.get_running_loop()\n    return await _wait(fs, timeout, return_when, loop)\n\n\ndef _release_waiter(waiter, *args):\n    if not waiter.done():\n        waiter.set_result(None)\n\n\nasync def wait_for(fut, timeout):","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/tasks.py#L404-L440","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ndone, pending = await asyncio.wait(running)  # running can be []\n\n// after\nif running:\n    done, pending = await asyncio.wait(running)\nelse:\n    done, pending = set(), set()","handlingStrategy":"validation","validationCode":"async def wait_all(tasks):\n    tasks = list(tasks)\n    if not tasks:\n        return set(), set()\n    return await asyncio.wait(tasks)","typeGuard":"null","tryCatchPattern":"null","preventionTips":["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"],"tags":["asyncio","tasks","wait","validation"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}