1import { assert, describe, it } from "@effect/vitest"
2import { Cause, Data, Deferred, Effect, Exit, Fiber, MutableHashMap, Option, RcMap, Ref, Scope } from "effect"
3import { TestClock } from "effect/testing"
4
5describe("RcMap", () => {
6 describe("lookup defects", () => {
7 for (const throws of [false, true]) {
8 it.effect.each([0, 1000])(
9 `releases capacity after a ${throws ? "throwing" : "dying"} lookup with TTL %s`,
10 (idleTimeToLive) =>
11 Effect.gen(function*() {
12 const map = yield* RcMap.make({
13 lookup: (key: string) => {
14 if (key !== "bad") return Effect.succeed(key)
15 if (throws) throw "boom"
16 return Effect.die("boom")
17 },
18 capacity: 1,
19 idleTimeToLive
20 })
21
22 assert.deepStrictEqual(yield* RcMap.get(map, "bad").pipe(Effect.scoped, Effect.exit), Exit.die("boom"))
23 if (idleTimeToLive > 0) {
24 assert.isTrue(yield* RcMap.has(map, "bad"))
25 yield* TestClock.adjust(idleTimeToLive)
26 }
27 assert.deepStrictEqual(yield* RcMap.get(map, "good").pipe(Effect.exit), Exit.succeed("good"))
28 assert.isFalse(yield* RcMap.has(map, "bad"))
29 })
30 )
31
32 it.effect(`shares a ${throws ? "throwing" : "dying"} lookup's defect with later borrowers`, () =>
33 Effect.gen(function*() {
34 const map = yield* RcMap.make({
35 lookup: (_key: string): Effect.Effect<string> => {
36 if (throws) throw "boom"
37 return Effect.die("boom")
38 }
39 })
40
41 assert.deepStrictEqual(yield* RcMap.get(map, "bad").pipe(Effect.exit), Exit.die("boom"))
42 const borrower = yield* RcMap.getOption(map, "bad").pipe(
43 Effect.exit,
44 Effect.timeoutOption(1000),
45 Effect.forkChild({ startImmediately: true })
46 )
47 yield* TestClock.adjust(1000)
48 assert.deepStrictEqual(yield* Fiber.join(borrower), Option.some(Exit.die("boom")))
49 }))
50 }
51 })
52
53 describe("invalidate", () => {
54 it.effect.each([0, 1000, Infinity])(
55 "releases only the invalidated entry with TTL %s",
56 (idleTimeToLive) =>
57 Effect.gen(function*() {
58 const acquired: Array<number> = []
59 const released: Array<number> = []
60 const mapScope = yield* Scope.make()
61 const oldScope = yield* Scope.make()
62 const newScope = yield* Scope.make()
63 const map = yield* RcMap.make({
64 lookup: (_key: string) =>
65 Effect.acquireRelease(
66 Effect.sync(() => {
67 const value = acquired.length + 1
68 acquired.push(value)
69 return value
70 }),
71 (value) => Effect.sync(() => released.push(value))
72 ),
73 idleTimeToLive
74 }).pipe(Scope.provide(mapScope))
75
76 assert.strictEqual(yield* RcMap.get(map, "key").pipe(Scope.provide(oldScope)), 1)
77 yield* RcMap.invalidate(map, "key")
78 assert.isFalse(yield* RcMap.has(map, "key"))
79 assert.deepStrictEqual(released, [])
80 assert.strictEqual(yield* RcMap.get(map, "key").pipe(Scope.provide(newScope)), 2)
81
82 yield* Scope.close(oldScope, Exit.void)
83 const afterOldRelease = { released: [...released], has: yield* RcMap.has(map, "key") }
84 const replacement = yield* Effect.scoped(RcMap.getOption(map, "key"))
85 yield* Scope.close(newScope, Exit.void)
86 const afterNewRelease = [...released]
87 if (idleTimeToLive === 1000) {
88 yield* TestClock.adjust(999)
89 assert.isTrue(yield* RcMap.has(map, "key"))
90 yield* TestClock.adjust(1)
91 assert.isFalse(yield* RcMap.has(map, "key"))
92 }
93 yield* Scope.close(mapScope, Exit.void)
94
95 assert.deepStrictEqual(acquired, [1, 2])
96 assert.deepStrictEqual(afterOldRelease, { released: [1], has: true })
97 assert.deepStrictEqual(replacement, Option.some(2))
98 assert.deepStrictEqual(afterNewRelease, idleTimeToLive === 0 ? [1, 2] : [1])
99 assert.deepStrictEqual(released, [1, 2])
100 })
101 )
102
103 it.effect("an invalidated entry's idle timer cannot remove its replacement", () =>
104 Effect.gen(function*() {
105 const acquired = yield* Ref.make(0)
106 const released: Array<number> = []
107 const mapScope = yield* Scope.make()
108 const map = yield* RcMap.make({
109 lookup: (_key: string) =>
110 Effect.acquireRelease(
111 Ref.updateAndGet(acquired, (n) => n + 1),
112 (value) => Effect.sync(() => released.push(value))
113 ),
114 idleTimeToLive: 1000
115 }).pipe(Scope.provide(mapScope))
116
117 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "key")), 1)
118 yield* TestClock.adjust(500)
119 const oldEntry = map.state._tag === "Open"
120 ? Option.getOrThrow(MutableHashMap.get(map.state.map, "key"))
121 : undefined
122 assert.isDefined(oldEntry)
123 assert.isDefined(oldEntry.fiber)
124 const idleFiber = oldEntry.fiber
125 const oldScope = yield* Scope.make()
126 const newScope = yield* Scope.make()
127 assert.strictEqual(yield* RcMap.get(map, "key").pipe(Scope.provide(oldScope)), 1)
128 yield* RcMap.invalidate(map, "key")
129 assert.deepStrictEqual(released, [])
130 assert.strictEqual(yield* RcMap.get(map, "key").pipe(Scope.provide(newScope)), 2)
131 yield* Scope.close(oldScope, Exit.void)
132 const idleExit = idleFiber.pollUnsafe()
133 assert.isDefined(idleExit)
134 assert.isTrue(Exit.hasInterrupts(idleExit))
135 yield* TestClock.adjust(1000)
136 const replacement = yield* Effect.scoped(RcMap.getOption(map, "key"))
137 const afterTimer = [...released]
138 yield* Scope.close(mapScope, Exit.void)
139 assert.deepStrictEqual(released, [1, 2])
140 yield* Scope.close(newScope, Exit.void)
141
142 assert.deepStrictEqual(replacement, Option.some(2))
143 assert.deepStrictEqual(afterTimer, [1])
144 assert.deepStrictEqual(released, [1, 2])
145 assert.strictEqual(yield* Ref.get(acquired), 2)
146 }))
147 })
148
149 describe("getOption", () => {
150 it.effect("returns None without lookup or capacity acquisition when missing", () =>
151 Effect.gen(function*() {
152 const lookups = yield* Ref.make(0)
153 const map = yield* RcMap.make({
154 lookup: (key: string) => Ref.update(lookups, (n) => n + 1).pipe(Effect.as(key)),
155 capacity: 0
156 })
157
158 assert.deepStrictEqual(yield* RcMap.getOption(map, "missing"), Option.none())
159 assert.deepStrictEqual(yield* RcMap.getOption("missing")(map), Option.none())
160 assert.strictEqual(yield* Ref.get(lookups), 0)
161 }))
162
163 it.effect("retains a ready entry until the caller scope closes", () =>
164 Effect.gen(function*() {
165 const released = yield* Ref.make(0)
166 const map = yield* RcMap.make({
167 lookup: (key: string) =>
168 Effect.acquireRelease(
169 Effect.succeed(key),
170 () => Ref.update(released, (n) => n + 1)
171 )
172 })
173 const ownerScope = yield* Scope.make()
174 const borrowerScope = yield* Scope.make()
175
176 yield* RcMap.get(map, "ready").pipe(Scope.provide(ownerScope))
177 assert.deepStrictEqual(
178 yield* RcMap.getOption("ready")(map).pipe(Scope.provide(borrowerScope)),
179 Option.some("ready")
180 )
181
182 yield* Scope.close(ownerScope, Exit.void)
183 assert.strictEqual(yield* Ref.get(released), 0)
184 yield* Scope.close(borrowerScope, Exit.void)
185 assert.strictEqual(yield* Ref.get(released), 1)
186 }))
187
188 it.effect("shares an in-flight entry", () =>
189 Effect.gen(function*() {
190 const started = yield* Deferred.make<void>()
191 const complete = yield* Deferred.make<void>()
192 const released = yield* Ref.make(0)
193 const map = yield* RcMap.make({
194 lookup: (key: string) =>
195 Effect.acquireRelease(
196 Deferred.succeed(started, void 0).pipe(
197 Effect.andThen(Deferred.await(complete)),
198 Effect.as(key)
199 ),
200 () => Ref.update(released, (n) => n + 1)
201 )
202 })
203 const ownerScope = yield* Scope.make()
204 const borrowerScope = yield* Scope.make()
205 const owner = yield* RcMap.get(map, "pending").pipe(
206 Scope.provide(ownerScope),
207 Effect.forkChild({ startImmediately: true })
208 )
209 yield* Deferred.await(started)
210 const borrower = yield* RcMap.getOption(map, "pending").pipe(
211 Scope.provide(borrowerScope),
212 Effect.forkChild({ startImmediately: true })
213 )
214
215 assert.strictEqual(map.state._tag === "Open" ? Array.from(map.state.map)[0][1].refCount : 0, 2)
216 yield* Deferred.succeed(complete, void 0)
217 assert.strictEqual(yield* Fiber.join(owner), "pending")
218 assert.deepStrictEqual(yield* Fiber.join(borrower), Option.some("pending"))
219
220 yield* Scope.close(ownerScope, Exit.void)
221 assert.strictEqual(yield* Ref.get(released), 0)
222 yield* Scope.close(borrowerScope, Exit.void)
223 assert.strictEqual(yield* Ref.get(released), 1)
224 }))
225
226 it.effect("propagates a cached failure", () =>
227 Effect.gen(function*() {
228 const lookups = yield* Ref.make(0)
229 const map = yield* RcMap.make({
230 lookup: (_key: string) => Ref.update(lookups, (n) => n + 1).pipe(Effect.andThen(Effect.fail("boom")))
231 })
232 const ownerScope = yield* Scope.make()
233 const borrowerScope = yield* Scope.make()
234
235 assert.deepStrictEqual(
236 yield* RcMap.get(map, "failed").pipe(Scope.provide(ownerScope), Effect.exit),
237 Exit.fail("boom")
238 )
239 assert.deepStrictEqual(
240 yield* RcMap.getOption(map, "failed").pipe(Scope.provide(borrowerScope), Effect.exit),
241 Exit.fail("boom")
242 )
243 assert.strictEqual(yield* Ref.get(lookups), 1)
244
245 yield* Scope.close(ownerScope, Exit.void)
246 yield* Scope.close(borrowerScope, Exit.void)
247 }))
248
249 it.effect("retains an idle entry and restarts its TTL on release", () =>
250 Effect.gen(function*() {
251 const released = yield* Ref.make(0)
252 const map = yield* RcMap.make({
253 lookup: (key: string) =>
254 Effect.acquireRelease(
255 Effect.succeed(key),
256 () => Ref.update(released, (n) => n + 1)
257 ),
258 idleTimeToLive: 1000
259 })
260 const ownerScope = yield* Scope.make()
261 yield* RcMap.get(map, "ttl").pipe(Scope.provide(ownerScope))
262 yield* Scope.close(ownerScope, Exit.void)
263
264 yield* TestClock.adjust(500)
265 const borrowerScope = yield* Scope.make()
266 assert.deepStrictEqual(
267 yield* RcMap.getOption(map, "ttl").pipe(Scope.provide(borrowerScope)),
268 Option.some("ttl")
269 )
270 yield* TestClock.adjust(500)
271 assert.strictEqual(yield* Ref.get(released), 0)
272
273 yield* Scope.close(borrowerScope, Exit.void)
274 yield* TestClock.adjust(999)
275 assert.strictEqual(yield* Ref.get(released), 0)
276 yield* TestClock.adjust(1)
277 assert.strictEqual(yield* Ref.get(released), 1)
278 assert.deepStrictEqual(yield* RcMap.getOption(map, "ttl"), Option.none())
279 }))
280
281 it.effect("linearizes retention before invalidation", () =>
282 Effect.gen(function*() {
283 const released = yield* Ref.make(0)
284 const map = yield* RcMap.make({
285 lookup: (key: string) =>
286 Effect.acquireRelease(
287 Effect.succeed(key),
288 () => Ref.update(released, (n) => n + 1)
289 )
290 })
291 const ownerScope = yield* Scope.make()
292 const borrowerScope = yield* Scope.make()
293 yield* RcMap.get(map, "key").pipe(Scope.provide(ownerScope))
294 assert.deepStrictEqual(
295 yield* RcMap.getOption(map, "key").pipe(Scope.provide(borrowerScope)),
296 Option.some("key")
297 )
298
299 yield* RcMap.invalidate(map, "key")
300 assert.isFalse(yield* RcMap.has(map, "key"))
301 yield* Scope.close(ownerScope, Exit.void)
302 assert.strictEqual(yield* Ref.get(released), 0)
303 yield* Scope.close(borrowerScope, Exit.void)
304 assert.strictEqual(yield* Ref.get(released), 1)
305 }))
306
307 it.effect("returns None when the map is closed", () =>
308 Effect.gen(function*() {
309 const mapScope = yield* Scope.make()
310 const map = yield* RcMap.make({
311 lookup: (key: string) => Effect.succeed(key)
312 }).pipe(Scope.provide(mapScope))
313 yield* Scope.close(mapScope, Exit.void)
314
315 assert.deepStrictEqual(yield* RcMap.getOption(map, "key"), Option.none())
316 }))
317 })
318
319 it.effect("deallocation", () =>
320 Effect.gen(function*() {
321 const acquired: Array<string> = []
322 const released: Array<string> = []
323 const mapScope = yield* Scope.make()
324 const map = yield* RcMap.make({
325 lookup: (key: string) =>
326 Effect.acquireRelease(
327 Effect.sync(() => {
328 acquired.push(key)
329 return key
330 }),
331 () => Effect.sync(() => released.push(key))
332 )
333 }).pipe(
334 Scope.provide(mapScope)
335 )
336
337 assert.deepStrictEqual(acquired, [])
338 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
339 assert.deepStrictEqual(acquired, ["foo"])
340 assert.deepStrictEqual(released, ["foo"])
341
342 const scopeA = yield* Scope.make()
343 const scopeB = yield* Scope.make()
344 yield* RcMap.get(map, "bar").pipe(Scope.provide(scopeA))
345 yield* Effect.scoped(RcMap.get(map, "bar"))
346 yield* RcMap.get(map, "baz").pipe(Scope.provide(scopeB))
347 yield* Effect.scoped(RcMap.get(map, "baz"))
348 assert.deepStrictEqual(acquired, ["foo", "bar", "baz"])
349 assert.deepStrictEqual(released, ["foo"])
350 yield* Scope.close(scopeB, Exit.void)
351 assert.deepStrictEqual(acquired, ["foo", "bar", "baz"])
352 assert.deepStrictEqual(released, ["foo", "baz"])
353 yield* Scope.close(scopeA, Exit.void)
354 assert.deepStrictEqual(acquired, ["foo", "bar", "baz"])
355 assert.deepStrictEqual(released, ["foo", "baz", "bar"])
356
357 const scopeC = yield* Scope.make()
358 yield* RcMap.get(map, "qux").pipe(Scope.provide(scopeC))
359 assert.deepStrictEqual(acquired, ["foo", "bar", "baz", "qux"])
360 assert.deepStrictEqual(released, ["foo", "baz", "bar"])
361
362 yield* Scope.close(mapScope, Exit.void)
363 assert.deepStrictEqual(acquired, ["foo", "bar", "baz", "qux"])
364 assert.deepStrictEqual(released, ["foo", "baz", "bar", "qux"])
365
366 const exit = yield* RcMap.get(map, "boom").pipe(Effect.scoped, Effect.exit)
367 assert.isTrue(Exit.hasInterrupts(exit))
368 }))
369
370 it.effect("idleTimeToLive", () =>
371 Effect.gen(function*() {
372 const acquired: Array<string> = []
373 const released: Array<string> = []
374 const map = yield* RcMap.make({
375 lookup: (key: string) =>
376 Effect.acquireRelease(
377 Effect.sync(() => {
378 acquired.push(key)
379 return key
380 }),
381 () => Effect.sync(() => released.push(key))
382 ),
383 idleTimeToLive: 1000
384 })
385
386 assert.deepStrictEqual(acquired, [])
387 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
388 assert.deepStrictEqual(acquired, ["foo"])
389 assert.deepStrictEqual(released, [])
390
391 yield* TestClock.adjust(1000)
392 assert.deepStrictEqual(released, ["foo"])
393
394 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "bar")), "bar")
395 assert.deepStrictEqual(acquired, ["foo", "bar"])
396 assert.deepStrictEqual(released, ["foo"])
397
398 yield* TestClock.adjust(500)
399 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "bar")), "bar")
400 assert.deepStrictEqual(acquired, ["foo", "bar"])
401 assert.deepStrictEqual(released, ["foo"])
402
403 yield* TestClock.adjust(1000)
404 assert.deepStrictEqual(released, ["foo", "bar"])
405
406 yield* Effect.scoped(RcMap.get(map, "baz"))
407 assert.deepStrictEqual(acquired, ["foo", "bar", "baz"])
408 yield* RcMap.invalidate(map, "baz")
409 assert.deepStrictEqual(acquired, ["foo", "bar", "baz"])
410 assert.deepStrictEqual(released, ["foo", "bar", "baz"])
411 }))
412
413 it.effect(".touch", () =>
414 Effect.gen(function*() {
415 const acquired: Array<string> = []
416 const released: Array<string> = []
417 const map = yield* RcMap.make({
418 lookup: (key: string) =>
419 Effect.acquireRelease(
420 Effect.sync(() => {
421 acquired.push(key)
422 return key
423 }),
424 () => Effect.sync(() => released.push(key))
425 ),
426 idleTimeToLive: 1000
427 })
428
429 assert.deepStrictEqual(acquired, [])
430 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
431 assert.deepStrictEqual(acquired, ["foo"])
432 assert.deepStrictEqual(released, [])
433
434 yield* TestClock.adjust(500)
435 assert.deepStrictEqual(released, [])
436
437 yield* RcMap.touch(map, "foo")
438 yield* TestClock.adjust(500)
439 assert.deepStrictEqual(released, [])
440 yield* TestClock.adjust(500)
441 assert.deepStrictEqual(released, ["foo"])
442 }))
443
444 it.effect("capacity", () =>
445 Effect.gen(function*() {
446 const map = yield* RcMap.make({
447 lookup: (key: string) => Effect.succeed(key),
448 capacity: 2,
449 idleTimeToLive: 1000
450 })
451
452 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
453 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
454 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "bar")), "bar")
455
456 const exit = yield* RcMap.get(map, "baz").pipe(Effect.scoped, Effect.exit)
457 assert.deepStrictEqual(
458 exit,
459 Exit.fail(new Cause.ExceededCapacityError(`RcMap attempted to exceed capacity of 2`))
460 )
461
462 yield* TestClock.adjust(1000)
463 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "baz")), "baz")
464 }))
465
466 it.effect("complex key", () =>
467 Effect.gen(function*() {
468 class Key extends Data.Class<{ readonly id: number }> {}
469 const map = yield* RcMap.make({
470 lookup: (key: Key) => Effect.succeed(key.id),
471 capacity: 1
472 })
473
474 assert.strictEqual(yield* RcMap.get(map, new Key({ id: 1 })), 1)
475 // no failure means a hit
476 assert.strictEqual(yield* RcMap.get(map, new Key({ id: 1 })), 1)
477 }))
478
479 it.effect("keys lookup", () =>
480 Effect.gen(function*() {
481 const map = yield* RcMap.make({
482 lookup: (key: string) => Effect.succeed(key)
483 })
484
485 yield* RcMap.get(map, "foo")
486 yield* RcMap.get(map, "bar")
487 yield* RcMap.get(map, "baz")
488
489 assert.deepStrictEqual(Array.from(yield* RcMap.keys(map)), ["foo", "bar", "baz"])
490 }))
491
492 it.effect("dynamic idleTimeToLive", () =>
493 Effect.gen(function*() {
494 const acquired: Array<string> = []
495 const released: Array<string> = []
496 const map = yield* RcMap.make({
497 lookup: (key: string) =>
498 Effect.acquireRelease(
499 Effect.sync(() => {
500 acquired.push(key)
501 return key
502 }),
503 () => Effect.sync(() => released.push(key))
504 ),
505 idleTimeToLive: (key: string) => key.startsWith("short:") ? 500 : 2000
506 })
507
508 assert.deepStrictEqual(acquired, [])
509
510 yield* Effect.scoped(RcMap.get(map, "short:a"))
511 yield* Effect.scoped(RcMap.get(map, "long:b"))
512 assert.deepStrictEqual(acquired, ["short:a", "long:b"])
513 assert.deepStrictEqual(released, [])
514
515 yield* TestClock.adjust(500)
516 assert.deepStrictEqual(released, ["short:a"])
517
518 yield* TestClock.adjust(1500)
519 assert.deepStrictEqual(released, ["short:a", "long:b"])
520 }))
521
522 it.effect("dynamic idleTimeToLive with touch", () =>
523 Effect.gen(function*() {
524 const acquired: Array<string> = []
525 const released: Array<string> = []
526 const map = yield* RcMap.make({
527 lookup: (key: string) =>
528 Effect.acquireRelease(
529 Effect.sync(() => {
530 acquired.push(key)
531 return key
532 }),
533 () => Effect.sync(() => released.push(key))
534 ),
535 idleTimeToLive: (key: string) => key.startsWith("short:") ? 500 : 2000
536 })
537
538 yield* Effect.scoped(RcMap.get(map, "short:a"))
539 assert.deepStrictEqual(acquired, ["short:a"])
540 assert.deepStrictEqual(released, [])
541
542 yield* TestClock.adjust(250)
543 yield* RcMap.touch(map, "short:a")
544 yield* TestClock.adjust(250)
545 assert.deepStrictEqual(released, [])
546
547 yield* TestClock.adjust(250)
548 assert.deepStrictEqual(released, ["short:a"])
549 }))
550
551 it.effect("closing the map while an idle entry is being released runs every finalizer", () =>
552 Effect.gen(function*() {
553 const gate = yield* Deferred.make<void>()
554 let released = 0
555 const scope = yield* Scope.make()
556 const map = yield* RcMap.make({
557 lookup: (_: string) =>
558 Effect.gen(function*() {
559 yield* Effect.addFinalizer(() => Effect.sync(() => released++))
560 yield* Effect.addFinalizer(() => Effect.andThen(Deferred.await(gate), Effect.sync(() => released++)))
561 }),
562 idleTimeToLive: "1 second"
563 }).pipe(Scope.provide(scope))
564 yield* Effect.scoped(RcMap.get(map, "a"))
565 yield* TestClock.adjust("1 second")
566 assert.isFalse(yield* RcMap.has(map, "a"))
567 const close = yield* Effect.forkChild(Scope.close(scope, Exit.void), { startImmediately: true })
568 yield* Deferred.succeed(gate, void 0)
569 yield* Fiber.join(close)
570 assert.strictEqual(released, 2)
571 }))
572
573 it.effect("drops an interrupted lookup and closes its scope", () =>
574 Effect.gen(function*() {
575 let lookups = 0
576 const started = yield* Deferred.make<void>()
577 const interrupt = yield* Deferred.make<void>()
578 const finalized = yield* Deferred.make<void>()
579 const map = yield* RcMap.make({
580 lookup: (_key: string) =>
581 Effect.gen(function*() {
582 if (++lookups > 1) return lookups
583 yield* Effect.addFinalizer(() => Deferred.succeed(finalized, void 0))
584 yield* Deferred.succeed(started, void 0)
585 yield* Deferred.await(interrupt)
586 return yield* Effect.interrupt
587 }),
588 idleTimeToLive: "1 minute"
589 })
590
591 // The only borrower leaves, so nothing else releases the entry.
592 const borrower = yield* Effect.forkChild(Effect.scoped(RcMap.get(map, "key")), { startImmediately: true })
593 yield* Deferred.await(started)
594 yield* Fiber.interrupt(borrower)
595 yield* Deferred.succeed(interrupt, void 0)
596
597 assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "key")), 2)
598 yield* Deferred.await(finalized)
599 }))
600})