effect-browser 0.3.0-beta.13 → 0.3.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +59 -19
  2. package/dist/{Browser-QiT6Cdn_.mjs → Browser-BBSySyjT.mjs} +2 -2
  3. package/dist/{Browser-QiT6Cdn_.mjs.map → Browser-BBSySyjT.mjs.map} +1 -1
  4. package/dist/Browser.d.mts +1 -1
  5. package/dist/Browser.mjs +1 -1
  6. package/dist/Cdp.mjs +1 -1
  7. package/dist/Chromium.mjs +1 -1
  8. package/dist/Expect.d.mts +1 -1
  9. package/dist/Expect.mjs +1 -1
  10. package/dist/{Frame-Di2PxyF7.d.mts → Frame-DkNdjjjr.d.mts} +3 -5
  11. package/dist/Frame.d.mts +1 -1
  12. package/dist/Frame.mjs +2 -4
  13. package/dist/Frame.mjs.map +1 -1
  14. package/dist/Moment.d.mts +1 -1
  15. package/dist/Moment.mjs +7 -3
  16. package/dist/Moment.mjs.map +1 -1
  17. package/dist/{Page-B7X6DQUn.d.mts → Page-DL9eylp3.d.mts} +2 -2
  18. package/dist/Page.d.mts +1 -1
  19. package/dist/{Plan-BS0jpENY.d.mts → Plan-6As6vcwT.d.mts} +3 -3
  20. package/dist/{Plan-Bl2NM1D5.mjs → Plan-CXA2BW-G.mjs} +4 -6
  21. package/dist/Plan-CXA2BW-G.mjs.map +1 -0
  22. package/dist/Plan.d.mts +1 -1
  23. package/dist/Plan.mjs +1 -1
  24. package/dist/Policy.d.mts +1 -1
  25. package/dist/Presentation.d.mts +1 -1
  26. package/dist/Presentation.mjs +1 -1
  27. package/dist/Stage.d.mts +5 -4
  28. package/dist/Stage.mjs +53 -20
  29. package/dist/Stage.mjs.map +1 -1
  30. package/dist/{Supervisor-CYWFK3vM.d.mts → Supervisor-5tZ47hR5.d.mts} +40 -10
  31. package/dist/{Supervisor-TNKNOVzu.mjs → Supervisor-DaGlQVjv.mjs} +120 -68
  32. package/dist/Supervisor-DaGlQVjv.mjs.map +1 -0
  33. package/dist/Supervisor.d.mts +2 -2
  34. package/dist/Supervisor.mjs +2 -2
  35. package/dist/index.d.mts +4 -4
  36. package/dist/index.mjs +3 -3
  37. package/dist/{page-ZxMhw16l.mjs → page-B92QnRfT.mjs} +196 -166
  38. package/dist/page-B92QnRfT.mjs.map +1 -0
  39. package/package.json +1 -1
  40. package/src/Frame.ts +2 -4
  41. package/src/Moment.ts +14 -3
  42. package/src/Plan.ts +7 -6
  43. package/src/Stage.ts +61 -21
  44. package/src/Supervisor.ts +205 -172
  45. package/src/internal/pictures/capture.ts +172 -188
  46. package/src/internal/pictures/pictures.ts +50 -73
  47. package/src/internal/pictures/quiescence.ts +94 -0
  48. package/src/internal/reading/ready.ts +9 -3
  49. package/src/internal/supervisor/lifecycle.ts +40 -15
  50. package/dist/Plan-Bl2NM1D5.mjs.map +0 -1
  51. package/dist/Supervisor-TNKNOVzu.mjs.map +0 -1
  52. package/dist/page-ZxMhw16l.mjs.map +0 -1
package/src/Supervisor.ts CHANGED
@@ -21,6 +21,7 @@
21
21
  import {
22
22
  Cause,
23
23
  Clock,
24
+ Context,
24
25
  DateTime,
25
26
  Deferred,
26
27
  Duration,
@@ -92,9 +93,32 @@ export interface Opened<A = void> {
92
93
  readonly release?: Effect.Effect<Lifecycle.Released> | undefined;
93
94
  }
94
95
 
96
+ /** Facts available before an acquisition attempt; awaiting `after` never changes release order. */
97
+ export interface Admission {
98
+ readonly number: number;
99
+ /**
100
+ * Every earlier session that stopped serving, including failed attempts and older drains.
101
+ * Waits for all pending releases; an Unconfirmed end remains visible to later attempts.
102
+ * Undefined before any release. A still-serving nonexclusive predecessor is excluded.
103
+ */
104
+ readonly after: Effect.Effect<Lifecycle.Released> | undefined;
105
+ }
106
+
107
+ /**
108
+ * Optional service supplied during `open`. Providers report failed-attempt cleanup here before
109
+ * their scope closes; successful generations instead return `Opened.release`. Reporting does
110
+ * not release resources or replace the original acquisition failure.
111
+ */
112
+ export class Releasing extends Context.Service<
113
+ Releasing,
114
+ { readonly released: (outcome: Lifecycle.Released) => Effect.Effect<void> }
115
+ >()("effect-browser/Supervisor/Releasing") {}
116
+
95
117
  export interface Options<E, R, A = void> {
96
118
  /** Opens one generation in the scope it is given. */
97
119
  readonly open: Effect.Effect<Opened<A>, E, R>;
120
+ /** Runs before every open attempt, including the first; failures follow `reopen` and `definite`. */
121
+ readonly admit?: ((opening: Admission) => Effect.Effect<void, E, R>) | undefined;
98
122
  /** When to try a failed open again. Defaults to exponential from 1 second, for 5 minutes. */
99
123
  readonly reopen?: Schedule.Schedule<unknown, E> | undefined;
100
124
  /**
@@ -132,6 +156,8 @@ export interface Lease<A> {
132
156
  readonly generation: number;
133
157
  readonly browser: Browser.Service;
134
158
  readonly value: A;
159
+ /** Declare this serving generation unusable and revoke it now; stale declarations do nothing. */
160
+ readonly lose: (detail: string) => Effect.Effect<void>;
135
161
  /** First notice that serving ended; loss or retirement may revoke before its deadline. */
136
162
  readonly ending: Effect.Effect<Superseded>;
137
163
  /** An independent scope; its resources close before provider release. Fails once revoked. */
@@ -167,13 +193,17 @@ interface Held<A> {
167
193
  readonly opened: Opened<A>;
168
194
  readonly ending: Deferred.Deferred<Superseded>;
169
195
  readonly revoked: Deferred.Deferred<Superseded>;
196
+ readonly released: Deferred.Deferred<Lifecycle.Released>;
170
197
  readonly scope: Scope.Closeable;
171
198
  }
172
199
 
173
200
  type State<A> = Lifecycle.State<Held<A>, Hold>;
174
- type Apply<A> = (
175
- input: Lifecycle.Input<Held<A>, Hold>,
176
- ) => Effect.Effect<Lifecycle.Step<Held<A>, Hold>>;
201
+ type Input<A> = Lifecycle.Input<Held<A>, Hold>;
202
+ type Step<A> = Lifecycle.Step<Held<A>, Hold>;
203
+ type Apply<A> = (input: Input<A>) => Effect.Effect<Step<A>>;
204
+
205
+ const superseded = (generation: number, { reason, revokeAt }: Lifecycle.Draining) =>
206
+ new Superseded({ generation, reason, revokeAt });
177
207
 
178
208
  const retired = () => new Unavailable({ reason: "retired", detail: "the supervisor was retired" });
179
209
 
@@ -183,15 +213,6 @@ const describe = (cause: Cause.Cause<unknown>) => {
183
213
  return Predicate.isError(error) ? error.message : String(error);
184
214
  };
185
215
 
186
- const unavailable = (
187
- resolution: Extract<Lifecycle.Resolution<unknown>, { readonly _tag: "Unavailable" }>,
188
- ) =>
189
- new Unavailable({
190
- reason: resolution.reason,
191
- detail: resolution.detail,
192
- cause: resolution.cause,
193
- });
194
-
195
216
  const timedOut = () =>
196
217
  new Unavailable({ reason: "opening", detail: "no generation opened within the wait" });
197
218
 
@@ -216,7 +237,7 @@ const waitFor = <A, H>(
216
237
  const result = (resolution: Lifecycle.Resolution<A>) =>
217
238
  resolution._tag === "Serve"
218
239
  ? Effect.succeed(resolution.live)
219
- : Effect.fail(unavailable(resolution));
240
+ : Effect.fail(new Unavailable(resolution));
220
241
 
221
242
  return Effect.suspend(() => {
222
243
  const current = Lifecycle.resolve(state.value, target);
@@ -259,25 +280,26 @@ const leasing = <A>(
259
280
  now: () => number,
260
281
  clock: Clock.Clock,
261
282
  ) => {
262
- const borrow = (hold: Hold, wait: Duration.Duration) =>
263
- Effect.gen(function* () {
264
- const until = now() + Duration.toMillis(wait);
265
-
266
- for (;;) {
267
- const step = yield* apply({ _tag: "Hold", hold });
268
-
269
- if (step.borrowed !== undefined) return step.borrowed;
270
- yield* waitFor(state, Duration.millis(Math.max(0, until - now())), clock).pipe(
271
- Effect.interruptible,
272
- );
273
- if (now() >= until) return yield* timedOut();
274
- }
275
- });
283
+ const borrow = Effect.fnUntraced(function* (hold: Hold, wait: Duration.Duration) {
284
+ const until = now() + Duration.toMillis(wait);
285
+
286
+ for (;;) {
287
+ const step = yield* apply({ _tag: "Hold", hold });
288
+
289
+ if (step.borrowed !== undefined) return step.borrowed;
290
+ yield* waitFor(state, Duration.millis(Math.max(0, until - now())), clock).pipe(
291
+ Effect.interruptible,
292
+ );
293
+ if (now() >= until) return yield* timedOut();
294
+ }
295
+ });
276
296
 
277
297
  const leaseOf = (live: Lifecycle.Live<Held<A>>): Lease<A> => ({
278
298
  generation: live.number,
279
299
  browser: live.value.opened.browser,
280
300
  value: live.value.opened.value,
301
+ lose: (detail) =>
302
+ apply({ _tag: "Lost", number: live.number, cause: "declared", detail }).pipe(Effect.asVoid),
281
303
  ending: Deferred.await(live.value.ending),
282
304
  retain: Effect.uninterruptible(
283
305
  Effect.gen(function* () {
@@ -310,9 +332,18 @@ const leasing = <A>(
310
332
  apply({ _tag: "Let", number: live.number, hold }).pipe(Effect.asVoid),
311
333
  );
312
334
 
335
+ const revoked = Deferred.await(live.value.revoked).pipe(Effect.flatMap(Effect.fail));
336
+
313
337
  return yield* Effect.raceFirst(
314
- work(leaseOf(live)),
315
- Deferred.await(live.value.revoked).pipe(Effect.flatMap(Effect.fail)),
338
+ work(leaseOf(live)).pipe(
339
+ // Declaring loss and returning can finish before the race's other fiber runs.
340
+ Effect.tap(() =>
341
+ Deferred.isDone(live.value.revoked).pipe(
342
+ Effect.flatMap((done) => (done ? revoked : Effect.void)),
343
+ ),
344
+ ),
345
+ ),
346
+ revoked,
316
347
  );
317
348
  }),
318
349
  );
@@ -338,16 +369,12 @@ const acquireGeneration = <A, E, R>(
338
369
  scope,
339
370
  ending: Deferred.makeUnsafe<Superseded>(),
340
371
  revoked: Deferred.makeUnsafe<Superseded>(),
372
+ released: Deferred.makeUnsafe<Lifecycle.Released>(),
341
373
  })),
342
374
  ),
343
375
  ),
344
376
  );
345
377
 
346
- const changedUntil = <A>(
347
- state: SubscriptionRef.SubscriptionRef<State<A>>,
348
- predicate: (current: State<A>) => boolean,
349
- ) => SubscriptionRef.changes(state).pipe(Stream.filter(predicate), Stream.runHead, Effect.asVoid);
350
-
351
378
  const watchGeneration = <A>(
352
379
  live: Lifecycle.Live<Held<A>>,
353
380
  rotateBefore: Duration.Duration | undefined,
@@ -369,53 +396,62 @@ const watchGeneration = <A>(
369
396
  return Effect.all([loss, rotation], { concurrency: 2, discard: true });
370
397
  };
371
398
 
372
- // Revocation stops use work; retained resources and use finalizers let go before the provider.
373
- const releaseGeneration = <A>(
399
+ // After revocation, retained resources and use finalizers let go before the provider.
400
+ const releaseGeneration = Effect.fnUntraced(function* <A>(
374
401
  command: Extract<Lifecycle.Command<Held<A>>, { readonly _tag: "Release" | "Leave" }>,
375
402
  state: SubscriptionRef.SubscriptionRef<State<A>>,
376
403
  apply: Apply<A>,
377
- ) =>
378
- Effect.gen(function* () {
379
- const { live, ending } = command;
380
-
381
- yield* Deferred.succeed(
382
- live.value.revoked,
383
- new Superseded({
384
- generation: live.number,
385
- reason: ending.reason,
386
- revokeAt: ending.revokeAt,
387
- }),
388
- );
389
- const holds = Lifecycle.lookup(state.value, live.number)?.holds ?? [];
390
-
391
- const cleaned = yield* Effect.forEach(
392
- holds,
393
- (hold) =>
394
- Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void),
395
- { concurrency: 8 },
396
- );
404
+ ) {
405
+ const { live } = command;
406
+
407
+ const cleaned = yield* Effect.forEach(
408
+ Lifecycle.lookup(state.value, live.number)?.holds ?? [],
409
+ (hold) =>
410
+ Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void),
411
+ { concurrency: 8 },
412
+ );
397
413
 
398
- yield* changedUntil(
399
- state,
400
- (current) => (Lifecycle.lookup(current, live.number)?.holds.size ?? 0) === 0,
401
- );
414
+ yield* SubscriptionRef.changes(state).pipe(
415
+ Stream.filter((current) => (Lifecycle.lookup(current, live.number)?.holds.size ?? 0) === 0),
416
+ Stream.runHead,
417
+ );
402
418
 
403
- const released =
404
- command._tag === "Leave"
405
- ? undefined
406
- : yield* (live.value.opened.release ?? Effect.succeed(new Lifecycle.Settled())).pipe(
407
- Effect.catchCause((cause) =>
408
- Effect.succeed(
409
- new Lifecycle.Unconfirmed({ detail: `the release failed: ${describe(cause)}` }),
410
- ),
419
+ const released =
420
+ command._tag === "Leave"
421
+ ? undefined
422
+ : yield* (live.value.opened.release ?? Effect.succeed(new Lifecycle.Settled())).pipe(
423
+ Effect.catchCause((cause) =>
424
+ Effect.succeed(
425
+ new Lifecycle.Unconfirmed({ detail: `the release failed: ${describe(cause)}` }),
411
426
  ),
412
- );
427
+ ),
428
+ );
413
429
 
414
- const closed = yield* Effect.exit(Scope.close(live.value.scope, Exit.void));
430
+ const closed = yield* Effect.exit(Scope.close(live.value.scope, Exit.void));
415
431
 
416
- yield* apply({ _tag: "Released", number: live.number, released });
417
- yield* Exit.asVoidAll([...cleaned, closed]);
418
- });
432
+ yield* apply({ _tag: "Released", number: live.number, released });
433
+ if (released !== undefined) yield* Deferred.succeed(live.value.released, released);
434
+ yield* Exit.asVoidAll([...cleaned, closed]);
435
+ });
436
+
437
+ const admissionAfter = <A>(state: State<A>) => {
438
+ const released = state.released;
439
+
440
+ const pending = [...state.draining.values()]
441
+ .filter((drain) => drain._tag !== "Leaving")
442
+ .map(({ live }) => live.value.released);
443
+
444
+ return pending.length === 0 && released === undefined
445
+ ? undefined
446
+ : Effect.gen(function* () {
447
+ let outcome = released;
448
+
449
+ for (const release of pending)
450
+ outcome = Lifecycle.combine(outcome, yield* Deferred.await(release));
451
+
452
+ return outcome ?? new Lifecycle.Settled();
453
+ });
454
+ };
419
455
 
420
456
  const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
421
457
  options: MakeOptions<E, R, A>,
@@ -425,7 +461,6 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
425
461
  const context = yield* Effect.context<Exclude<R, Scope.Scope>>();
426
462
  const own = yield* Scope.fork(yield* Effect.scope);
427
463
  const exclusive = options.exclusive ?? false;
428
- const definite = options.definite ?? (() => false);
429
464
 
430
465
  const reopen: Schedule.Schedule<unknown, E> =
431
466
  options.reopen ??
@@ -449,118 +484,116 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
449
484
  const runOpen = yield* FiberHandle.runtime(opening)();
450
485
  const runRelease = yield* FiberMap.runtime(releases)();
451
486
 
452
- const apply = (
453
- input: Lifecycle.Input<Held<A | void>, Hold>,
454
- ): Effect.Effect<Lifecycle.Step<Held<A | void>, Hold>> =>
455
- applying
456
- .withPermit(
457
- Effect.gen(function* () {
458
- const current = state.value;
459
- const step = Lifecycle.transition(current, input, exclusive, now(), handover);
487
+ const apply: Apply<A | void> = Effect.fnUntraced(
488
+ function* (input: Input<A | void>) {
489
+ const current = state.value;
490
+ const step = Lifecycle.transition(current, input, exclusive, now(), handover);
460
491
 
461
- if (step.state !== current) yield* SubscriptionRef.set(state, step.state);
462
- yield* perform(step);
492
+ if (step.state !== current) yield* SubscriptionRef.set(state, step.state);
493
+ yield* perform(step);
463
494
 
464
- return step;
495
+ return step;
496
+ },
497
+ (effect) => applying.withPermit(effect),
498
+ Effect.uninterruptible,
499
+ Effect.provideService(Clock.Clock, clock),
500
+ );
501
+
502
+ const perform = Effect.fnUntraced(function* (step: Step<A | void>): Effect.fn.Return<void> {
503
+ for (const [number, generation] of step.events)
504
+ PubSub.publishUnsafe(events, [
505
+ new Generation({
506
+ number,
507
+ at: clock.currentTimeMillisUnsafe(),
508
+ hostTime: now(),
509
+ state: generation,
465
510
  }),
466
- )
467
- .pipe(Effect.uninterruptible, Effect.provideService(Clock.Clock, clock));
468
-
469
- const perform = (step: Lifecycle.Step<Held<A | void>, Hold>): Effect.Effect<void> =>
470
- Effect.gen(function* () {
471
- for (const [number, generation] of step.events)
472
- PubSub.publishUnsafe(events, [
473
- new Generation({
474
- number,
475
- at: clock.currentTimeMillisUnsafe(),
476
- hostTime: now(),
477
- state: generation,
478
- }),
479
- ]);
480
- for (const command of step.commands)
481
- switch (command._tag) {
482
- case "Open":
483
- runOpen(openGeneration(command.number, command.after));
484
- break;
485
- case "Watch":
486
- yield* Effect.forkIn(
487
- Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context),
488
- command.live.value.scope,
489
- );
490
- break;
491
- case "Drain": {
492
- const ending = new Superseded({
493
- generation: command.live.number,
494
- reason: command.ending.reason,
495
- revokeAt: command.ending.revokeAt,
496
- });
497
-
498
- yield* Deferred.succeed(command.live.value.ending, ending);
499
- yield* Effect.forkIn(
500
- until(ending.revokeAt, now).pipe(
501
- Effect.andThen(
502
- Effect.suspend(() => apply({ _tag: "Revoke", number: command.live.number })),
503
- ),
511
+ ]);
512
+ for (const command of step.commands)
513
+ switch (command._tag) {
514
+ case "Open":
515
+ runOpen(openGeneration(command.number));
516
+ break;
517
+ case "Watch":
518
+ yield* Effect.forkIn(
519
+ Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context),
520
+ command.live.value.scope,
521
+ );
522
+ break;
523
+ case "Drain": {
524
+ const ending = superseded(command.live.number, command.ending);
525
+
526
+ yield* Deferred.succeed(command.live.value.ending, ending);
527
+ yield* Effect.forkIn(
528
+ until(ending.revokeAt, now).pipe(
529
+ Effect.andThen(
530
+ Effect.suspend(() => apply({ _tag: "Revoke", number: command.live.number })),
504
531
  ),
505
- command.live.value.scope,
506
- );
507
- break;
508
- }
509
- case "Release":
510
- case "Leave":
511
- runRelease(command.live.number, releaseGeneration(command, state, apply));
512
- break;
532
+ ),
533
+ command.live.value.scope,
534
+ );
535
+ break;
513
536
  }
514
- });
537
+ case "Release":
538
+ case "Leave":
539
+ yield* Deferred.succeed(
540
+ command.live.value.revoked,
541
+ superseded(command.live.number, command.ending),
542
+ );
543
+ runRelease(command.live.number, releaseGeneration(command, state, apply));
544
+ break;
545
+ }
546
+ });
547
+
548
+ const openGeneration = (number: number) => {
549
+ const openOnce = acquireGeneration(
550
+ Effect.gen(function* () {
551
+ const after = admissionAfter(state.value);
552
+
553
+ if (exclusive && after !== undefined) yield* after;
554
+ yield* options.admit?.({ number, after }) ?? Effect.void;
515
555
 
516
- const openOnce = acquireGeneration(options.open, own).pipe(Effect.provideContext(context));
556
+ return yield* options.open.pipe(
557
+ Effect.provideService(Releasing, {
558
+ released: (released) =>
559
+ apply({ _tag: "AttemptReleased", released }).pipe(Effect.asVoid),
560
+ }),
561
+ );
562
+ }),
563
+ own,
564
+ ).pipe(Effect.provideContext(context));
517
565
 
518
- const openGeneration = (number: number, after: number | undefined) =>
519
- Effect.uninterruptibleMask((restore) =>
566
+ return Effect.uninterruptibleMask((restore) =>
520
567
  restore(
521
- (after === undefined
522
- ? Effect.void
523
- : changedUntil(state, (current) => !current.draining.has(after))
524
- ).pipe(
525
- Effect.andThen(
526
- Effect.retry(openOnce, { schedule: reopen, while: (error) => !definite(error) }),
527
- ),
528
- ),
568
+ Effect.retry(openOnce, {
569
+ schedule: reopen,
570
+ while: (error) => !options.definite?.(error),
571
+ }),
529
572
  ).pipe(
530
573
  Effect.exit,
531
- Effect.flatMap((exit) =>
532
- Exit.isSuccess(exit)
533
- ? apply({ _tag: "Opened", live: { number, value: exit.value } })
534
- : Cause.hasInterruptsOnly(exit.cause)
535
- ? apply({ _tag: "Abandoned", number })
536
- : apply({
537
- _tag: "Failed",
538
- number,
539
- detail: describe(exit.cause),
540
- cause: Cause.squash(exit.cause),
541
- }),
542
- ),
574
+ Effect.flatMap((exit) => {
575
+ if (Exit.isSuccess(exit))
576
+ return apply({ _tag: "Opened", live: { number, value: exit.value } });
577
+ const cause = exit.cause;
578
+
579
+ return apply(
580
+ Cause.hasInterruptsOnly(cause)
581
+ ? { _tag: "Abandoned", number }
582
+ : { _tag: "Failed", number, detail: describe(cause), cause: Cause.squash(cause) },
583
+ );
584
+ }),
543
585
  ),
544
586
  );
545
-
546
- const use = leasing(state, apply, waitTimeout, now, clock);
587
+ };
547
588
 
548
589
  const end = yield* Effect.cached(PubSub.publish(events, Exit.void));
549
590
 
550
- const stop = (input: Lifecycle.Input<Held<A | void>, Hold>) =>
551
- apply(input).pipe(
552
- Effect.andThen(FiberHandle.clear(opening)),
553
- Effect.andThen(FiberMap.awaitEmpty(releases)),
554
- Effect.andThen(
555
- Effect.suspend(() =>
556
- Deferred.isDone(releases.deferred).pipe(
557
- Effect.flatMap((failed) => (failed ? FiberMap.join(releases) : Effect.void)),
558
- ),
559
- ),
560
- ),
561
- Effect.ensuring(end),
562
- Effect.asVoid,
563
- );
591
+ const stop = Effect.fnUntraced(function* (input: Input<A | void>) {
592
+ yield* apply(input);
593
+ yield* FiberHandle.clear(opening);
594
+ yield* FiberMap.awaitEmpty(releases);
595
+ if (yield* Deferred.isDone(releases.deferred)) yield* FiberMap.join(releases);
596
+ }, Effect.ensuring(end));
564
597
 
565
598
  const retire = yield* Effect.cached(stop({ _tag: "Retire" }));
566
599
 
@@ -569,7 +602,7 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
569
602
 
570
603
  return {
571
604
  browser: Effect.map(waitFor(state, waitTimeout, clock), (live) => live.value.opened.browser),
572
- use,
605
+ use: leasing(state, apply, waitTimeout, now, clock),
573
606
  states: Stream.fromPubSubTake(events),
574
607
  rotate: apply({ _tag: "Rotate" }).pipe(
575
608
  Effect.flatMap(({ reply }) =>