import { afterEach, expect, test, vi } from "vitest" import { CoordinationAbortError, CoordinationTimeoutError, ReadWriteLock, } from "#Source/orchestration/index.ts" afterEach(() => { vi.clearAllTimers() vi.useRealTimers() vi.restoreAllMocks() }) test("ReadWriteLock tracks reader and writer state across immediate acquisitions", () => { const readWriteLock = new ReadWriteLock() expect(readWriteLock.isLocked()).toBe(false) expect(readWriteLock.isReadLocked()).toBe(false) expect(readWriteLock.isWriteLocked()).toBe(false) const firstReader = readWriteLock.tryAcquireRead() const secondReader = readWriteLock.tryAcquireRead() expect(firstReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" }) expect(secondReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" }) expect(readWriteLock.getActiveReaderCount()).toBe(2) expect(readWriteLock.isReadLocked()).toBe(true) expect(readWriteLock.tryAcquireWrite()).toBeUndefined() firstReader?.release() secondReader?.release() const writer = readWriteLock.tryAcquireWrite() expect(writer?.details).toEqual({ coordination: "read-write-lock", mode: "write" }) expect(readWriteLock.isWriteLocked()).toBe(true) expect(readWriteLock.isLocked()).toBe(true) writer?.release() expect(readWriteLock.isLocked()).toBe(false) }) test("ReadWriteLock keeps queued writers ahead of later readers", async () => { const readWriteLock = new ReadWriteLock() const activeReader = readWriteLock.tryAcquireRead() expect(activeReader).toBeDefined() const writerPromise = readWriteLock.acquireWrite() const lateReaderPromise = readWriteLock.acquireRead() expect(readWriteLock.getPendingWriterCount()).toBe(1) expect(readWriteLock.getPendingReaderCount()).toBe(1) expect(readWriteLock.getPendingCount()).toBe(2) activeReader?.release() const writer = await writerPromise let lateReaderResolved = false void lateReaderPromise.then(() => { lateReaderResolved = true }) await Promise.resolve() expect(readWriteLock.isWriteLocked()).toBe(true) expect(lateReaderResolved).toBe(false) writer.release() const lateReader = await lateReaderPromise expect(readWriteLock.getPendingCount()).toBe(0) expect(readWriteLock.getActiveReaderCount()).toBe(1) lateReader.release() expect(readWriteLock.isLocked()).toBe(false) }) test("ReadWriteLock acquire methods reject on timeout and abort without leaking counts", async () => { vi.useFakeTimers() const readWriteLock = new ReadWriteLock() const writer = readWriteLock.tryAcquireWrite() expect(writer).toBeDefined() const timeoutPromise = readWriteLock.acquireRead({ timeout: 20 }) const timeoutExpectation = expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError) expect(readWriteLock.getPendingReaderCount()).toBe(1) await vi.advanceTimersByTimeAsync(20) await timeoutExpectation expect(readWriteLock.getPendingReaderCount()).toBe(0) const abortController = new AbortController() const abortPromise = readWriteLock.acquireWrite({ abortSignal: abortController.signal }) const abortExpectation = expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError) expect(readWriteLock.getPendingWriterCount()).toBe(1) abortController.abort("cancelled") await abortExpectation expect(readWriteLock.getPendingWriterCount()).toBe(0) writer?.release() }) test("ReadWriteLock runExclusive methods release permits after callback completion", async () => { const readWriteLock = new ReadWriteLock() await expect(readWriteLock.runExclusiveRead(() => "read")).resolves.toBe("read") expect(readWriteLock.isLocked()).toBe(false) const error = new Error("write failed") await expect( readWriteLock.runExclusiveWrite(() => { throw error }), ).rejects.toThrow(error) expect(readWriteLock.isLocked()).toBe(false) }) test("ReadWriteLock duplicate release stays silent by default and uses custom handler when provided", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { // no-op to silence warnings during test }) const silentReadWriteLock = new ReadWriteLock() const silentPermit = silentReadWriteLock.tryAcquireWrite() expect(silentPermit).toBeDefined() silentPermit?.release() silentPermit?.release() silentPermit?.release() expect(silentReadWriteLock.isLocked()).toBe(false) expect(warnSpy).not.toHaveBeenCalled() const onDuplicateRelease = vi.fn() const customReadWriteLock = new ReadWriteLock({ onDuplicateRelease }) const customPermit = customReadWriteLock.tryAcquireRead() expect(customPermit).toBeDefined() customPermit?.release() customPermit?.release() customPermit?.release() expect(customReadWriteLock.isLocked()).toBe(false) expect(onDuplicateRelease).toHaveBeenCalledTimes(2) expect(onDuplicateRelease).toHaveBeenCalledWith( "ReadWriteLock permit release was called more than once.", ) expect(warnSpy).not.toHaveBeenCalled() })