fix: major bug - detach game process to avoid launcher-held handles causing zombie process

This commit is contained in:
Fazri Gading
2026-02-01 23:12:29 +08:00
parent 5de155f190
commit 6ee23e1944

View File

@@ -356,34 +356,45 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
try { try {
let spawnOptions = { let spawnOptions = {
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],
detached: true, detached: true, // Detach on all platforms to make game process independent
env: env env: env
}; };
if (process.platform === 'win32') { if (process.platform === 'win32') {
spawnOptions.shell = false; spawnOptions.shell = false;
spawnOptions.windowsHide = true; spawnOptions.windowsHide = true;
// Windows: Hide the spawned process window
} }
const child = spawn(clientPath, args, spawnOptions); const child = spawn(clientPath, args, spawnOptions);
// Release process reference immediately so it's truly independent
// This works on all platforms (Windows, macOS, Linux)
child.unref();
console.log(`Game process started with PID: ${child.pid}`); console.log(`Game process started with PID: ${child.pid}`);
let hasExited = false; let hasExited = false;
let outputReceived = false; let outputReceived = false;
let launchCheckTimeout;
child.stdout.on('data', (data) => { if (child.stdout) {
outputReceived = true; child.stdout.on('data', (data) => {
console.log(`Game output: ${data.toString().trim()}`); outputReceived = true;
}); console.log(`Game output: ${data.toString().trim()}`);
});
}
child.stderr.on('data', (data) => { if (child.stderr) {
outputReceived = true; child.stderr.on('data', (data) => {
console.error(`Game error: ${data.toString().trim()}`); outputReceived = true;
}); console.error(`Game error: ${data.toString().trim()}`);
});
}
child.on('error', (error) => { child.on('error', (error) => {
hasExited = true; hasExited = true;
clearTimeout(launchCheckTimeout);
console.error(`Failed to start game process: ${error.message}`); console.error(`Failed to start game process: ${error.message}`);
if (progressCallback) { if (progressCallback) {
progressCallback(`Failed to start game: ${error.message}`, -1, null, null, null); progressCallback(`Failed to start game: ${error.message}`, -1, null, null, null);
@@ -392,6 +403,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
child.on('exit', (code, signal) => { child.on('exit', (code, signal) => {
hasExited = true; hasExited = true;
clearTimeout(launchCheckTimeout);
if (code !== null) { if (code !== null) {
console.log(`Game process exited with code ${code}`); console.log(`Game process exited with code ${code}`);
if (code !== 0 && progressCallback) { if (code !== 0 && progressCallback) {
@@ -402,18 +414,19 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
} }
}); });
// Monitor game process status in background // Monitor game process status in background - wait 5 seconds for verification
setTimeout(() => { launchCheckTimeout = setTimeout(() => {
if (!hasExited) { if (!hasExited) {
console.log('Game appears to be running successfully'); console.log('Game appears to be running successfully');
child.unref(); // Keep process reference active - don't unref() immediately
// This ensures the launcher stays alive while the game is running
if (progressCallback) { if (progressCallback) {
progressCallback('Game launched successfully', 100, null, null, null); progressCallback('Game launched successfully', 100, null, null, null);
} }
} else if (!outputReceived) { } else if (!outputReceived) {
console.warn('Game process exited immediately with no output - possible issue with game files or dependencies'); console.warn('Game process exited immediately with no output - possible issue with game files or dependencies');
} }
}, 3000); }, 5000);
// Return immediately, don't wait for setTimeout // Return immediately, don't wait for setTimeout
return { success: true, installed: true, launched: true, pid: child.pid }; return { success: true, installed: true, launched: true, pid: child.pid };