{"record":{"id":"88cf1b6c77343eff","repo":"PaddlePaddle/PaddleOCR","slug":"timed-out-after-timeoutms-ms-waiting-for-job-j","errorCode":null,"errorMessage":"Timed out after ${timeoutMs}ms waiting for job ${jobId}","messagePattern":"Timed out after (.+?)ms waiting for job (.+?)","errorType":"exception","errorClass":"PollTimeoutError","httpStatus":null,"severity":"warning","filePath":"api_sdk/typescript/src/internal/poller.ts","lineNumber":59,"sourceCode":"      const remaining = deadline - Date.now();\n      const data = await this.withPollTimeout(jobId, remaining, () => this.http.getJobStatus(jobId, signal, remaining));\n      const status = normalizeStatus(jobId, data);\n\n      if (status.state === \"done\") {\n        const jsonUrl = resultJsonUrl(data);\n        const resultRemaining = deadline - Date.now();\n        return await this.withPollTimeout(jobId, resultRemaining, () => this.http.fetchJsonl(jsonUrl, signal, resultRemaining));\n      }\n\n      if (status.state === \"failed\") {\n        throw new JobFailedError(jobId, status.errorMsg || \"Unknown error\");\n      }\n\n      await this.sleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal);\n      interval = Math.min(interval * MULTIPLIER, MAX_INTERVAL);\n    }\n\n    throw new PollTimeoutError(jobId, this.maxWaitTime);\n  }\n\n  async getStatus(jobId: string, signal?: AbortSignal): Promise<JobStatus> {\n    return normalizeStatus(jobId, await this.http.getJobStatus(jobId, signal));\n  }\n\n  async getBatchStatus(batchId: string, signal?: AbortSignal): Promise<BatchStatus> {\n    const data = await this.http.getBatchStatus(batchId, signal);\n    if (!isRecord(data) || !Array.isArray(data.extractResult)) {\n      throw new ResponseFormatError(\"Batch response is missing extractResult.\");\n    }\n    return {\n      batchId,\n      jobs: data.extractResult.map((item) => {\n        if (!isRecord(item) || typeof item.jobId !== \"string\") {\n          throw new ResponseFormatError(\"Batch extractResult item is missing jobId.\");\n        }\n        return normalizeStatus(item.jobId, item);","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/api_sdk/typescript/src/internal/poller.ts#L41-L77","documentation":"PollTimeoutError is thrown by the poller's main loop when the overall maxWaitTime budget is exhausted while the job is still pending or running — the wait loop deadline passed before the job reached 'done' or 'failed'. The jobId and the configured timeoutMs are exposed as properties. The job itself may still complete server-side; only the local wait gave up.","triggerScenarios":"Waiting on a large or queued document that legitimately takes longer than maxWaitTime (default budget); jobs queued behind rate limits; overloaded backend during peak hours; maxWaitTime accidentally set very low or in the wrong unit (seconds vs ms).","commonSituations":"1000-page PDFs processed slowly; free-tier queues during peak; developers passing 30 meaning 30 seconds but the SDK reading milliseconds; tight deadlines in serverless functions that must respond quickly.","solutions":["Raise the wait budget: waitForResult(jobId, { maxWaitTime: 10 * 60_000 }) — remember the unit is milliseconds","On timeout, do not resubmit: resume polling the same jobId later with getStatus(jobId) since the job keeps running server-side","Split huge documents into smaller submissions with narrower pageRanges so each finishes faster","For serverless hosts, poll asynchronously (persist jobId, poll on the next invocation) instead of blocking"],"exampleFix":"// before\nconst result = await poller.waitForResult(jobId); // default budget too small\n\n// after (resume instead of resubmitting)\nlet result;\ntry {\n  result = await poller.waitForResult(jobId, { maxWaitTime: 15 * 60_000 });\n} catch (e) {\n  if (e instanceof PollTimeoutError) {\n    result = await poller.waitForResult(e.jobId, { maxWaitTime: 15 * 60_000 });\n  }\n}","handlingStrategy":"retry","validationCode":"function budgetMatchesExpectation(maxWaitTimeMs: number, pages: number): boolean {\n  const msPerPage = 5_000; // conservative\n  return maxWaitTimeMs > pages * msPerPage;\n}","typeGuard":"function isPollTimeout(e: unknown): e is PollTimeoutError {\n  return e instanceof PollTimeoutError;\n}","tryCatchPattern":"try {\n  result = await poller.waitForResult(jobId, { maxWaitTime: 15 * 60_000 });\n} catch (e) {\n  if (e instanceof PollTimeoutError) {\n    // job still runs server-side: resume polling the SAME jobId, never resubmit\n    result = await poller.waitForResult(e.jobId, { maxWaitTime: 15 * 60_000 });\n  } else throw e;\n}","preventionTips":["Remember maxWaitTime is milliseconds — audit any value < 1000 for unit mistakes","Size the budget to the document (pages × per-page latency) with slack","Persist jobIds so waits can resume across process restarts instead of blocking"],"tags":["timeout","polling","job","typescript"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}