Skip to content

Commit 4fd2f0d

Browse files
committed
fix:Windows で「閉じて再起動」時に port 8080 衝突で起動失敗する不具合を修正
根本原因はシングルインスタンスロックの欠如: - ウィンドウ × は Tray に隠す挙動なので、アプリ #1 + Backend #1 が動作中 - もう一度アプリアイコンをクリックすると Electron #2 が起動 → Backend #2 が port 8080 で衝突 修正: - app.requestSingleInstanceLock を導入。2 つ目の起動は既存ウィンドウを前面化 - 孤児バックエンドが残っているケースの保険として、port 占有時に taskkill/pkill で除去
1 parent 6595237 commit 4fd2f0d

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

‎src/main/backend.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { ChildProcess, spawn } from 'child_process'
1+
import { ChildProcess, exec, spawn } from 'child_process'
22
import { randomBytes } from 'crypto'
3+
import { promisify } from 'util'
34
import { app, dialog } from 'electron'
45
import { join } from 'path'
56
import { existsSync } from 'fs'
@@ -68,6 +69,22 @@ function waitForHealth(port: number): Promise<void> {
6869
})
6970
}
7071

72+
const execAsync = promisify(exec)
73+
74+
// 前回セッションのバックエンドが孤児として残っている場合に kill する。
75+
// 通常は before-quit → stopBackend で死ぬが、Electron がクラッシュ or 強制終了されたケースの保険。
76+
async function killOrphanBackends(): Promise<void> {
77+
const cmd =
78+
process.platform === 'win32'
79+
? 'taskkill /F /IM backnote-backend.exe'
80+
: 'pkill -9 -x backnote-backend'
81+
try {
82+
await execAsync(cmd)
83+
} catch {
84+
// 該当プロセスが無ければ非0 で返る。正常系。
85+
}
86+
}
87+
7188
function isPortInUse(port: number): Promise<boolean> {
7289
return new Promise((resolve) => {
7390
let resolved = false
@@ -109,6 +126,10 @@ export async function startBackend(port: number = BACKEND_PORT): Promise<void> {
109126

110127
// 前回のバックエンドが孤児として残っているケースに備えて port が空くのを待つ。
111128
// 空かないまま spawn すると bind 失敗で log.Fatalf → exit ハンドラがダイアログを出してしまう。
129+
if (await isPortInUse(port)) {
130+
// 孤児バックエンドを kill してから空くのを待つ。
131+
await killOrphanBackends()
132+
}
112133
await waitForPortFree(port)
113134

114135
intentionalShutdown = false

‎src/main/index.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ ipcMain.on('hide-tray-popover', () => {
7777
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors,NetworkServiceInProcess2')
7878
app.commandLine.appendSwitch('enable-features', 'NetworkServiceInProcess')
7979

80+
// シングルインスタンスロック。Tray 常駐型なので、ウィンドウを × で閉じた後に
81+
// アプリアイコンを再クリックされても 2 つ目の Electron を起動しない。
82+
// 起動してしまうとバックエンドが port 衝突で死ぬ。
83+
const gotTheLock = app.requestSingleInstanceLock()
84+
if (!gotTheLock) {
85+
app.quit()
86+
}
87+
88+
app.on('second-instance', () => {
89+
if (mainWindow) {
90+
if (!mainWindow.isVisible()) mainWindow.show()
91+
if (mainWindow.isMinimized()) mainWindow.restore()
92+
mainWindow.focus()
93+
}
94+
})
95+
8096
app.whenReady().then(async () => {
8197
try {
8298
await startBackend()

0 commit comments

Comments
 (0)