Skip to content

Web Locks: a request waiting for a lock stays pending after the worker thread holding the lock exits #67216

Open

Description

@steida

Version

v26.13.0 (also v24.22.2)

Platform

Darwin 25.6.0 arm64 (macOS 26.6.2)

Subsystem

worker, Web Locks (navigator.locks)

What steps will reproduce the bug?

import { Worker } from "node:worker_threads";
import { setTimeout as sleep } from "node:timers/promises";

// A worker thread takes a lock and then exits while holding it.
const holder = new Worker(
  `
  const { parentPort } = require("node:worker_threads");
  navigator.locks.request("my-lock", () => {
    parentPort.postMessage("held");
    parentPort.once("message", () => process.exit());
    return new Promise(() => {});
  });
  `,
  { eval: true },
);
await new Promise((resolve) => holder.once("message", resolve));

const waiter = navigator.locks.request("my-lock", () => "granted");
holder.postMessage("exit");
await new Promise((resolve) => holder.once("exit", resolve));

console.log(await Promise.race([waiter, sleep(1000).then(() => "still waiting")]));

// Any unrelated lock request grants it.
await navigator.locks.request("other", { ifAvailable: true }, () => {});
console.log(await Promise.race([waiter, sleep(1000).then(() => "still waiting")]));

How often does it reproduce? Is there a required condition?

Always, when the thread holding the lock ends through process.exit() or an uncaught exception, or calls process.exit() right after releasing the lock. The same happens when the waiting request was made in another worker thread. worker.terminate() didn't reproduce it here.

What is the expected behavior? Why is that the expected behavior?

granted on the first line. Ending the thread releases its locks, so the waiting request should be granted, as it is in browsers when a worker holding a lock ends.

What do you see instead?

still waiting
granted

The request stays pending until any thread requests any lock.

Additional information

LockManager::CleanupEnvironment in src/node_locks.cc removes the ending environment's held locks but doesn't process the pending queue, as ReleaseLockAndProcessQueue does. Found while running Evolu (https://www.evolu.dev), a local-first platform, on Node.js worker threads. There, a thread waiting for a database lock would hang forever when the thread holding it crashed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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