From 177148ad5db67e02858d02c20c835b9ab248f1bd Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 2 May 2026 19:06:28 +0200 Subject: [PATCH] perf(electron): defer fixPath() off the synchronous startup path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixPath() was called as the very first statement at module load (top-level, before app.setName, before app.whenReady), which on macOS/Linux spawns an interactive login shell — bash/zsh -ilc 'env' — and waits SYNCHRONOUSLY for it to print the environment back. With oh-my-zsh / heavy .bashrc setups this is routinely 50-300ms blocking the Electron main process before window creation can even begin. The only purpose of fixPath in this app is to populate process.env.PATH so that subsequently-spawned external player binaries (MPV/VLC) can be resolved by bare name. That's a user-action path (clicking play with external player configured), not a startup-critical one. Two callers exist (player.events.ts) and both fall back to bare 'mpv' / 'vlc' only after checking well-known absolute paths. Move the call into a setImmediate scheduled at the END of bootstrapAppEvents — after DB init, IPC handler registration, and window load. The user-visible startup sequence no longer carries the shell-spawn cost. By the time anyone could plausibly click an external player, PATH is already hydrated. Idempotent + Windows-gated (fix-path is a no-op on Windows anyway). Co-Authored-By: Claude Opus 4.7 (1M context) Entire-Checkpoint: f957cd9849e0 --- apps/electron-backend/src/main.ts | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/apps/electron-backend/src/main.ts b/apps/electron-backend/src/main.ts index 7a62233a5..ad3861d1a 100644 --- a/apps/electron-backend/src/main.ts +++ b/apps/electron-backend/src/main.ts @@ -32,6 +32,34 @@ if (electronUserDataPath) { app.setPath('userData', electronUserDataPath); } +let fixPathScheduled = false; + +/** + * Update process.env.PATH from the user's interactive login shell so that + * spawned external players (MPV/VLC) can be resolved by binary name. + * + * Runs after window creation + IPC handler registration so the 50-300 ms + * shell-spawn cost (bash/zsh -ilc env) doesn't block startup. Idempotent: + * subsequent calls are no-ops. + */ +function scheduleDeferredFixPath(): void { + if (fixPathScheduled || process.platform === 'win32') { + return; + } + + fixPathScheduled = true; + setImmediate(() => { + try { + fixPath(); + if (isStartupTraceEnabled()) { + trace('startup', 'fix-path:done'); + } + } catch (error) { + console.warn('fix-path failed:', error); + } + }); +} + export default class Main { static initialize() { if (SquirrelEvents.handleEvents()) { @@ -89,11 +117,18 @@ export default class Main { if (isStartupTraceEnabled()) { trace('startup', 'bootstrap-events:done'); } + + // Hydrate process.env.PATH from the user's login shell now — after + // the window has loaded and IPC handlers are live. Fire-and-forget + // (setImmediate) so it doesn't gate any user-visible work. Worst + // case: the user clicks an external player within the ~100 ms it + // takes to complete; the spawn would still find MPV/VLC at any of + // the well-known paths checked by getDefault*Path before falling + // back to bare-name PATH lookup. + scheduleDeferredFixPath(); } } -fixPath(); - // handle setup events as quickly as possible Main.initialize();