worker: add web locks api

PR-URL: https://github.com/nodejs/node/pull/58666
Fixes: https://github.com/nodejs/node/pull/36502
Refs: https://w3c.github.io/web-locks
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
ishabi 2025-05-30 00:57:31 +02:00 committed by James M Snell
parent 0629a175c0
commit 062e8b5a74
70 changed files with 5030 additions and 0 deletions

View file

@ -768,6 +768,55 @@ consisting of the runtime name and major version number.
console.log(`The user-agent is ${navigator.userAgent}`); // Prints "Node.js/21"
```
### `navigator.locks`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
The `navigator.locks` read-only property returns a [`LockManager`][] instance that
can be used to coordinate access to resources that may be shared across multiple
threads within the same process. This global implementation matches the semantics
of the [browser `LockManager`][] API.
```mjs
// Request an exclusive lock
await navigator.locks.request('my_resource', async (lock) => {
// The lock has been acquired.
console.log(`Lock acquired: ${lock.name}`);
// Lock is automatically released when the function returns
});
// Request a shared lock
await navigator.locks.request('shared_resource', { mode: 'shared' }, async (lock) => {
// Multiple shared locks can be held simultaneously
console.log(`Shared lock acquired: ${lock.name}`);
});
```
```cjs
// Request an exclusive lock
navigator.locks.request('my_resource', async (lock) => {
// The lock has been acquired.
console.log(`Lock acquired: ${lock.name}`);
// Lock is automatically released when the function returns
}).then(() => {
console.log('Lock released');
});
// Request a shared lock
navigator.locks.request('shared_resource', { mode: 'shared' }, async (lock) => {
// Multiple shared locks can be held simultaneously
console.log(`Shared lock acquired: ${lock.name}`);
}).then(() => {
console.log('Shared lock released');
});
```
See [`worker.locks`][] for detailed API documentation.
## Class: `PerformanceEntry`
<!-- YAML
@ -1263,6 +1312,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[`CountQueuingStrategy`]: webstreams.md#class-countqueuingstrategy
[`DecompressionStream`]: webstreams.md#class-decompressionstream
[`EventTarget` and `Event` API]: events.md#eventtarget-and-event-api
[`LockManager`]: worker_threads.md#class-lockmanager
[`MessageChannel`]: worker_threads.md#class-messagechannel
[`MessagePort`]: worker_threads.md#class-messageport
[`PerformanceEntry`]: perf_hooks.md#class-performanceentry
@ -1313,6 +1363,8 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[`setTimeout`]: timers.md#settimeoutcallback-delay-args
[`structuredClone`]: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
[`window.navigator`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator
[`worker.locks`]: worker_threads.md#workerlocks
[browser `LockManager`]: https://developer.mozilla.org/en-US/docs/Web/API/LockManager
[buffer section]: buffer.md
[built-in objects]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
[timers]: timers.md

View file

@ -755,6 +755,153 @@ if (isMainThread) {
}
```
## `worker.locks`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
* {LockManager}
An instance of a [`LockManager`][LockManager] that can be used to coordinate
access to resources that may be shared across multiple threads within the same
process. The API mirrors the semantics of the
[browser `LockManager`][]
### Class: `Lock`
<!-- YAML
added: REPLACEME
-->
The `Lock` interface provides information about a lock that has been granted via
[`locks.request()`][locks.request()]
#### `lock.name`
<!-- YAML
added: REPLACEME
-->
* {string}
The name of the lock.
#### `lock.mode`
<!-- YAML
added: REPLACEME
-->
* {string}
The mode of the lock. Either `shared` or `exclusive`.
### Class: `LockManager`
<!-- YAML
added: REPLACEME
-->
The `LockManager` interface provides methods for requesting and introspecting
locks. To obtain a `LockManager` instance use
```mjs
import { locks } from 'node:worker_threads';
```
```cjs
'use strict';
const { locks } = require('node:worker_threads');
```
This implementation matches the [browser `LockManager`][] API.
#### `locks.request(name[, options], callback)`
<!-- YAML
added: REPLACEME
-->
* `name` {string}
* `options` {Object}
* `mode` {string} Either `'exclusive'` or `'shared'`. **Default:** `'exclusive'`.
* `ifAvailable` {boolean} If `true`, the request will only be granted if the
lock is not already held. If it cannot be granted, `callback` will be
invoked with `null` instead of a `Lock` instance. **Default:** `false`.
* `steal` {boolean} If `true`, any existing locks with the same name are
released and the request is granted immediately, pre-empting any queued
requests. **Default:** `false`.
* `signal` {AbortSignal} that can be used to abort a
pending (but not yet granted) lock request.
* `callback` {Function} Invoked once the lock is granted (or immediately with
`null` if `ifAvailable` is `true` and the lock is unavailable). The lock is
released automatically when the function returns, or—if the function returns
a promise—when that promise settles.
* Returns: {Promise} Resolves once the lock has been released.
```mjs
import { locks } from 'node:worker_threads';
await locks.request('my_resource', async (lock) => {
// The lock has been acquired.
});
// The lock has been released here.
```
```cjs
'use strict';
const { locks } = require('node:worker_threads');
locks.request('my_resource', async (lock) => {
// The lock has been acquired.
}).then(() => {
// The lock has been released here.
});
```
#### `locks.query()`
<!-- YAML
added: REPLACEME
-->
* Returns: {Promise}
Resolves with a `LockManagerSnapshot` describing the currently held and pending
locks for the current process.
```mjs
import { locks } from 'node:worker_threads';
const snapshot = await locks.query();
for (const lock of snapshot.held) {
console.log(`held lock: name ${lock.name}, mode ${lock.mode}`);
}
for (const pending of snapshot.pending) {
console.log(`pending lock: name ${pending.name}, mode ${pending.mode}`);
}
```
```cjs
'use strict';
const { locks } = require('node:worker_threads');
locks.query().then((snapshot) => {
for (const lock of snapshot.held) {
console.log(`held lock: name ${lock.name}, mode ${lock.mode}`);
}
for (const pending of snapshot.pending) {
console.log(`pending lock: name ${pending.name}, mode ${pending.mode}`);
}
});
```
## Class: `BroadcastChannel extends EventTarget`
<!-- YAML
@ -1937,6 +2084,7 @@ thread spawned will spawn another until the application crashes.
[Addons worker support]: addons.md#worker-support
[ECMAScript module loader]: esm.md#data-imports
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[LockManager]: #class-lockmanager
[Signals events]: process.md#signal-events
[Web Workers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
[`'close'` event]: #event-close
@ -1992,7 +2140,9 @@ thread spawned will spawn another until the application crashes.
[`worker.terminate()`]: #workerterminate
[`worker.threadId`]: #workerthreadid_1
[async-resource-worker-pool]: async_context.md#using-asyncresource-for-a-worker-thread-pool
[browser `LockManager`]: https://developer.mozilla.org/en-US/docs/Web/API/LockManager
[browser `MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
[child processes]: child_process.md
[contextified]: vm.md#what-does-it-mean-to-contextify-an-object
[locks.request()]: #locksrequestname-options-callback
[v8.serdes]: v8.md#serialization-api