denoland/deno · error · DOMException

AbortError

AbortError

Error message

The lock request was aborted

What it means

When a LockManager.request() call has an AbortSignal and the request is queued (the lock is held elsewhere), aborting the signal rejects the request's promise. If the application set signal.reason it is re-thrown as-is; otherwise Deno throws a DOMException named AbortError with this message from ext/web/locks.js. The listener is removed and no lock is held — the callback never runs.

Source

Thrown at ext/web/locks.js:202

      if (options.signal) {
        const onAbort = () => op_lock_manager_cancel(rid);
        options.signal.addEventListener("abort", onAbort, { once: true });
        try {
          heldRid = await op_lock_manager_await_lock(rid);
        } finally {
          options.signal.removeEventListener("abort", onAbort);
        }
        if (heldRid == null) {
          throw options.signal.reason ??
            new DOMException(
              "The lock request was aborted",
              "AbortError",
            );
        }
      } else {
        heldRid = await op_lock_manager_await_lock(rid);
        if (heldRid == null) {
          throw new DOMException(
            "The lock request was aborted",
            "AbortError",
          );
        }
      }
    }

    try {
      const lock = webidl.createBranded(Lock);
      lock[_name] = name;
      lock[_mode] = options.mode;
      // Run the callback while concurrently watching for the lock being
      // stolen. Awaiting the steal op also keeps the event loop alive for as
      // long as the lock is held.
      const callbackPromise = (async () => await callback(lock))();
      const stolen = await SafePromiseRace([
        op_lock_manager_await_steal(heldRid),
        PromisePrototypeThen(callbackPromise, () => false),

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Catch the rejection around the request() call and treat AbortError as 'did not acquire' — skip or retry the work.
  2. Distinguish causes: if you set controller.abort(reason), compare e === reason before falling back to DOMException handling.
  3. For timeouts, prefer AbortSignal.timeout(ms) and catch AbortError explicitly.
  4. If aborts are unexpected, investigate the long lock holder — that is the real cause, not the abort.

Example fix

// before
const p = navigator.locks.request("r", { signal: ctrl.signal }, cb);
await p; // unhandled AbortError when aborted

// after
try {
  await navigator.locks.request("r", { signal: ctrl.signal }, cb);
} catch (e) {
  if (e.name === "AbortError") return; // acquisition cancelled
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (opts.signal?.aborted) {
  return; // nothing to request
}
await navigator.locks.request(n, opts, cb);

Type guard

function isAbortError(e) {
  return e instanceof DOMException && e.name === "AbortError";
}

Try / catch

try {
  await navigator.locks.request(n, { signal: ctrl.signal }, cb);
} catch (e) {
  if (e === ctrl.signal.reason || (e instanceof DOMException && e.name === "AbortError")) return; // cancelled while queued
  throw e;
}

Prevention

When it happens

Trigger: Aborting an AbortController while request() is queued behind another holder; a timeout via setTimeout(() => ctrl.abort(), 5000) racing lock acquisition; server shutdown aborting in-flight lock requests.

Common situations: Adding request timeouts to lock acquisition so slow holders cannot stall workers; graceful-shutdown paths that abort pending requests; racing a lock against user cancellation in a web UI.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/f3e88f0d3be22476. Report an issue: GitHub.