{"record":{"id":"530a469a85daecab","repo":"opendatalab/MinerU","slug":"pdf-image-rendering-timeout-after-timeout-s-for-p","errorCode":null,"errorMessage":"PDF image rendering timeout after {timeout}s for pages {start_page_id + 1}-{end_page_id + 1}","messagePattern":"PDF image rendering timeout after (.+?)s for pages (.+?)-(.+?)","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"mineru/utils/pdf_image_tools.py","lineNumber":316,"sourceCode":"        futures = []\n        future_to_range = {}\n        for range_start, range_end in page_ranges:\n            future = _submit_pdf_render_task(\n                executor,\n                _load_images_from_pdf_worker,\n                pdf_bytes,\n                dpi,\n                range_start,\n                range_end,\n                image_type,\n            )\n            futures.append(future)\n            future_to_range[future] = range_start\n\n        _, not_done = wait(futures, timeout=timeout, return_when=ALL_COMPLETED)\n        if not_done:\n            recycle_executor = True\n            raise TimeoutError(\n                f\"PDF image rendering timeout after {timeout}s \"\n                f\"for pages {start_page_id + 1}-{end_page_id + 1}\"\n            )\n\n        all_results = []\n        for future in futures:\n            range_start = future_to_range[future]\n            images_list = future.result()\n            collected_image_lists.append(images_list)\n            all_results.append((range_start, images_list))\n\n        all_results.sort(key=lambda x: x[0])\n        images_list = []\n        for _, imgs in all_results:\n            images_list.extend(imgs)\n\n        collected_image_lists.clear()\n        return images_list","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/opendatalab/MinerU/blob/4fe4bde114a23ee5dd637eae99b767f4669bf58c/mineru/utils/pdf_image_tools.py#L298-L334","documentation":"TimeoutError raised by MineRU's parallel PDF page renderer when the executor futures that render page ranges to images do not all complete within the configured timeout (wait(..., timeout=timeout, return_when=ALL_COMPLETED) returns not_done futures). Rendering is split into page-range chunks submitted to a process/thread pool; the message reports the overall timeout and the 1-based page span requested. The executor is flagged for recycling (recycle_executor = True) because a worker is presumed stuck, and remaining futures are abandoned.","triggerScenarios":"Calling the PDF-to-image utility (mineru.utils.pdf_image_tools) with a very large or pathological PDF (hundreds/thousands of pages, huge page sizes, complex vector graphics) and the default or a low timeout; or with an executor whose workers are starved (CPU-bound competing processes, very low memory causing swapping, PyMuPDF deadlocks in forked workers).","commonSituations":"Batch servers processing scanned books at high DPI; containers with CPU limits where each worker gets a fraction of a core; memory exhaustion making rendering orders of magnitude slower; a hung worker from a known fork-after-import issue in the renderer.","solutions":["Raise the timeout parameter so it scales with page count and DPI (e.g. 30s per 50 pages as a starting heuristic), since the budget covers all ranges submitted together.","Reduce per-page cost: lower DPI, process the document in smaller end_page_id/start_page_id chunks instead of the whole file at once.","Give the process real resources: increase container CPU/memory limits, avoid oversubscribing the executor with unrelated work.","If it recurs deterministically on one file, that PDF is likely corrupt/pathological — try re-saving it with qpdf or Ghostscript, or render that page range separately to isolate the culprit page.","Retry once on a fresh process: the executor-recycling flag exists because a stuck worker often clears after a restart."],"exampleFix":"# before\nimages = pdf_image_tools...render(timeout=60)  # 500-page scanned book -> TimeoutError\n\n# after\nfor start in range(0, n_pages, 50):\n    images += render_range(start, min(start + 50, n_pages), dpi=150, timeout=120)","handlingStrategy":"retry","validationCode":"n_pages = get_page_count(pdf_bytes)\ntimeout = max(60, n_pages * 2)  # scale budget with document size\ndpi = 150 if n_pages > 200 else 200  # reduce cost for big docs","typeGuard":"def render_budget_ok(n_pages: int, dpi: int, timeout: int) -> bool:\n    # rough heuristic: ~0.5-2s per page at 150-200 dpi with healthy workers\n    return timeout >= n_pages * 1.5 * (dpi / 150.0)","tryCatchPattern":"for attempt in range(2):\n    try:\n        images = render_pages(pdf_bytes, start, end, dpi=dpi, timeout=timeout)\n        break\n    except TimeoutError:\n        if attempt == 1:\n            raise  # executor is recycled; retry once on a fresh one, then surface\n        timeout *= 2","preventionTips":["Scale the timeout with page count and DPI instead of using a fixed constant.","Chunk long documents (e.g. 50 pages per call) so one slow range cannot blow the whole budget.","Size container CPU/memory limits for parallel rendering; starving workers is the most common cause of deterministic timeouts.","Isolate pathological PDFs: re-save suspects with qpdf/Ghostscript before processing."],"tags":["pdf","timeout","rendering","performance","mineru"],"backgroundTag":null,"analyzedSha":"4fe4bde114a23ee5dd637eae99b767f4669bf58c","analyzedAt":"2026-08-14T21:29:18.456Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}