import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions, } from "./internal/wait-constraints.ts" import { internalAssertTimeoutOption, internalBindWaitConstraints, internalCleanupWaitConstraints, internalThrowIfAborted, } from "./internal/wait-constraints.ts" import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts" import { InternalWaitQueue } from "./internal/wait-queue.ts" import type { CoordinationPermitDetails } from "./permit.ts" import { Permit } from "./permit.ts" /** * @description 链表形式的等待节点。 * * 之所以不使用数组,是因为等待中的任务可能会被 timeout 或 abort 主动取消。 * 使用双向链表后,任意节点都可以在 O(1) 时间内从等待队列中移除,不需要做 * `indexOf + splice` 这样的线性扫描。 */ interface InternalMutexQueueEntry extends InternalWaitQueueEntry, InternalWaitConstraintsEntry { resolve: (permit: MutexPermit) => void reject: (reason: unknown) => void isSettled: boolean } /** * @description 表示获取互斥锁时可选的等待控制参数。 */ export interface MutexAcquireOptions extends InternalWaitConstraintsOptions { /** * @description 等待锁的超时时间,单位为毫秒。 */ timeout?: number | undefined /** * @description 等待期间使用的中止信号;一旦中止,会将本次等待从队列中移除并拒绝。 */ abortSignal?: AbortSignal | undefined } /** * @description 表示 `Mutex` 成功获取后的占用句柄。 */ export type MutexPermit = Permit> const INTERNAL_MUTEX_DUPLICATE_RELEASE_MESSAGE = "Mutex permit release was called more than once." /** * @description 表示 `Mutex` 在检测到重复 `release()` 误用时使用的处理函数。 */ export type MutexDuplicateReleaseHandler = (message: string) => void /** * @description 表示构造 `Mutex` 时可选的运行时配置。 */ export interface MutexOptions { /** * @description 重复调用同一个 `release()` 时的可选处理器。 * * 未提供时,`Mutex` 会保持静默;提供后,每个 `Mutex` 实例最多调用一次。 */ onDuplicateRelease?: MutexDuplicateReleaseHandler | undefined } /** * @description 表示用于串行化异步临界区的互斥锁。 * * @example * ``` * const mutex = new Mutex() * let counter = 0 * * await Promise.all([ * mutex.runExclusive(async () => { * const current = counter * await Promise.resolve() * counter = current + 1 * }), * mutex.runExclusive(async () => { * const current = counter * await Promise.resolve() * counter = current + 1 * }), * ]) * * // Expect: 2 * const example1 = counter * ``` */ export class Mutex { /** * @description 等待队列中的每个节点都代表一个尚未拿到锁的等待者。 */ private readonly queue: InternalWaitQueue /** * @description 当前是否已有持锁者进入临界区。 */ private internalIsLocked: boolean /** * @description 重复 release 误用的可选处理器。 */ private readonly onDuplicateRelease: MutexDuplicateReleaseHandler | undefined constructor(options: MutexOptions = {}) { this.queue = new InternalWaitQueue() this.internalIsLocked = false this.onDuplicateRelease = options.onDuplicateRelease } /** * @description 读取当前锁是否已经被持有。 */ isLocked(): boolean { return this.internalIsLocked } /** * @description 读取当前等待队列中的任务数量。 */ getPendingCount(): number { return this.queue.getCount() } /** * @description 尝试把锁交给下一个可运行的等待者。 * * 这里始终从队头取任务,保持 FIFO;若遇到已失效节点,则跳过直到找到下一个有效等待者。 */ private dispatch(): void { if (this.internalIsLocked === true) { return } while (this.queue.isEmpty() === false) { const nextEntry = this.queue.shift() if (nextEntry === undefined) { return } if (nextEntry.isSettled === true) { continue } this.internalIsLocked = true nextEntry.isSettled = true internalCleanupWaitConstraints(nextEntry) nextEntry.resolve(this.buildPermit()) return } } /** * @description 为当前持锁者构造一个 permit。 */ private buildPermit(): MutexPermit { return new Permit>({ details: { coordination: "mutex" }, duplicateReleaseMessage: INTERNAL_MUTEX_DUPLICATE_RELEASE_MESSAGE, onDuplicateRelease: this.onDuplicateRelease, onRelease: (): void => { this.internalIsLocked = false this.dispatch() }, }) } /** * @description 以失败状态结束一个等待节点。 * * 该逻辑会统一处理:标记结束、清理约束、从队列摘除、拒绝 Promise,最后尝试继续调度后继等待者。 */ private rejectEntry(entry: InternalMutexQueueEntry, reason: unknown): void { if (entry.isSettled === true) { return } entry.isSettled = true internalCleanupWaitConstraints(entry) this.queue.remove(entry) entry.reject(reason) this.dispatch() } /** * @description 尝试立即获取锁;若当前不可用则返回 `undefined`。 */ tryAcquire(): MutexPermit | undefined { if (this.internalIsLocked === true || this.queue.isEmpty() === false) { return undefined } this.internalIsLocked = true return this.buildPermit() } /** * @description 等待直到成功获取锁。 * * 当锁暂时不可用时,调用方会进入 FIFO 等待队列;一旦前面的持锁者释放,队头等待者会被唤醒。 */ async acquire(options: MutexAcquireOptions = {}): Promise { internalAssertTimeoutOption("Mutex acquire", options.timeout) internalThrowIfAborted("Mutex acquire", options.abortSignal) const immediatePermit = this.tryAcquire() if (immediatePermit !== undefined) { return immediatePermit } const permit = await new Promise((resolve, reject) => { const entry: InternalMutexQueueEntry = { resolve, reject, isSettled: false, isQueued: false, previous: undefined, next: undefined, timeoutId: undefined, abortSignal: undefined, abortListener: undefined, } internalBindWaitConstraints(entry, options, { abortOperation: "Mutex acquire", timeoutOperation: "Mutex acquire", onAbort: (error) => { this.rejectEntry(entry, error) }, onTimeout: (error) => { this.rejectEntry(entry, error) }, }) this.queue.enqueue(entry) this.dispatch() }) return permit } /** * @description 在独占锁保护下执行回调。 * * 无论回调成功还是抛错,permit 都会在 `finally` 中释放,避免把锁永久占住。 */ async runExclusive(callback: () => Promise, options?: MutexAcquireOptions): Promise async runExclusive(callback: () => T, options?: MutexAcquireOptions): Promise async runExclusive( callback: () => Promise | T, options: MutexAcquireOptions = {}, ): Promise { const permit = await this.acquire(options) try { return await callback() } finally { permit.release() } } }