attempt to improve mpv exit handlers (#1348)

This commit is contained in:
jeffvli
2025-12-09 18:07:54 -08:00
parent a02fc28785
commit 816df56ef1
+96 -7
View File
@@ -4,6 +4,7 @@ import { rm } from 'fs/promises';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import MpvAPI from 'node-mpv'; import MpvAPI from 'node-mpv';
import { pid } from 'node:process'; import { pid } from 'node:process';
import process from 'process';
import { getMainWindow, sendToastToRenderer } from '../../../index'; import { getMainWindow, sendToastToRenderer } from '../../../index';
import { createLog, isWindows } from '../../../utils'; import { createLog, isWindows } from '../../../utils';
@@ -149,12 +150,28 @@ export const getMpvInstance = () => {
return mpvInstance; return mpvInstance;
}; };
const quit = async () => { const quit = async (instance?: MpvAPI | null) => {
const instance = getMpvInstance(); const mpv = instance || getMpvInstance();
if (instance) { if (mpv) {
await instance.quit(); try {
await mpv.quit();
} catch {
// If quit() fails, try to kill the process directly
const mpvProcess = (mpv as any).process || (mpv as any).mpvProcess;
if (mpvProcess && typeof mpvProcess.kill === 'function') {
try {
mpvProcess.kill('SIGTERM');
} catch (killErr) {
mpvLog({ action: 'Failed to kill mpv process' }, killErr as NodeMpvError);
}
}
}
if (!isWindows()) { if (!isWindows()) {
await rm(socketPath); try {
await rm(socketPath);
} catch {
// Ignore errors when removing socket file
}
} }
} }
}; };
@@ -431,6 +448,36 @@ enum MpvState {
let mpvState = MpvState.STARTED; let mpvState = MpvState.STARTED;
// Cleanup function that can be called from multiple places
const cleanupMpv = async (force = false) => {
if (mpvState === MpvState.DONE && !force) {
return;
}
const instance = getMpvInstance();
if (instance) {
try {
if (!force) {
await instance.stop();
}
await quit(instance);
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to cleanup mpv` }, err);
// Force kill as fallback
const mpvProcess = (instance as any).process || (instance as any).mpvProcess;
if (mpvProcess && typeof mpvProcess.kill === 'function') {
try {
mpvProcess.kill('SIGKILL');
} catch {
// Ignore kill errors
}
}
} finally {
mpvInstance = null;
}
}
};
app.on('before-quit', async (event) => { app.on('before-quit', async (event) => {
switch (mpvState) { switch (mpvState) {
case MpvState.DONE: case MpvState.DONE:
@@ -442,8 +489,7 @@ app.on('before-quit', async (event) => {
try { try {
mpvState = MpvState.IN_PROGRESS; mpvState = MpvState.IN_PROGRESS;
event.preventDefault(); event.preventDefault();
await getMpvInstance()?.stop(); await cleanupMpv();
await quit();
} catch (err: any | NodeMpvError) { } catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to cleanly before-quit` }, err); mpvLog({ action: `Failed to cleanly before-quit` }, err);
} finally { } finally {
@@ -454,3 +500,46 @@ app.on('before-quit', async (event) => {
} }
} }
}); });
// Handle process exit events to ensure mpv is killed even if app crashes
process.on('exit', () => {
const instance = getMpvInstance();
if (instance) {
// Try to access and kill the process directly
const mpvProcess = (instance as any).process || (instance as any).mpvProcess;
if (mpvProcess && typeof mpvProcess.kill === 'function') {
try {
mpvProcess.kill('SIGKILL');
} catch {
// Ignore errors during exit
}
}
}
});
// Handle signals that can terminate the process
process.on('SIGINT', async () => {
await cleanupMpv(true);
process.exit(0);
});
process.on('SIGTERM', async () => {
await cleanupMpv(true);
process.exit(0);
});
// Handle uncaught exceptions - cleanup mpv before crashing
process.on('uncaughtException', async (error) => {
console.error('Uncaught exception:', error);
await cleanupMpv(true).catch(() => {
// Ignore cleanup errors during crash
});
});
// Handle unhandled rejections - cleanup mpv
process.on('unhandledRejection', async (reason) => {
console.error('Unhandled rejection:', reason);
await cleanupMpv(true).catch(() => {
// Ignore cleanup errors
});
});