import { assert, describe, it } from "@effect/vitest" import { Effect, Exit, Fiber, TxReentrantLock } from "effect" describe("TxReentrantLock", () => { describe("constructors", () => { it.effect("make creates an unlocked lock", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() assert.strictEqual(yield* TxReentrantLock.locked(lock), false) assert.strictEqual(yield* TxReentrantLock.readLocked(lock), false) assert.strictEqual(yield* TxReentrantLock.writeLocked(lock), false) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 0) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 0) }))) }) describe("read lock", () => { it.effect("acquireRead and releaseRead work correctly", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const count = yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(count, 1) assert.strictEqual(yield* TxReentrantLock.readLocked(lock), true) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 1) const remaining = yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(remaining, 0) assert.strictEqual(yield* TxReentrantLock.readLocked(lock), false) }))) it.effect("multiple read acquires from same fiber are reentrant", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const c1 = yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(c1, 1) const c2 = yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(c2, 2) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 2) yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 1) yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 0) }))) }) describe("write lock", () => { it.effect("acquireWrite and releaseWrite work correctly", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const count = yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(count, 1) assert.strictEqual(yield* TxReentrantLock.writeLocked(lock), true) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 1) const remaining = yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(remaining, 0) assert.strictEqual(yield* TxReentrantLock.writeLocked(lock), false) }))) it.effect("multiple write acquires from same fiber are reentrant", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const c1 = yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(c1, 1) const c2 = yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(c2, 2) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 2) yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 1) yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 0) }))) it.effect("write lock holder can acquire read lock (reentrancy)", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() yield* TxReentrantLock.acquireWrite(lock) const readCount = yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(readCount, 1) assert.strictEqual(yield* TxReentrantLock.locked(lock), true) yield* TxReentrantLock.releaseRead(lock) yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(yield* TxReentrantLock.locked(lock), false) }))) }) describe("scoped operations", () => { it.effect("readLock scoped acquires and releases", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) yield* Effect.scoped( Effect.gen(function*() { yield* TxReentrantLock.readLock(lock) assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), true) }) ) assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), false) })) it.effect("writeLock scoped acquires and releases", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) yield* Effect.scoped( Effect.gen(function*() { yield* TxReentrantLock.writeLock(lock) assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), true) }) ) assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), false) })) it.effect("writeLock can be acquired reentrantly in the same scope", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) yield* Effect.scoped( Effect.gen(function*() { yield* TxReentrantLock.writeLock(lock) assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), true) }) ) assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), false) })) }) describe("withLock operations", () => { it.effect("withReadLock runs effect and releases", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* TxReentrantLock.withReadLock( lock, Effect.gen(function*() { assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), true) return "read-result" }) ) assert.strictEqual(result, "read-result") assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), false) })) it.effect("withWriteLock runs effect and releases", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* TxReentrantLock.withWriteLock( lock, Effect.gen(function*() { assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), true) return "write-result" }) ) assert.strictEqual(result, "write-result") assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), false) })) it.effect("withLock is alias for withWriteLock", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* TxReentrantLock.withLock( lock, Effect.gen(function*() { assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), true) return "exclusive" }) ) assert.strictEqual(result, "exclusive") assert.strictEqual(yield* Effect.tx(TxReentrantLock.locked(lock)), false) })) it.effect("withReadLock releases on failure", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* Effect.exit( TxReentrantLock.withReadLock( lock, Effect.fail("boom") ) ) assert.isTrue(Exit.isFailure(result)) assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), false) })) it.effect("withWriteLock releases on failure", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* Effect.exit( TxReentrantLock.withWriteLock( lock, Effect.fail("boom") ) ) assert.isTrue(Exit.isFailure(result)) assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), false) })) it.effect("withReadLock data-last (pipe) style", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* lock.pipe( TxReentrantLock.withReadLock(Effect.succeed("piped")) ) assert.strictEqual(result, "piped") assert.strictEqual(yield* Effect.tx(TxReentrantLock.readLocked(lock)), false) })) it.effect("withWriteLock data-last (pipe) style", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const result = yield* lock.pipe( TxReentrantLock.withWriteLock(Effect.succeed("piped-write")) ) assert.strictEqual(result, "piped-write") assert.strictEqual(yield* Effect.tx(TxReentrantLock.writeLocked(lock)), false) })) }) describe("concurrency", () => { it.effect("multiple readers can proceed concurrently", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const fiber1 = yield* Effect.forkChild( TxReentrantLock.withReadLock(lock, Effect.succeed(1)) ) const fiber2 = yield* Effect.forkChild( TxReentrantLock.withReadLock(lock, Effect.succeed(2)) ) const fiber3 = yield* Effect.forkChild( TxReentrantLock.withReadLock(lock, Effect.succeed(3)) ) const [r1, r2, r3] = yield* Effect.all([ Fiber.join(fiber1), Fiber.join(fiber2), Fiber.join(fiber3) ]) assert.strictEqual(r1, 1) assert.strictEqual(r2, 2) assert.strictEqual(r3, 3) assert.strictEqual(yield* Effect.tx(TxReentrantLock.locked(lock)), false) })) it.effect("writer blocks other writers", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const order: Array = [] yield* Effect.tx(TxReentrantLock.acquireWrite(lock)) const fiber = yield* Effect.forkChild( TxReentrantLock.withWriteLock( lock, Effect.sync(() => { order.push("child-wrote") }) ) ) // Child should be blocked because we hold the write lock order.push("parent-releasing") yield* Effect.tx(TxReentrantLock.releaseWrite(lock)) yield* Fiber.join(fiber) assert.deepStrictEqual(order, ["parent-releasing", "child-wrote"]) assert.strictEqual(yield* Effect.tx(TxReentrantLock.locked(lock)), false) })) it.effect("writer blocks readers from other fibers", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const order: Array = [] yield* Effect.tx(TxReentrantLock.acquireWrite(lock)) const fiber = yield* Effect.forkChild( TxReentrantLock.withReadLock( lock, Effect.sync(() => { order.push("child-read") }) ) ) order.push("parent-releasing") yield* Effect.tx(TxReentrantLock.releaseWrite(lock)) yield* Fiber.join(fiber) assert.deepStrictEqual(order, ["parent-releasing", "child-read"]) assert.strictEqual(yield* Effect.tx(TxReentrantLock.locked(lock)), false) })) it.effect("reader blocks writer from another fiber", () => Effect.gen(function*() { const lock = yield* Effect.tx(TxReentrantLock.make()) const order: Array = [] yield* Effect.tx(TxReentrantLock.acquireRead(lock)) const fiber = yield* Effect.forkChild( TxReentrantLock.withWriteLock( lock, Effect.sync(() => { order.push("child-wrote") }) ) ) order.push("parent-releasing") yield* Effect.tx(TxReentrantLock.releaseRead(lock)) yield* Fiber.join(fiber) assert.deepStrictEqual(order, ["parent-releasing", "child-wrote"]) assert.strictEqual(yield* Effect.tx(TxReentrantLock.locked(lock)), false) })) }) describe("getters", () => { it.effect("readLocks counts total read locks", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() yield* TxReentrantLock.acquireRead(lock) yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 2) yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 1) yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(yield* TxReentrantLock.readLocks(lock), 0) }))) it.effect("writeLocks counts write lock depth", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 0) yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 1) yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 2) yield* TxReentrantLock.releaseWrite(lock) yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(yield* TxReentrantLock.writeLocks(lock), 0) }))) it.effect("locked returns true when any lock is held", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() assert.strictEqual(yield* TxReentrantLock.locked(lock), false) yield* TxReentrantLock.acquireRead(lock) assert.strictEqual(yield* TxReentrantLock.locked(lock), true) yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(yield* TxReentrantLock.locked(lock), false) yield* TxReentrantLock.acquireWrite(lock) assert.strictEqual(yield* TxReentrantLock.locked(lock), true) yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(yield* TxReentrantLock.locked(lock), false) }))) }) describe("type guards", () => { it.effect("isTxReentrantLock identifies TxReentrantLock instances", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() assert.isTrue(TxReentrantLock.isTxReentrantLock(lock)) assert.isFalse(TxReentrantLock.isTxReentrantLock({})) assert.isFalse(TxReentrantLock.isTxReentrantLock(null)) assert.isFalse(TxReentrantLock.isTxReentrantLock(undefined)) }))) }) describe("release without acquire", () => { it.effect("releaseRead without acquire returns 0", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const result = yield* TxReentrantLock.releaseRead(lock) assert.strictEqual(result, 0) }))) it.effect("releaseWrite without acquire returns 0", () => Effect.tx(Effect.gen(function*() { const lock = yield* TxReentrantLock.make() const result = yield* TxReentrantLock.releaseWrite(lock) assert.strictEqual(result, 0) }))) }) })