| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Deferred, Effect, Exit, Fiber, Scope } from "effect" |
| 3 | import { ResourceMap } from "effect/cluster/internal/resourceMap" |
| 4 | |
| 5 | describe("ResourceMap", () => { |
| 6 | it.effect("closes a failed lookup scope", () => |
| 7 | Effect.scoped(Effect.gen(function*() { |
| 8 | let finalized = 0 |
| 9 | const map = yield* ResourceMap.make((_key: string) => |
| 10 | Effect.gen(function*() { |
| 11 | yield* Effect.addFinalizer(() => Effect.sync(() => finalized++)) |
| 12 | return yield* Effect.fail("failed") |
| 13 | }) |
| 14 | ) |
| 15 | yield* Effect.exit(map.get("key")) |
| 16 | assert.strictEqual(finalized, 1) |
| 17 | }))) |
| 18 | |
| 19 | it.effect("shares a lookup until every get is interrupted, then starts fresh", () => |
| 20 | Effect.scoped(Effect.gen(function*() { |
| 21 | let lookups = 0 |
| 22 | const started = yield* Deferred.make<void>() |
| 23 | const finalizing = yield* Deferred.make<void>() |
| 24 | const finishFinalizer = yield* Deferred.make<void>() |
| 25 | const map = yield* ResourceMap.make((_key: string) => |
| 26 | Effect.suspend(() => { |
| 27 | if (++lookups > 1) return Effect.succeed(lookups) |
| 28 | return Deferred.succeed(started, void 0).pipe( |
| 29 | Effect.andThen(Effect.never), |
| 30 | Effect.onInterrupt(() => |
| 31 | Deferred.succeed(finalizing, void 0).pipe(Effect.andThen(Deferred.await(finishFinalizer))) |
| 32 | ) |
| 33 | ) |
| 34 | }) |
| 35 | ) |
| 36 | |
| 37 | const first = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 38 | yield* Deferred.await(started) |
| 39 | const second = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 40 | yield* Fiber.interrupt(first) |
| 41 | assert.isFalse(yield* Deferred.isDone(finalizing)) |
| 42 | |
| 43 | // A get made while the abandoned lookup finalizes starts a fresh one. |
| 44 | const interruptSecond = yield* Effect.forkChild(Fiber.interrupt(second), { startImmediately: true }) |
| 45 | yield* Deferred.await(finalizing) |
| 46 | const fresh = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 47 | yield* Deferred.succeed(finishFinalizer, void 0) |
| 48 | yield* Fiber.join(interruptSecond) |
| 49 | assert.deepStrictEqual(yield* Fiber.await(fresh), Exit.succeed(2)) |
| 50 | }))) |
| 51 | |
| 52 | it.effect("an abandoned lookup does not remove a replacement entry", () => |
| 53 | Effect.scoped(Effect.gen(function*() { |
| 54 | let lookups = 0 |
| 55 | const started = yield* Deferred.make<void>() |
| 56 | const release = yield* Deferred.make<void>() |
| 57 | const map = yield* ResourceMap.make((_key: string) => |
| 58 | Effect.suspend(() => { |
| 59 | const lookup = ++lookups |
| 60 | if (lookup === 1) return Deferred.succeed(started, void 0).pipe(Effect.andThen(Effect.never)) |
| 61 | return Deferred.await(release).pipe(Effect.as(lookup)) |
| 62 | }) |
| 63 | ) |
| 64 | |
| 65 | const abandoned = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 66 | yield* Deferred.await(started) |
| 67 | yield* map.remove("key") |
| 68 | const replacement = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 69 | yield* Fiber.interrupt(abandoned) |
| 70 | const joiner = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 71 | |
| 72 | yield* Deferred.succeed(release, void 0) |
| 73 | assert.deepStrictEqual(yield* Fiber.await(replacement), Exit.succeed(2)) |
| 74 | assert.deepStrictEqual(yield* Fiber.await(joiner), Exit.succeed(2)) |
| 75 | }))) |
| 76 | |
| 77 | it.effect("closing the map interrupts a pending lookup and closes its scope", () => |
| 78 | Effect.gen(function*() { |
| 79 | let finalized = 0 |
| 80 | const started = yield* Deferred.make<void>() |
| 81 | const mapScope = yield* Scope.make() |
| 82 | const map = yield* ResourceMap.make((_key: string) => |
| 83 | Effect.gen(function*() { |
| 84 | yield* Effect.addFinalizer(() => |
| 85 | Effect.sync(() => { |
| 86 | finalized++ |
| 87 | }) |
| 88 | ) |
| 89 | yield* Deferred.succeed(started, void 0) |
| 90 | return yield* Effect.never |
| 91 | }) |
| 92 | ).pipe(Scope.provide(mapScope)) |
| 93 | |
| 94 | const getter = yield* Effect.forkChild(map.get("key"), { startImmediately: true }) |
| 95 | yield* Deferred.await(started) |
| 96 | yield* Scope.close(mapScope, Exit.void) |
| 97 | |
| 98 | assert.strictEqual(finalized, 1) |
| 99 | assert.isTrue(Exit.hasInterrupts(yield* Fiber.await(getter))) |
| 100 | })) |
| 101 | }) |