Skip to content

Synchronous parse has no yield point, making CPU-metered runtimes hard to budget #585

Description

@fcbwilliams

Summary

pdfToMarkdown and friends are synchronous and run to completion in one go. On Cloudflare Workers
that is awkward: CPU time is metered per invocation (10 ms on the free plan, 30 s default on paid,
5 min maximum), and a synchronous parse holds the isolate for its entire duration with no opportunity
to yield, checkpoint, or report progress.

Measurement

Parse time scales roughly linearly with content (Node 25.9.0, process.hrtime.bigint(), so
indicative of magnitude rather than exact workerd figures):

paras=  60   pdf=0.02 MiB   parse=  101 ms
paras= 600   pdf=0.13 MiB   parse=  568 ms
paras=3000   pdf=0.62 MiB   parse= 2623 ms

Extrapolating, a 5 MB PDF lands near 20 s — inside the paid 30 s default but not comfortably — and a
25 MB one would exceed even the 5-minute maximum. On the free plan's 10 ms, even the smallest test
document is 10× over budget.

For contrast, the reason this matters specifically on Workers: a remote conversion service reached
over a binding is network I/O, which does not count toward CPU time at all. Moving that work
in-process converts a free operation into the metered one. That is a fair trade for losing a remote
dependency, but it needs an API that makes the cost manageable.

Ask

Some way to parse incrementally, so a caller can budget CPU. Rough options, no strong preference:

  1. An async page-at-a-time APIAsyncIterable<PageContent>, or a callback invoked per page,
    yielding to the event loop between pages. Lets a caller stop at a deadline and resume, or stream
    results.
  2. An AbortSignal checked mid-parse. The conversion functions already accept signal; if it is
    only checked at hop boundaries rather than during the page loop, a long single-document parse
    cannot actually be cancelled. Honouring it inside the loop would at least make a deadline
    enforceable.
  3. A documented cost model — even just "parse is O(content length), expect roughly N ms/MB" in the
    README would let consumers decide statically whether to do this inline or hand it to a queue.

(2) may already be true — I did not verify how granularly signal is checked, so treat that as a
question rather than a claim.

Note

Much of the actual parsing lives in pdf-codec, so a real incremental API probably needs changes
there too; filing here because this is the public surface consumers hold, and the shape of the
answer should be decided at this level.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions