Skip to content

Commit 84ab409

Browse files
addaleaxdevsnek
authored andcommitted
worker: implement Web Locks API
This is based upon nodejs#22719 and exposes the same API. Instead of a C++-backed approach, this variant implements the API mostly in JS. Refs: nodejs#22719 Co-authored-by: Gus Caplan <[email protected]>
1 parent 04e5418 commit 84ab409

File tree

12 files changed

+908
-11
lines changed

12 files changed

+908
-11
lines changed

doc/api/worker_threads.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,108 @@ if (isMainThread) {
274274
}
275275
```
276276

277+
## `worker.locks`
278+
<!-- YAML
279+
added: REPLACEME
280+
-->
281+
282+
> Stability: 1 - Experimental
283+
284+
* {LockManager}
285+
286+
An instance of a [`LockManager`][].
287+
288+
### Class: `Lock`
289+
<!-- YAML
290+
added: REPLACEME
291+
-->
292+
293+
The Lock interface provides the name and mode of a previously requested lock,
294+
which is received in the callback to [`locks.request()`][].
295+
296+
#### `lock.name`
297+
<!-- YAML
298+
added: REPLACEME
299+
-->
300+
301+
* {string}
302+
303+
The name of this lock.
304+
305+
#### `lock.mode`
306+
<!-- YAML
307+
added: REPLACEME
308+
-->
309+
310+
* {string}
311+
312+
The mode of this lock. Either `shared` or `exclusive`.
313+
314+
### Class: `LockManager`
315+
<!-- YAML
316+
added: REPLACEME
317+
-->
318+
319+
The `LockManager` interface provides methods for requesting a new [`Lock`][]
320+
object and querying for an existing `Lock` object. To get an instance of
321+
`LockManager`, call `worker_threads.locks`.
322+
323+
With the exception of `AbortController` support, this implementation matches
324+
the [browser `LockManager`][] API.
325+
326+
#### `locks.request(name[, options], callback)`
327+
<!-- YAML
328+
added: REPLACEME
329+
-->
330+
331+
* `name` {string}
332+
* `options` {Object}
333+
* `mode` {string} Either `'exclusive'` or `'shared'`. **Default:**
334+
`'exclusive'`.
335+
* `ifAvailable` {boolean} If `true`, the lock request will only be
336+
granted if it is not already held. If it cannot be granted, the
337+
callback will be invoked with `null` instead of a `Lock` instance.
338+
**Default:** `false`.
339+
* `steal` {boolean} If `true`, then any held locks with the same name will be
340+
released, and the request will be granted, preempting any queued requests
341+
for it. **Default:** `false`.
342+
* `callback` {Function} The function to be invoked while the lock is acquired.
343+
The lock will be released when the function ends, or if the function returns
344+
a promise, when that promise settles.
345+
* Returns: {Promise}
346+
347+
Requests a [`Lock`][] object with parameters specifying its name and
348+
characteristics.
349+
350+
```js
351+
worker_threads.locks.request('my_resource', async (lock) => {
352+
// The lock was granted.
353+
}).then(() => {
354+
// The lock is released here.
355+
});
356+
```
357+
358+
#### `locks.query()`
359+
<!-- YAML
360+
added: REPLACEME
361+
-->
362+
363+
* Returns: {Promise}
364+
365+
Returns a Promise that resolves with a [`LockManagerSnapshot`][] which contains
366+
information about held and pending locks.
367+
368+
```js
369+
worker_threads.locks.query().then((state) => {
370+
state.held.forEach((lock) => {
371+
console.log(`held lock: name ${lock.name}, mode ${lock.mode}`);
372+
});
373+
state.pending.forEach((request) => {
374+
console.log(`requested lock: name ${request.name}, mode ${request.mode}`);
375+
});
376+
});
377+
```
378+
277379
## Class: `BroadcastChannel extends EventTarget`
278380
<!-- YAML
279381
added: v15.4.0
@@ -1086,6 +1188,9 @@ active handle in the event system. If the worker is already `unref()`ed calling
10861188
[`EventTarget`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
10871189
[`FileHandle`]: fs.md#fs_class_filehandle
10881190
[`KeyObject`]: crypto.md#crypto_class_keyobject
1191+
[`Lock`]: #worker_threads_class_lock
1192+
[`LockManager`]: #worker_threads_class_lockmanager
1193+
[`LockManagerSnapshot`]: https://developer.mozilla.org/en-US/docs/Web/API/LockManagerSnapshot
10891194
[`MessagePort`]: #worker_threads_class_messageport
10901195
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
10911196
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
@@ -1095,6 +1200,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
10951200
[`data:` URL]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
10961201
[`fs.close()`]: fs.md#fs_fs_close_fd_callback
10971202
[`fs.open()`]: fs.md#fs_fs_open_path_flags_mode_callback
1203+
[`locks.request()`]: #worker_threads_locks_request_name_options_callback
10981204
[`markAsUntransferable()`]: #worker_threads_worker_markasuntransferable_object
10991205
[`perf_hooks.performance`]: perf_hooks.md#perf_hooks_perf_hooks_performance
11001206
[`perf_hooks` `eventLoopUtilization()`]: perf_hooks.md#perf_hooks_performance_eventlooputilization_utilization1_utilization2
@@ -1126,6 +1232,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
11261232
[`worker.terminate()`]: #worker_threads_worker_terminate
11271233
[`worker.threadId`]: #worker_threads_worker_threadid_1
11281234
[async-resource-worker-pool]: async_hooks.md#async-resource-worker-pool
1235+
[browser `LockManager`]: https://developer.mozilla.org/en-US/docs/Web/API/LockManager
11291236
[browser `MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
11301237
[child processes]: child_process.md
11311238
[contextified]: vm.md#vm_what_does_it_mean_to_contextify_an_object

lib/internal/worker.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ let debug = require('internal/util/debuglog').debuglog('worker', (fn) => {
8585
});
8686

8787
let cwdCounter;
88+
let locksInitialized = false;
8889

8990
if (isMainThread) {
9091
cwdCounter = new Uint32Array(new SharedArrayBuffer(4));
@@ -166,6 +167,13 @@ class Worker extends EventEmitter {
166167
options.env);
167168
}
168169

170+
if (isMainThread && !locksInitialized) {
171+
locksInitialized = true;
172+
// Make sure that locks are initialized before active
173+
// multithreading starts.
174+
require('worker_threads').locks.query();
175+
}
176+
169177
// Set up the C++ handle for the worker, as well as some internal wiring.
170178
this[kHandle] = new WorkerImpl(url,
171179
env === process.env ? null : env,

lib/internal/worker/io.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ class BroadcastChannel extends EventTarget {
348348
throw new ERR_MISSING_ARGS('name');
349349
super();
350350
this[kName] = `${name}`;
351-
this[kHandle] = broadcastChannel(this[kName]);
351+
this[kHandle] = broadcastChannel(`userland:${this[kName]}`);
352352
this[kOnMessage] = FunctionPrototypeBind(onMessageEvent, this, 'message');
353353
this[kOnMessageError] =
354354
FunctionPrototypeBind(onMessageEvent, this, 'messageerror');
@@ -412,6 +412,7 @@ defineEventHandler(BroadcastChannel.prototype, 'messageerror');
412412
module.exports = {
413413
drainMessagePort,
414414
messageTypes,
415+
kHandle,
415416
kPort,
416417
kIncrementsPortRef,
417418
kWaitingStreams,

0 commit comments

Comments
 (0)