v1.5.0 — @harperfast/prerender-browser
@harperfast/prerender-browser v1.5.0
Per-host claim/result resilience: the worker no longer busy-loops a drained queue, and it
backs off (circuit-breaks) a Harper host that is overloaded (503) or unreachable — while
picking up freshly-enqueued work promptly. Additive, backward-compatible config; no API
changes to startWorker.
What & why
Previously the claim loop re-hit /render_queue/claim back-to-back whenever local queue
state still said queued (e.g. the empty MQTT status hadn't propagated yet), and a
non-200 just logged and immediately retried — hammering an already-struggling host. The
idle path also slept an un-woken random up-to-60s, so an idle worker could take up to a
minute to pick up a new job.
This release introduces one per-host availability model (HostHealth) fed by three
signals:
- Idle backoff. A zero-job claim (or an
empty/pausedqueue_statusmessage) parks
that host foridleMs(default 15s, jittered) instead of re-polling immediately. The
drained-queue busy-loop is gone. - Circuit breaker. A
503/429/502/504or a network error on claim or
job_resultopens a per-host circuit with exponential, jittered backoff betweenminMs
andmaxMs, honoring a (positive, capped)Retry-After, with a half-open probe on
recovery. Because claim and result share the per-host circuit, a result 503 also pauses
claims to that host (no point claiming work you can't return). - Interruptible idle sleep. The wait wakes the instant a host becomes eligible — a
queuedstatus arrives, or a circuit clears after a successful result POST — so new work
is picked up without waiting out a timer.
Plus: job_result POSTs now retry transient failures (bounded by resultRetries and
the job lease) so an expensive render isn't dropped on a blip, and the payload (incl. gzip)
is encoded once across retries.
New config (all optional, under startWorker({ backoff }))
| option | default | meaning |
|---|---|---|
idleMs |
15000 | delay before re-polling a host that reported empty/no work |
minMs |
1000 | base circuit backoff on the first 503/unreachable |
maxMs |
30000 | circuit backoff cap |
pausedMs |
30000 | re-poll delay when a host reports paused |
maxIdleMs |
60000 | cap on the idle sleep between poll re-evaluations |
resultRetries |
3 | max job_result POST retries on 503/5xx/network before dropping |
Notes
- Consumer-side only — no plugin/response-shape change, so it interoperates with any
running plugin. Queue status is reconciled from MQTT (timestamp-guarded against
out-of-order/retained delivery) and from claim outcomes. render()awaitssendResult()within a concurrency slot; result retries are capped at
5s/attempt so a failing host can't pin slots (host-level backoff still applies via the
shared circuit).- 42/42 tests pass; lint + format clean.
dist/is built at release time (gitignored).