/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/web/locks.js
314 строк
9 KB
crowlbot
fix(ext/node): implement worker_threads.locks via Web Locks (#35963)
22 июл 2026, 16:15
Не верифицирован
22 июл 2026, 16:15
2731794
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. import { core, primordials } from "ext:core/mod.js"; import { op_lock_manager_await_lock, op_lock_manager_await_steal, op_lock_manager_cancel, op_lock_manager_is_stolen, op_lock_manager_query, op_lock_manager_release, op_lock_manager_request, } from "ext:core/ops"; const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js"); const { DOMException } = core.loadExtScript("ext:deno_web/01_dom_exception.js"); const { PromisePrototypeThen, SafePromiseRace, StringFromCharCode, StringPrototypeCharCodeAt, StringPrototypeIndexOf, StringPrototypeSlice, StringPrototypeStartsWith, Symbol, TypeError, } = primordials; // Lock names are DOMStrings that may contain lone surrogates, but the op // boundary converts strings to UTF-8, which replaces lone surrogates with // U+FFFD. That would make otherwise-distinct names (e.g. "\uD800" and // "\uFFFD") collide as keys in the lock manager. To round-trip names // losslessly, lone // surrogates and the escape marker itself are escaped into the private-use area // before crossing into Rust, and decoded again when read back via query(). const LOCK_NAME_ESCAPE = 0xE000; const LOCK_NAME_ESCAPE_CHAR = StringFromCharCode(LOCK_NAME_ESCAPE); function encodeLockName(name) { let result = null; for (let i = 0; i < name.length; i++) { const c = StringPrototypeCharCodeAt(name, i); let replacement = null; if (c === LOCK_NAME_ESCAPE) { replacement = StringFromCharCode(LOCK_NAME_ESCAPE, LOCK_NAME_ESCAPE); } else if ( c >= 0xD800 && c <= 0xDBFF && StringPrototypeCharCodeAt(name, i + 1) >= 0xDC00 && StringPrototypeCharCodeAt(name, i + 1) <= 0xDFFF ) { // Valid surrogate pair: survives UTF-8 conversion, so keep it as-is. if (result !== null) { result += StringFromCharCode(c, StringPrototypeCharCodeAt(name, i + 1)); } i++; continue; } else if (c >= 0xD800 && c <= 0xDFFF) { // Lone surrogate: map into the private-use area [0xE001, 0xE800]. replacement = StringFromCharCode(LOCK_NAME_ESCAPE, c - 0xD800 + 0xE001); } if (replacement !== null) { if (result === null) result = StringPrototypeSlice(name, 0, i); result += replacement; } else if (result !== null) { result += StringFromCharCode(c); } } return result === null ? name : result; } function decodeLockName(name) { if (StringPrototypeIndexOf(name, LOCK_NAME_ESCAPE_CHAR) === -1) return name; let result = ""; for (let i = 0; i < name.length; i++) { const c = StringPrototypeCharCodeAt(name, i); if (c === LOCK_NAME_ESCAPE) { const next = StringPrototypeCharCodeAt(name, i + 1); result += next === LOCK_NAME_ESCAPE ? LOCK_NAME_ESCAPE_CHAR : StringFromCharCode(next - 0xE001 + 0xD800); i++; } else { result += StringFromCharCode(c); } } return result; } const _name = Symbol("[[name]]"); const _mode = Symbol("[[mode]]"); class LockManager { [webidl.brand] = webidl.brand; constructor() { webidl.illegalConstructor(); } async request(name, optionsOrCallback, callback = undefined) { webidl.assertBranded(this, LockManagerPrototype); const prefix = "Failed to execute 'request'"; webidl.requiredArguments(arguments.length, 2, prefix); let options; if (arguments.length === 2) { options = {}; callback = optionsOrCallback; } else { options = optionsOrCallback; } if (typeof callback !== "function") { throw new TypeError("callback must be a function"); } options = webidl.converters.LockOptions( options, prefix, "Argument 2", ); if (StringPrototypeStartsWith(name, "-")) { throw new DOMException( "'name' must not start with '-'", "NotSupportedError", ); } if (options.steal && options.ifAvailable) { throw new DOMException( "'steal' and 'ifAvailable' are exclusive", "NotSupportedError", ); } if (options.steal && options.mode !== "exclusive") { throw new DOMException( "'mode' must be 'exclusive' if 'steal' is specified", "NotSupportedError", ); } if (options.signal && (options.steal || options.ifAvailable)) { throw new DOMException( "'signal' cannot be provided with 'steal' or 'ifAvailable'", "NotSupportedError", ); } if (options.signal) { options.signal.throwIfAborted(); } const { status, rid } = op_lock_manager_request( encodeLockName(name), options.mode, options.ifAvailable, options.steal, ); // status 2 = ifAvailable but lock not grantable if (status === 2) { return await callback(null); } let heldRid; if (status === 0) { // Granted immediately heldRid = rid; // Lock requests are processed asynchronously, so a signal aborted // synchronously right after request() returns must still abort the // request before the callback runs. Yield a microtask, then re-check. if (options.signal) { await null; if (options.signal.aborted) { op_lock_manager_release(heldRid); throw options.signal.reason ?? new DOMException( "The lock request was aborted", "AbortError", ); } } } else { // Pending (status === 1) - wait for the lock to be granted 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), ]); // The race can be won by a fast-returning callback even when the lock was // stolen: the steal is recorded synchronously but `await_steal` only // resolves an event-loop turn later, whereas the callback resolves on a // microtask. Re-check synchronously so a stolen lock always rejects. if (stolen || op_lock_manager_is_stolen(heldRid)) { throw new DOMException( "The operation was aborted", "AbortError", ); } return await callbackPromise; } finally { op_lock_manager_release(heldRid); } } // `query()` returns a Promise per the Web Locks spec and Node's // `LockManager`; the underlying op is synchronous, so there is nothing to // await, but the method stays `async` to produce a real Promise. // deno-lint-ignore require-await async query() { webidl.assertBranded(this, LockManagerPrototype); const { held, pending } = op_lock_manager_query(); for (let i = 0; i < held.length; i++) { held[i].name = decodeLockName(held[i].name); } for (let i = 0; i < pending.length; i++) { pending[i].name = decodeLockName(pending[i].name); } return { held, pending }; } } webidl.configureInterface(LockManager); const LockManagerPrototype = LockManager.prototype; class Lock { [_name]; [_mode]; [webidl.brand] = webidl.brand; constructor() { webidl.illegalConstructor(); } get name() { webidl.assertBranded(this, LockPrototype); return this[_name]; } get mode() { webidl.assertBranded(this, LockPrototype); return this[_mode]; } } webidl.configureInterface(Lock); const LockPrototype = Lock.prototype; webidl.converters.LockMode = webidl.createEnumConverter("LockMode", [ "shared", "exclusive", ]); webidl.converters.LockOptions = webidl .createDictionaryConverter( "LockOptions", [ { key: "mode", converter: webidl.converters["LockMode"], defaultValue: "exclusive", }, { key: "ifAvailable", converter: webidl.converters["boolean"], defaultValue: false, }, { key: "steal", converter: webidl.converters["boolean"], defaultValue: false, }, { key: "signal", converter: webidl.converters["AbortSignal"], }, ], ); const locks = webidl.createBranded(LockManager); export { Lock, LockManager, locks };