{"record":{"id":"8692b57e0eaf2a3c","repo":"opendatalab/MinerU","slug":"task-wait-handle-is-unavailable","errorCode":null,"errorMessage":"Task wait handle is unavailable","messagePattern":"Task wait handle is unavailable","errorType":"exception","errorClass":"TaskWaitAbortedError","httpStatus":503,"severity":"warning","filePath":"mineru/cli/fast_api.py","lineNumber":1025,"sourceCode":"        self,\n        task: AsyncParseTask,\n        request: Request,\n    ) -> dict[str, Any]:\n        return task.to_status_payload(\n            request,\n            queued_ahead=self.get_queued_ahead(task.task_id),\n        )\n\n    async def wait_for_terminal_state(self, task_id: str) -> AsyncParseTask:\n        task = self.tasks.get(task_id)\n        if task is None:\n            raise TaskWaitAbortedError(\"Task not found\")\n        if is_task_terminal(task.status):\n            return task\n\n        task_event = self.task_events.get(task_id)\n        if task_event is None:\n            raise TaskWaitAbortedError(\"Task wait handle is unavailable\")\n\n        event_wait_task = asyncio.create_task(task_event.wait())\n        manager_wait_task = asyncio.create_task(self.manager_wakeup.wait())\n        done: set[asyncio.Task[Any]] = set()\n        pending: set[asyncio.Task[Any]] = set()\n        try:\n            done, pending = await asyncio.wait(\n                {event_wait_task, manager_wait_task},\n                return_when=asyncio.FIRST_COMPLETED,\n            )\n        finally:\n            for waiter in pending:\n                waiter.cancel()\n            if pending:\n                await asyncio.gather(*pending, return_exceptions=True)\n            for waiter in done:\n                with suppress(asyncio.CancelledError):\n                    waiter.result()","sourceCodeStart":1007,"sourceCodeEnd":1043,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/cli/fast_api.py#L1007-L1043","documentation":"TaskWaitAbortedError('Task wait handle is unavailable') raised in wait_for_terminal_state when the task record exists and is non-terminal, but there is no asyncio.Event for it in self.task_events. The event map and task map are meant to stay in sync, so this indicates an internal inconsistency: the event was already consumed/removed (e.g. by cleanup or shutdown paths) while the task entry lingers. End users cannot cause it directly; it is a server-side lifecycle bug or a race during manager teardown.","triggerScenarios":"A long-poll request in flight while the background cleanup or shutdown code removes the task's event but not (yet) the task; calling wait_for_terminal_state concurrently with manager shutdown; any code path that deletes from task_events independently of tasks.","commonSituations":"Hitting the status endpoint exactly as retention cleanup runs; server shutdown while clients are long-polling; upgrading between mineru versions where the cleanup logic changed how events are removed.","solutions":["Retry the status request once: the very next lookup usually either finds a terminal task (returns before the event is needed) or reports Task not found, both handled.","Resubmit the job if the retry keeps failing — the manager state for that task is corrupted.","Upgrade mineru: this is an internal invariant violation worth reporting upstream with logs of the cleanup timeline.","Avoid issuing long-poll waits during planned shutdown windows."],"exampleFix":"# before\nstatus = await wait_for_terminal_state(task_id)  # may raise TaskWaitAbortedError\n\n# after\ntry:\n    status = await wait_for_terminal_state(task_id)\nexcept TaskWaitAbortedError:\n    task = task_manager.get(task_id)  # fall back to one-shot lookup\n    status = task if task else resubmit()","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    task = await manager.wait_for_terminal_state(task_id)\nexcept TaskWaitAbortedError:\n    snapshot = manager.get(task_id)\n    if snapshot is None or is_task_terminal(snapshot.status):\n        return snapshot  # consistent state reached after one retry\n    raise","preventionTips":["Avoid long-poll waits while the server is shutting down or cleanup sweeps run.","Report recurring occurrences upstream — this is an internal invariant break, not a usage error."],"tags":["mineru","fastapi","task-management","race-condition","internal-error"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}