Skip to content

Commit 5b565e8

Browse files
authored
perf(desktop): create windows before the background service connects (#49763)
1 parent ec8d95f commit 5b565e8

2 files changed

Lines changed: 64 additions & 8 deletions

File tree

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,57 @@
11
import { expect, test } from "bun:test"
2-
import { Effect } from "effect"
2+
import { Deferred, Effect, Fiber } from "effect"
33
import { BackgroundServiceState } from "./background-service-state"
44

55
test("new consumers receive the latest reconnected service", async () => {
66
const initial = { url: "http://127.0.0.1:4100", password: "first" }
77
const replacement = { url: "http://127.0.0.1:4200", password: "second" }
88
const service = await Effect.runPromise(
9-
BackgroundServiceState.make({ initial: Effect.succeed(initial), reconnect: Effect.succeed(replacement) }),
9+
Effect.scoped(
10+
Effect.gen(function* () {
11+
const state = yield* BackgroundServiceState.make({
12+
initial: Effect.succeed(initial),
13+
reconnect: Effect.succeed(replacement),
14+
})
15+
expect(yield* state.connection).toEqual(initial)
16+
expect(yield* state.reconnect).toEqual(replacement)
17+
return yield* state.connection
18+
}),
19+
),
1020
)
21+
expect(service).toEqual(replacement)
22+
})
23+
24+
test("the state is ready before the initial connect finishes and consumers wait for it", async () => {
25+
const initial = { url: "http://127.0.0.1:4100", password: "first" }
26+
await Effect.runPromise(
27+
Effect.scoped(
28+
Effect.gen(function* () {
29+
const gate = yield* Deferred.make<void>()
30+
const state = yield* BackgroundServiceState.make({
31+
initial: Deferred.await(gate).pipe(Effect.as(initial)),
32+
reconnect: Effect.succeed(initial),
33+
})
34+
const consumer = yield* Effect.forkChild(state.connection)
35+
yield* Effect.sleep("10 millis")
36+
expect(consumer.pollUnsafe()).toBeUndefined()
37+
yield* Deferred.succeed(gate, undefined)
38+
expect(yield* Fiber.join(consumer)).toEqual(initial)
39+
}),
40+
),
41+
)
42+
})
1143

12-
expect(await Effect.runPromise(service.connection)).toEqual(initial)
13-
expect(await Effect.runPromise(service.reconnect)).toEqual(replacement)
14-
expect(await Effect.runPromise(service.connection)).toEqual(replacement)
44+
test("a failed initial connect fails consumers instead of hanging them", async () => {
45+
const result = await Effect.runPromise(
46+
Effect.scoped(
47+
Effect.gen(function* () {
48+
const state = yield* BackgroundServiceState.make({
49+
initial: Effect.fail(new Error("service unavailable")),
50+
reconnect: Effect.succeed({ url: "http://127.0.0.1:4100", password: "later" }),
51+
})
52+
return yield* state.connection.pipe(Effect.exit)
53+
}),
54+
),
55+
)
56+
expect(result._tag).toBe("Failure")
1557
})
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
11
export * as BackgroundServiceState from "./background-service-state"
22

3-
import { Effect, Exit, Ref } from "effect"
3+
import { Deferred, Effect, Exit, Ref } from "effect"
44
import type { SidecarCredentials } from "./sidecar-credentials"
55

6+
// The initial connect runs in the background: windows are created as soon as the main process
7+
// is up, and the renderer loads its bundle while the CLI service is being resolved. Consumers
8+
// await the first outcome through `connection`, so nothing observes the service before it exists.
69
export const make = Effect.fn("BackgroundServiceState.make")(function* (options: {
710
readonly initial: Effect.Effect<SidecarCredentials.Data, unknown>
811
readonly reconnect: Effect.Effect<SidecarCredentials.Data>
912
}) {
1013
// Every Exit is an Effect, so the latest resolution replays directly for each consumer.
11-
const current = yield* Ref.make<Exit.Exit<SidecarCredentials.Data, unknown>>(yield* options.initial.pipe(Effect.exit))
14+
const current = yield* Ref.make<Exit.Exit<SidecarCredentials.Data, unknown> | undefined>(undefined)
15+
const first = yield* Deferred.make<void>()
16+
yield* options.initial.pipe(
17+
Effect.exit,
18+
Effect.flatMap((exit) => Ref.set(current, exit)),
19+
Effect.ensuring(Deferred.succeed(first, undefined)),
20+
Effect.forkScoped,
21+
)
1222
return {
13-
connection: Ref.get(current).pipe(Effect.flatten, Effect.orDie),
23+
connection: Deferred.await(first).pipe(
24+
Effect.flatMap(() => Ref.get(current)),
25+
Effect.flatMap((exit) => exit ?? Exit.die(new Error("background service connect did not resolve"))),
26+
Effect.orDie,
27+
),
1428
reconnect: options.reconnect.pipe(Effect.tap((next) => Ref.set(current, Exit.succeed(next)))),
1529
}
1630
})

0 commit comments

Comments
 (0)