UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

347 lines 11.6 kB
/** * @module SharedLock */ import { UnexpectedError, } from "../../../../utilities/_module-exports.js"; /** * Note the `MemorySharedLockAdapter` is limited to single process usage and cannot be shared across multiple servers or different processes. * This adapter is meant to be used for testing. * * IMPORT_PATH: `"@daiso-tech/core/shared-lock/memory-shared-lock-adapter"` * @group Adapters */ export class MemorySharedLockAdapter { map; /** * @example * ```ts * import { MemorySharedLockAdapter } from "@daiso-tech/core/shared-lock/memory-shared-lock-adapter"; * * const sharedLockAdapter = new MemorySharedLockAdapter(); * ``` * You can also provide an `Map`. * @example * ```ts * import { MemorySharedLockAdapter } from "@daiso-tech/core/shared-lock/memory-shared-lock-adapter"; * * const map = new Map<any, any>(); * const sharedLockAdapter = new MemorySharedLockAdapter(map); * ``` */ constructor(map = new Map()) { this.map = map; } // eslint-disable-next-line @typescript-eslint/require-await async deInit() { for (const [key, sharedLock] of this.map) { const writerLock = sharedLock.writerLock; if (writerLock !== null && writerLock.hasExpiration) { clearTimeout(writerLock.timeoutId); } const readerSemaphore = sharedLock.readerSemaphore; if (readerSemaphore !== null) { for (const [_, { timeoutId }] of readerSemaphore.slots) { if (timeoutId !== null) { clearTimeout(timeoutId); } } } this.map.delete(key); } } // eslint-disable-next-line @typescript-eslint/require-await async acquireWriter(key, lockId, ttl) { const sharedLock = this.map.get(key); const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore !== null) { return false; } let writerLock = sharedLock?.writerLock ?? null; if (writerLock !== null) { return writerLock.owner === lockId; } if (ttl === null) { writerLock = { owner: lockId, hasExpiration: false, }; this.map.set(key, { writerLock, readerSemaphore: null, }); } else { const timeoutId = setTimeout(() => { this.map.delete(key); }, ttl.toMilliseconds()); writerLock = { owner: lockId, hasExpiration: true, timeoutId, expiration: ttl.toEndDate(), }; this.map.set(key, { writerLock, readerSemaphore: null, }); } return true; } // eslint-disable-next-line @typescript-eslint/require-await async releaseWriter(key, lockId) { const sharedLock = this.map.get(key); const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore !== null) { return false; } const writerLock = sharedLock?.writerLock ?? null; if (writerLock === null) { return false; } if (writerLock.owner !== lockId) { return false; } if (writerLock.hasExpiration) { clearTimeout(writerLock.timeoutId); } this.map.delete(key); return true; } // eslint-disable-next-line @typescript-eslint/require-await async forceReleaseWriter(key) { const sharedLock = this.map.get(key); const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore !== null) { return false; } const writerLock = sharedLock?.writerLock ?? null; if (writerLock === null) { return false; } if (writerLock.hasExpiration) { clearTimeout(writerLock.timeoutId); } this.map.delete(key); return true; } // eslint-disable-next-line @typescript-eslint/require-await async refreshWriter(key, lockId, ttl) { const sharedLock = this.map.get(key); const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore !== null) { return false; } const writerLock = sharedLock?.writerLock ?? null; if (writerLock === null) { return false; } if (writerLock.owner !== lockId) { return false; } if (!writerLock.hasExpiration) { return false; } clearTimeout(writerLock.timeoutId); const timeoutId = setTimeout(() => { this.map.delete(key); }, ttl.toMilliseconds()); this.map.set(key, { readerSemaphore: null, writerLock: { ...writerLock, timeoutId, }, }); return true; } // eslint-disable-next-line @typescript-eslint/require-await async acquireReader(settings) { const { key, lockId, limit, ttl } = settings; const sharedLock = this.map.get(key); const writerLock = sharedLock?.writerLock ?? null; if (writerLock !== null) { return false; } let readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore === null) { readerSemaphore = { limit, slots: new Map(), }; this.map.set(key, { readerSemaphore, writerLock: null, }); } if (readerSemaphore.slots.size >= readerSemaphore.limit) { return false; } if (readerSemaphore.slots.has(lockId)) { return true; } if (ttl === null) { readerSemaphore.slots.set(lockId, { timeoutId: null, expiration: null, }); } else { const timeoutId = setTimeout(() => { readerSemaphore.slots.delete(lockId); }, ttl.toMilliseconds()); readerSemaphore.slots.set(lockId, { timeoutId, expiration: ttl.toEndDate(), }); } this.map.set(key, { readerSemaphore, writerLock: null, }); return true; } // eslint-disable-next-line @typescript-eslint/require-await async releaseReader(key, lockId) { const sharedLock = this.map.get(key); const writerLock = sharedLock?.writerLock ?? null; if (writerLock !== null) { return false; } const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore === null) { return false; } const slot = readerSemaphore.slots.get(lockId); if (slot === undefined) { return false; } if (slot.timeoutId !== null) { clearTimeout(slot.timeoutId); } readerSemaphore.slots.delete(lockId); this.map.set(key, { readerSemaphore, writerLock: null, }); if (readerSemaphore.slots.size === 0) { this.map.delete(key); } return true; } // eslint-disable-next-line @typescript-eslint/require-await async forceReleaseAllReaders(key) { const sharedLock = this.map.get(key); const writerLock = sharedLock?.writerLock ?? null; if (writerLock !== null) { return false; } const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (readerSemaphore === null) { return false; } const hasSlots = readerSemaphore.slots.size > 0; for (const [slotId, { timeoutId }] of readerSemaphore.slots) { clearTimeout(timeoutId ?? undefined); readerSemaphore.slots.delete(slotId); } this.map.delete(key); return hasSlots; } // eslint-disable-next-line @typescript-eslint/require-await async refreshReader(key, lockId, ttl) { const sharedLock = this.map.get(key); const writerLock = sharedLock?.writerLock ?? null; if (writerLock !== null) { return false; } const readerSemaphore = sharedLock?.readerSemaphore ?? null; if (!readerSemaphore) { return false; } const slot = readerSemaphore.slots.get(lockId); if (slot === undefined) { return false; } if (slot.timeoutId === null) { return false; } clearTimeout(slot.timeoutId); const timeoutId = setTimeout(() => { readerSemaphore.slots.delete(lockId); this.map.set(key, { readerSemaphore, writerLock: null, }); }, ttl.toMilliseconds()); readerSemaphore.slots.set(lockId, { timeoutId, expiration: ttl.toEndDate(), }); this.map.set(key, { readerSemaphore, writerLock: null, }); return true; } // eslint-disable-next-line @typescript-eslint/require-await async forceRelease(key) { const [hasReleasedAllReaders, hasReleasedWriter] = await Promise.all([ this.forceReleaseAllReaders(key), this.forceReleaseWriter(key), ]); return hasReleasedAllReaders || hasReleasedWriter; } // eslint-disable-next-line @typescript-eslint/require-await async getState(key) { const sharedLock = this.map.get(key); if (sharedLock === undefined) { return null; } const { writerLock, readerSemaphore } = sharedLock; if (writerLock === null && readerSemaphore !== null && readerSemaphore.slots.size === 0) { return null; } if (writerLock === null && readerSemaphore !== null && readerSemaphore.slots.size !== 0) { return { writer: null, reader: { limit: readerSemaphore.limit, acquiredSlots: new Map([...readerSemaphore.slots.entries()].map(([key, value]) => [key, value.expiration])), }, }; } if (readerSemaphore === null && writerLock !== null && !writerLock.hasExpiration) { return { reader: null, writer: { owner: writerLock.owner, expiration: null, }, }; } if (readerSemaphore === null && writerLock !== null && writerLock.hasExpiration && writerLock.expiration <= new Date()) { return null; } if (readerSemaphore === null && writerLock !== null && writerLock.hasExpiration) { return { reader: null, writer: { owner: writerLock.owner, expiration: writerLock.expiration, }, }; } throw new UnexpectedError("Invalid ISharedLockAdapterState, expected either the reader field must be defined or the writer field must be defined, but not both."); } } //# sourceMappingURL=memory-shared-lock-adapter.js.map