keyed-mutex.test.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { describe, expect } from "bun:test"
  2. import { Deferred, Effect, Fiber } from "effect"
  3. import { KeyedMutex } from "@opencode-ai/core/effect/keyed-mutex"
  4. import { it } from "../lib/effect"
  5. describe("KeyedMutex", () => {
  6. it.effect("serializes effects with the same key", () =>
  7. Effect.gen(function* () {
  8. const mutex = yield* KeyedMutex.make<string>()
  9. const firstStarted = yield* Deferred.make<void>()
  10. const releaseFirst = yield* Deferred.make<void>()
  11. const secondStarted = yield* Deferred.make<void>()
  12. const first = yield* mutex
  13. .withLock("shared")(
  14. Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseFirst))),
  15. )
  16. .pipe(Effect.forkChild)
  17. yield* Deferred.await(firstStarted)
  18. const second = yield* mutex.withLock("shared")(Deferred.succeed(secondStarted, undefined)).pipe(Effect.forkChild)
  19. yield* Effect.yieldNow
  20. expect(yield* Deferred.isDone(secondStarted)).toBe(false)
  21. yield* Deferred.succeed(releaseFirst, undefined)
  22. yield* Fiber.join(first)
  23. yield* Fiber.join(second)
  24. expect(yield* mutex.size).toBe(0)
  25. }),
  26. )
  27. it.effect("allows different keys to proceed independently", () =>
  28. Effect.gen(function* () {
  29. const mutex = yield* KeyedMutex.make<string>()
  30. const firstStarted = yield* Deferred.make<void>()
  31. const releaseFirst = yield* Deferred.make<void>()
  32. const secondFinished = yield* Deferred.make<void>()
  33. const first = yield* mutex
  34. .withLock("first")(Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseFirst))))
  35. .pipe(Effect.forkChild)
  36. yield* Deferred.await(firstStarted)
  37. yield* mutex.withLock("second")(Deferred.succeed(secondFinished, undefined))
  38. expect(yield* Deferred.isDone(secondFinished)).toBe(true)
  39. yield* Deferred.succeed(releaseFirst, undefined)
  40. yield* Fiber.join(first)
  41. expect(yield* mutex.size).toBe(0)
  42. }),
  43. )
  44. it.effect("removes an interrupted waiter without dropping the holder lock", () =>
  45. Effect.gen(function* () {
  46. const mutex = yield* KeyedMutex.make<string>()
  47. const firstStarted = yield* Deferred.make<void>()
  48. const releaseFirst = yield* Deferred.make<void>()
  49. const first = yield* mutex
  50. .withLock("shared")(
  51. Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseFirst))),
  52. )
  53. .pipe(Effect.forkChild)
  54. yield* Deferred.await(firstStarted)
  55. const interrupted = yield* mutex.withLock("shared")(Effect.void).pipe(Effect.forkChild)
  56. yield* Effect.yieldNow
  57. yield* Fiber.interrupt(interrupted)
  58. expect(yield* mutex.size).toBe(1)
  59. yield* Deferred.succeed(releaseFirst, undefined)
  60. yield* Fiber.join(first)
  61. expect(yield* mutex.size).toBe(0)
  62. }),
  63. )
  64. })