-
-
Notifications
You must be signed in to change notification settings - Fork 34.2k
worker: add Locks API #22719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
worker: add Locks API #22719
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,11 @@ const { | |
| const { internalBinding } = require('internal/bootstrap/loaders'); | ||
| const { MessagePort, MessageChannel } = internalBinding('messaging'); | ||
| const { handle_onclose } = internalBinding('symbols'); | ||
| const locks = internalBinding('locks'); | ||
| const { clearAsyncIdStack } = require('internal/async_hooks'); | ||
| const { serializeError, deserializeError } = require('internal/error-serdes'); | ||
| const DOMException = require('internal/domexception'); | ||
| const nextTickUtil = require('internal/process/next_tick'); | ||
|
|
||
| util.inherits(MessagePort, EventEmitter); | ||
|
|
||
|
|
@@ -44,6 +47,7 @@ const kStdioWantsMoreDataCallback = Symbol('kStdioWantsMoreDataCallback'); | |
| const kStartedReading = Symbol('kStartedReading'); | ||
| const kWaitingStreams = Symbol('kWaitingStreams'); | ||
| const kIncrementsPortRef = Symbol('kIncrementsPortRef'); | ||
| const kMode = Symbol('mode'); | ||
|
|
||
| const debug = util.debuglog('worker'); | ||
|
|
||
|
|
@@ -505,12 +509,159 @@ function pipeWithoutWarning(source, dest) { | |
| dest._maxListeners = destMaxListeners; | ||
| } | ||
|
|
||
| // https://wicg.github.io/web-locks/#api-lock | ||
| class Lock { | ||
| constructor() { | ||
| // eslint-disable-next-line no-restricted-syntax | ||
| throw new TypeError('Illegal constructor'); | ||
| } | ||
|
|
||
| get name() { | ||
| return this[kName]; | ||
| } | ||
|
|
||
| get mode() { | ||
| return this[kMode]; | ||
| } | ||
| } | ||
|
|
||
| Object.defineProperties(Lock.prototype, { | ||
| name: { enumerable: true }, | ||
| mode: { enumerable: true }, | ||
| [Symbol.toStringTag]: { | ||
| value: 'Lock', | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: true, | ||
| }, | ||
| }); | ||
|
|
||
| // https://wicg.github.io/web-locks/#api-lock-manager | ||
| class LockManager { | ||
| constructor() { | ||
| // eslint-disable-next-line no-restricted-syntax | ||
| throw new TypeError('Illegal constructor'); | ||
| } | ||
|
|
||
| // https://wicg.github.io/web-locks/#api-lock-manager-request | ||
| request(name, options, callback) { | ||
| if (callback === undefined) { | ||
| callback = options; | ||
| options = undefined; | ||
| } | ||
|
|
||
| // Let promise be a new promise. | ||
| let reject; | ||
| let resolve; | ||
| const promise = new Promise((res, rej) => { | ||
| resolve = res; | ||
| reject = rej; | ||
| }); | ||
|
|
||
| // If options was not passed, then let options be a new LockOptions | ||
| // dictionary with default members. | ||
| if (options === undefined) { | ||
| options = { | ||
| mode: 'exclusive', | ||
| ifAvailable: false, | ||
| steal: false, | ||
| }; | ||
| } | ||
|
|
||
| if (name.startsWith('-')) { | ||
| // if name starts with U+002D HYPHEN-MINUS (-), then reject promise with a | ||
| // "NotSupportedError" DOMException. | ||
| reject(new DOMException('NotSupportedError')); | ||
|
||
| } else if (options.ifAvailable === true && options.steal === true) { | ||
| // Otherwise, if both options' steal dictionary member and option's | ||
| // ifAvailable dictionary member are true, then reject promise with a | ||
| // "NotSupportedError" DOMException. | ||
| reject(new DOMException('NotSupportedError')); | ||
| } else if (options.steal === true && options.mode !== 'exclusive') { | ||
| // Otherwise, if options' steal dictionary member is true and option's | ||
| // mode dictionary member is not "exclusive", then reject promise with a | ||
| // "NotSupportedError" DOMException. | ||
| reject(new DOMException('NotSupportedError')); | ||
| } else { | ||
| // Otherwise, run these steps: | ||
|
|
||
| // Let request be the result of running the steps to request a lock with | ||
| // promise, the current agent, environment's id, origin, callback, name, | ||
| // options' mode dictionary member, options' ifAvailable dictionary | ||
| // member, and option's steal dictionary member. | ||
| nextTickUtil.queueMicrotask(() => { | ||
| locks.request( | ||
| promise, | ||
| (name, mode, waitingPromise, release) => { | ||
| const lock = Object.create(Lock.prototype, { | ||
| [kName]: { | ||
| value: name, | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: false, | ||
| }, | ||
| [kMode]: { | ||
| value: mode === 0 ? 'shared' : 'exclusive', | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: false, | ||
| }, | ||
| }); | ||
|
|
||
| // When lock lock's waiting promise settles (fulfills or rejects), | ||
| // enqueue the following steps on the lock task queue: | ||
| waitingPromise | ||
| .finally(() => undefined) | ||
| .then(() => { | ||
| // Release the lock lock. | ||
| release(); | ||
|
|
||
| // Resolve lock's released promise with lock's waiting promise. | ||
| resolve(waitingPromise); | ||
| }); | ||
|
|
||
| return callback(lock); | ||
| }, | ||
| name, | ||
| options.mode === 'shared' ? 0 : 1, | ||
| options.ifAvailable || false, | ||
| options.steal || false); | ||
| }); | ||
| } | ||
|
|
||
| // Return promise. | ||
| return promise; | ||
| } | ||
|
|
||
| // https://wicg.github.io/web-locks/#api-lock-manager-query | ||
| query() { | ||
| return new Promise((resolve) => { | ||
| nextTickUtil.queueMicrotask(() => { | ||
| const snapshot = locks.snapshot(); | ||
| resolve(snapshot); | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| Object.defineProperties(LockManager.prototype, { | ||
| request: { enumerable: true }, | ||
| query: { enumerable: true }, | ||
| [Symbol.toStringTag]: { | ||
| value: 'LockManager', | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: true, | ||
| }, | ||
| }); | ||
|
|
||
| module.exports = { | ||
| MessagePort, | ||
| MessageChannel, | ||
| threadId, | ||
| Worker, | ||
| setupChild, | ||
| isMainThread, | ||
| workerStdio | ||
| workerStdio, | ||
| LockManager, | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, to differentiate this from a callback API can we call this ‘disposer’ and not ‘callback’
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's called "callback" in the spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer this to be consistent with Node.js terminology. A callback for Node.js is an error-back. This would be confusing. This would also apply to our docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. While I generally prefer to keep naming the same or as same as possible to specs, in this case not calling this
callbackmakes sense. Also, on the off chance that someone decided to try to wrap this inutil.promisify()because it looks like a callback, it may make sense to have a custom promisify implementation for this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
util.promisify makes no sense here, it already returns a promise. i will rename the variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know that
util.promisify()makes no sense here, but that doesn't mean users won't try. Being defensive by providing a custom promisify implementation that skips wrapping and returns the actual promise makes sense... or, throw within the custom promisify to indicate that it doesn't make sense.