Compare commits

...
Author SHA1 Message Date
Brendan Allan 31ebe03714 fix(desktop): exit cleanly when stopping dev server 2026-08-26 15:06:07 +08:00
5 changed files with 25 additions and 4 deletions
@@ -274,6 +274,13 @@ function initConsoleTransport() {
return
}
for (const stream of [process.stdout, process.stderr]) {
stream.on("error", (error) => {
if (!isBrokenPipe(error)) throw error
log.transports.console.level = false
})
}
const writeConsole = log.transports.console.writeFn.bind(log.transports.console)
log.transports.console.writeFn = (options) => {
try {
+1 -1
View File
@@ -101,7 +101,7 @@ export const makeMainWindows = Effect.fn("Window.make")(function* () {
})
allowRendererPermissions(win)
wireWindowRecovery(win, id, () => relaunchHandler())
wireWindowRecovery(win, id, () => relaunchHandler(), registry.isQuitting)
wireNavigationPolicy(win, (url) => runFork(openExternalURL(url)))
wireRendererHeaders(win)
state.manage(win)
+11 -3
View File
@@ -3,6 +3,7 @@ import type { BrowserWindow } from "electron"
import { Effect } from "effect"
import { DesktopLogging, scoped } from "../native/logging"
import { nativeT } from "../native/translations"
import { isRendererUrl } from "./protocol"
import { safeWindowURL } from "./state"
import { makeUnresponsiveSampler } from "./unresponsive"
@@ -13,7 +14,7 @@ export const makeWindowRecovery = Effect.gen(function* () {
const logging = yield* DesktopLogging.Service
const createUnresponsiveSampler = yield* makeUnresponsiveSampler
function wireWindowRecovery(win: BrowserWindow, name: string, relaunch: () => void) {
function wireWindowRecovery(win: BrowserWindow, name: string, relaunch: () => void, isQuitting: () => boolean) {
let showing = false
const sampler = createUnresponsiveSampler(win, name)
@@ -40,10 +41,10 @@ export const makeWindowRecovery = Effect.gen(function* () {
}
const show = async (message: string, detail: string, wait: boolean) => {
if (showing || win.isDestroyed()) return
if (showing || win.isDestroyed() || isQuitting()) return
showing = true
try {
while (!win.isDestroyed()) {
while (!win.isDestroyed() && !isQuitting()) {
const actions: { id: RecoveryAction; label: string }[] = wait
? [
{ id: "relaunch", label: nativeT("desktop.recovery.action.relaunch") },
@@ -78,6 +79,11 @@ export const makeWindowRecovery = Effect.gen(function* () {
validatedURL: string,
isMainFrame: boolean,
) => {
if (isQuitting()) return
if (isMainFrame && errorCode === -102 && process.env.ELECTRON_RENDERER_URL && isRendererUrl(validatedURL)) {
app.quit()
return
}
runFork(
scoped(
"window",
@@ -113,6 +119,7 @@ export const makeWindowRecovery = Effect.gen(function* () {
})
win.webContents.on("render-process-gone", (_event, details) => {
sampler.stopAndFlush()
if (isQuitting()) return
runFork(
scoped(
"window",
@@ -130,6 +137,7 @@ export const makeWindowRecovery = Effect.gen(function* () {
)
})
win.on("unresponsive", () => {
if (isQuitting()) return
runFork(
scoped("window", Effect.logError("renderer unresponsive", { window: name, currentURL: safeWindowURL(win) })),
)
@@ -54,7 +54,9 @@ describe("window registry", () => {
const app = setup()
app.registry.register("a", { name: "a" })
app.registry.register("b", { name: "b" })
expect(app.registry.isQuitting()).toBe(false)
app.registry.setQuitting()
expect(app.registry.isQuitting()).toBe(true)
expect(app.registry.closed("a")).toBe(false)
expect(app.registry.closed("b")).toBe(false)
expect(app.state.stored).toEqual(["a", "b"])
@@ -78,6 +80,7 @@ describe("window registry", () => {
app.registry.register("b", { name: "b" })
app.registry.setQuitting()
app.registry.setQuitting(false)
expect(app.registry.isQuitting()).toBe(false)
expect(app.registry.closed("a")).toBe(true)
expect(app.state.stored).toEqual(["b"])
})
@@ -16,6 +16,9 @@ export function createWindowRegistry<W>(persistence: {
return {
persisted,
isQuitting() {
return quitting
},
setQuitting(value = true) {
quitting = value
},