| ... | ... | @@ -701,6 +701,84 @@ describe("DesktopBackendManager", () => { |
| 701 | 701 | ), |
| 702 | 702 | ); |
| 703 | 703 | |
| 704 | it.effect( |
| 705 | "re-probes readiness after the first budget expires while the backend is still alive", |
| 706 | () => |
| 707 | Effect.scoped( |
| 708 | Effect.gen(function* () { |
| 709 | const requestUrls: Array<string> = []; |
| 710 | let requestCount = 0; |
| 711 | let readyCount = 0; |
| 712 | let readinessTimeoutCount = 0; |
| 713 | const firstProbe = yield* Deferred.make<void>(); |
| 714 | const childExit = yield* Deferred.make<void>(); |
| 715 | |
| 716 | const spawnerLayer = Layer.succeed( |
| 717 | ChildProcessSpawner.ChildProcessSpawner, |
| 718 | ChildProcessSpawner.make(() => |
| 719 | Effect.succeed( |
| 720 | makeProcess({ |
| 721 | exitCode: Deferred.await(childExit).pipe( |
| 722 | Effect.as(ChildProcessSpawner.ExitCode(0)), |
| 723 | ), |
| 724 | }), |
| 725 | ), |
| 726 | ), |
| 727 | ); |
| 728 | |
| 729 | // The backend stays 503 through the first *two* readiness budgets |
| 730 | // and only becomes healthy (200) for the third round, i.e. it comes |
| 731 | // up well after the initial 50ms budget has expired. |
| 732 | const httpLayer = httpClientLayer((request) => |
| 733 | Effect.gen(function* () { |
| 734 | requestCount += 1; |
| 735 | requestUrls.push(request.url); |
| 736 | yield* Deferred.succeed(firstProbe, void 0); |
| 737 | return responseForRequest(request, requestCount <= 2 ? 503 : 200); |
| 738 | }), |
| 739 | ); |
| 740 | |
| 741 | const runFiber = yield* DesktopBackendManager.runBackendProcess({ |
| 742 | ...baseConfig, |
| 743 | desktopTelemetryStream: Stream.empty, |
| 744 | readinessTimeout: Duration.millis(50), |
| 745 | onReady: () => |
| 746 | Effect.sync(() => { |
| 747 | readyCount += 1; |
| 748 | }), |
| 749 | onReadinessFailure: () => |
| 750 | Effect.sync(() => { |
| 751 | readinessTimeoutCount += 1; |
| 752 | }), |
| 753 | }).pipe(Effect.provide(Layer.merge(spawnerLayer, httpLayer)), Effect.forkChild); |
| 754 | |
| 755 | yield* Deferred.await(firstProbe); |
| 756 | assert.equal(readyCount, 0); |
| 757 | assert.equal(readinessTimeoutCount, 0); |
| 758 | |
| 759 | // The first 50ms readiness budget expires while the backend still |
| 760 | // answers 503. The child is alive and may yet become healthy, so the |
| 761 | // probe must start a fresh round instead of stopping permanently — |
| 762 | // the pre-fix behavior left the app stuck on "Connecting to WSL…" |
| 763 | // forever even though the backend kept running. |
| 764 | yield* TestClock.adjust(Duration.millis(50)); |
| 765 | assert.equal(readinessTimeoutCount, 1); |
| 766 | assert.equal(readyCount, 0); |
| 767 | |
| 768 | // The second budget also expires (backend still 503), then the third |
| 769 | // round connects. The point is the probe persisted across budgets |
| 770 | // while the process was alive instead of giving up after the first. |
| 771 | yield* TestClock.adjust(Duration.millis(100)); |
| 772 | assert.equal(readinessTimeoutCount, 2); |
| 773 | assert.equal(readyCount, 1); |
| 774 | assert.equal(requestUrls.length, 3); |
| 775 | |
| 776 | yield* Deferred.succeed(childExit, void 0); |
| 777 | assert.equal((yield* Fiber.join(runFiber)).code.pipe(Option.getOrUndefined), 0); |
| 778 | }).pipe(Effect.provide(TestClock.layer())), |
| 779 | ), |
| 780 | ); |
| 781 | |
| 704 | 782 | it.effect("starts the configured backend and closes the scoped process on stop", () => |
| 705 | 783 | Effect.scoped( |
| 706 | 784 | Effect.gen(function* () { |