/** * MemoryLocker 是一个实现了 Locker 接口的类,用于在内存中管理锁。 * 该类设计用于对资源进行独占访问控制,常用于上传管理等场景。 * * 主要特性: * - 通过使用基于内存的映射来跟踪锁,确保资源的独占访问。 * - 实现锁获取的超时机制,缓解死锁情况。 * - 通过不同的机制促进锁的立即和优雅释放。 * * 锁定行为: * - 当对已经锁定的资源调用 `lock` 方法时,会调用 `cancelReq` 回调。 * 这向当前锁持有者发出信号,表示另一个进程正在请求锁,鼓励他们尽快释放锁。 * - 锁尝试会持续到指定的超时时间。如果超时到期且锁仍然不可用,则抛出错误以指示锁获取失败。 * * 锁的获取和释放: * - `lock` 方法实现了等待机制,允许锁请求在锁可用时成功,或在超时期间失败。 * - `unlock` 方法释放锁,使资源可供其他请求使用。 */ import { RequestRelease, Locker, ERRORS, Lock } from "../utils" export interface MemoryLockerOptions { acquireLockTimeout: number } interface LockEntry { requestRelease: RequestRelease } export class MemoryLocker implements Locker { timeout: number locks = new Map() constructor(options?: MemoryLockerOptions) { this.timeout = options?.acquireLockTimeout ?? 1000 * 30 } /** * 创建一个新的 MemoryLock 实例。 * @param id 锁的唯一标识符。 * @returns 返回一个新的 MemoryLock 实例。 */ newLock(id: string) { return new MemoryLock(id, this, this.timeout) } } class MemoryLock implements Lock { constructor( private id: string, private locker: MemoryLocker, private timeout: number = 1000 * 30 ) { } /** * 尝试获取锁。 * @param requestRelease 当锁被请求时调用的回调函数。 * @throws 如果锁获取超时,则抛出 ERRORS.ERR_LOCK_TIMEOUT 错误。 */ async lock(requestRelease: RequestRelease): Promise { const abortController = new AbortController() const lock = await Promise.race([ this.waitTimeout(abortController.signal), this.acquireLock(this.id, requestRelease, abortController.signal), ]) abortController.abort() if (!lock) { throw ERRORS.ERR_LOCK_TIMEOUT } } /** * 尝试获取指定 ID 的锁。 * @param id 锁的唯一标识符。 * @param requestRelease 当锁被请求释放时调用的回调函数。 * @param signal 用于取消操作的 AbortSignal。 * @returns 如果成功获取锁,则返回 true;否则返回 false。 */ protected async acquireLock( id: string, requestRelease: RequestRelease, signal: AbortSignal ): Promise { if (signal.aborted) { return false } const lock = this.locker.locks.get(id) if (!lock) { const lock = { requestRelease, } this.locker.locks.set(id, lock) return true } await lock.requestRelease?.() return await new Promise((resolve, reject) => { // 使用 setImmediate 的原因: // 1. 通过将递归调用推迟到下一个事件循环迭代来防止堆栈溢出。 // 2. 允许事件循环处理其他挂起的事件,保持服务器的响应性。 // 3. 通过给其他请求获取锁的机会,确保锁获取的公平性。 setImmediate(() => { this.acquireLock(id, requestRelease, signal).then(resolve).catch(reject) }) }) } /** * 释放锁。 * @throws 如果尝试释放未锁定的锁,则抛出错误。 */ async unlock(): Promise { const lock = this.locker.locks.get(this.id) if (!lock) { throw new Error('Releasing an unlocked lock!') } this.locker.locks.delete(this.id) } /** * 等待超时。 * @param signal 用于取消操作的 AbortSignal。 * @returns 如果超时,则返回 false。 */ protected waitTimeout(signal: AbortSignal) { return new Promise((resolve) => { const timeout = setTimeout(() => { resolve(false) }, this.timeout) const abortListener = () => { clearTimeout(timeout) signal.removeEventListener('abort', abortListener) resolve(false) } signal.addEventListener('abort', abortListener) }) } }