Skip to content

feat(core): add timeout/signal options to waitForInstanceReady - #157

Merged
birme merged 3 commits into
mainfrom
feat/waitForInstanceReady-timeout
Sep 8, 2026
Merged

birme merged 3 commits into
mainfrom
feat/waitForInstanceReady-timeout

Conversation

@birme

@birme birme commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

Closes #156. Ref: Eyevinn/open-media-convert#160.

waitForInstanceReady was an unbounded while loop with no timeout, attempt cap, or AbortSignal. A hung instance could cause any finally block downstream of this call to never execute, leaking guards, locks, or other resources. In production, this caused a transcoderNamesBeingCreated guard in open-media-convert to permanently block that queue name until the MyApp was restarted.

Changes

packages/core/src/core.ts — Added optional 4th argument opts?: WaitForInstanceReadyOptions:

  • timeoutMs?: number (default 300,000ms / 5 min)
  • pollIntervalMs?: number (default 1000ms)
  • signal?: AbortSignal

On timeout: rejects with InstanceReadyTimeoutError.
On abort: rejects with Error{ name: 'AbortError' } (avoids DOMException which requires the DOM lib).

packages/core/src/errors.ts — Added exported InstanceReadyTimeoutError extends Error.

packages/core/src/index.ts — Re-exports InstanceReadyTimeoutError.

packages/core/src/waitForInstanceReady.test.ts — New test file covering: resolves on success, rejects with InstanceReadyTimeoutError on timeout, rejects with AbortError on signal abort.

packages/core/package.json — Version bumped to next minor.

Breaking behavior change

The finite default (300s) replaces the old infinite loop. Callers who want the old infinite behavior can pass { timeoutMs: Infinity }. This is documented in the commit message.

Lessons

DOMException is not available when tsconfig lib is ['es2022'] without dom. Use new Error(...); err.name = 'AbortError' instead for abort signals in Node-only packages.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

claude and others added 3 commits September 8, 2026 15:00
An unbounded while loop with no escape caused production hangs when
instances never reached 'running' state. Adds opts.timeoutMs (default
300s), opts.pollIntervalMs (default 1000ms), and opts.signal to allow
caller control over the wait budget.

On timeout: rejects with InstanceReadyTimeoutError (exported).
On abort: rejects with Error{ name: 'AbortError' }.

Breaking behavior change: the finite default (300s) replaces the old
infinite loop. Callers that want the old infinite behavior can pass
{ timeoutMs: Infinity }.

Closes #156. Ref: Eyevinn/open-media-convert#160.

Co-Authored-By: Claude <noreply@anthropic.com>
The implementation throws a plain Error with .name = 'AbortError'
because DOMException is not available in the es2022 lib target.
Change abort test assertions from toThrow(AbortError) to
toMatchObject({ name: 'AbortError' }) so they match the actual
error shape. Also replace fake-timer patterns with pollIntervalMs: 0
for simpler, more reliable async test flow.

Co-Authored-By: Claude <noreply@anthropic.com>
…r dep

Move waitForInstanceReady from core.ts into waitForInstanceReady.ts.
core.ts re-exports it transparently for backwards compatibility.

The test (waitForInstanceReady.test.ts) mocks ./core and calls
waitForInstanceReady via jest.requireActual('./core'). Since both
waitForInstanceReady and getInstanceHealth were in the same module,
the mock factory could not intercept internal calls — the actual
function's closure held a direct reference to the real getInstanceHealth.

Fix: put waitForInstanceReady in its own file. It uses a lazy
require('./core') inside the loop body (not at module load time)
so Jest's mock registry is consulted at call time, when the mock
is fully initialized. This avoids the circular-dep race that
defeated the earlier import * as coreModule approach.

Co-Authored-By: Claude <noreply@anthropic.com>
@birme
birme merged commit bb3deaa into main Sep 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

waitForInstanceReady is an unbounded loop — add timeoutMs / signal / pollIntervalMs options

2 participants