Skip to content
Source
Sign inSign up
Open History to see the last commit

KeyedLock.test.ts

114 lines4.2 KB

1import { assert, describe, it } from "@effect/vitest";
2import * as Deferred from "effect/Deferred";
3import * as Effect from "effect/Effect";
4import * as Exit from "effect/Exit";
5import * as Fiber from "effect/Fiber";
6import * as Queue from "effect/Queue";
7import * as Ref from "effect/Ref";
8import * as Scope from "effect/Scope";
9​
10import * as KeyedLock from "./KeyedLock.ts";
11​
12describe("KeyedLock", () => {
13 it.effect("runs one holder of a key at a time, waiters in the order they queued", () =>
14 Effect.gen(function* () {
15 const lock = yield* KeyedLock.make<string>();
16 const events = yield* Ref.make<ReadonlyArray<string>>([]);
17 const record = (event: string) => Ref.update(events, (current) => [...current, event]);
18 const firstEntered = yield* Deferred.make<void>();
19 const releaseFirst = yield* Deferred.make<void>();
20​
21 const first = yield* lock
22 .withLock(
23 "key",
24 record("first:start").pipe(
25 Effect.andThen(Deferred.succeed(firstEntered, undefined)),
26 Effect.andThen(Deferred.await(releaseFirst)),
27 Effect.andThen(record("first:end")),
28 ),
29 )
30 .pipe(Effect.forkChild);
31 yield* Deferred.await(firstEntered);
32 const second = yield* lock.withLock("key", record("second")).pipe(Effect.forkChild);
33 const third = yield* lock.withLock("key", record("third")).pipe(Effect.forkChild);
34 yield* Effect.yieldNow;
35 assert.deepStrictEqual(yield* Ref.get(events), ["first:start"]);
36​
37 yield* Deferred.succeed(releaseFirst, undefined);
38 yield* Fiber.joinAll([first, second, third]);
39 assert.deepStrictEqual(yield* Ref.get(events), [
40 "first:start",
41 "first:end",
42 "second",
43 "third",
44 ]);
45 }),
46 );
47​
48 it.effect("runs holders of different keys concurrently", () =>
49 Effect.gen(function* () {
50 const lock = yield* KeyedLock.make<string>();
51 const entered = yield* Queue.unbounded<string>();
52 const release = yield* Deferred.make<void>();
53 const hold = (key: string) =>
54 lock.withLock(key, Queue.offer(entered, key).pipe(Effect.andThen(Deferred.await(release))));
55​
56 const holders = yield* Effect.forEach(["a", "b"], hold, { concurrency: "unbounded" }).pipe(
57 Effect.forkChild,
58 );
59 const both = new Set([yield* Queue.take(entered), yield* Queue.take(entered)]);
60 assert.deepStrictEqual(both, new Set(["a", "b"]));
61 yield* Deferred.succeed(release, undefined);
62 yield* Fiber.join(holders);
63 }),
64 );
65​
66 it.effect("keeps no lock for a key once its holders and waiters are gone", () =>
67 Effect.gen(function* () {
68 const lock = yield* KeyedLock.make<string>();
69 const entered = yield* Deferred.make<void>();
70 const release = yield* Deferred.make<void>();
71​
72 const holder = yield* lock
73 .withLock(
74 "key",
75 Deferred.succeed(entered, undefined).pipe(Effect.andThen(Deferred.await(release))),
76 )
77 .pipe(Effect.forkChild);
78 yield* Deferred.await(entered);
79 const abandoned = yield* lock.withLock("key", Effect.void).pipe(Effect.forkChild);
80 const waiting = yield* lock.withLock("key", Effect.void).pipe(Effect.forkChild);
81 yield* Effect.yieldNow;
82 assert.deepStrictEqual(yield* lock.activeKeys, ["key"]);
83​
84 yield* Fiber.interrupt(abandoned);
85 assert.deepStrictEqual(yield* lock.activeKeys, ["key"]);
86 yield* Deferred.succeed(release, undefined);
87 yield* Fiber.joinAll([holder, waiting]);
88 assert.strictEqual(
89 yield* lock.withLock("other", Effect.fail("boom")).pipe(Effect.flip),
90 "boom",
91 );
92​
93 assert.deepStrictEqual(yield* lock.activeKeys, []);
94 }),
95 );
96​
97 it.effect("runs the effect in the caller's scope", () =>
98 Effect.gen(function* () {
99 const lock = yield* KeyedLock.make<string>();
100 const finalized = yield* Ref.make(false);
101 const callerScope = yield* Scope.make();
102​
103 yield* lock
104 .withLock(
105 "key",
106 Effect.addFinalizer(() => Ref.set(finalized, true)),
107 )
108 .pipe(Scope.provide(callerScope));
109 assert.isFalse(yield* Ref.get(finalized));
110 yield* Scope.close(callerScope, Exit.void);
111 assert.isTrue(yield* Ref.get(finalized));
112 }),
113 );
114});