mirror of https://github.com/garrytan/gstack.git
fix: keep browse state until daemon exit
This commit is contained in:
parent
7a693dc052
commit
5152aca65e
|
|
@ -186,10 +186,6 @@ async function shutdown() {
|
||||||
clearInterval(idleCheckInterval);
|
clearInterval(idleCheckInterval);
|
||||||
flushBuffers(); // Final flush
|
flushBuffers(); // Final flush
|
||||||
|
|
||||||
// Remove the state file before closing Chromium so CLI stop/restart polling
|
|
||||||
// can observe shutdown progress even if browser.close() takes a moment.
|
|
||||||
try { fs.unlinkSync(STATE_FILE); } catch {}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Graceful close is best-effort here. If Chromium hangs, still exit so
|
// Graceful close is best-effort here. If Chromium hangs, still exit so
|
||||||
// stop/restart cannot wedge forever.
|
// stop/restart cannot wedge forever.
|
||||||
|
|
@ -199,6 +195,11 @@ async function shutdown() {
|
||||||
]);
|
]);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
// Keep the state file until exit. Other CLI processes treat it as proof that
|
||||||
|
// this PID still owns the port, so deleting it early can trigger a bogus
|
||||||
|
// replacement start while the old daemon is still shutting down.
|
||||||
|
try { fs.unlinkSync(STATE_FILE); } catch {}
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,15 @@ async function cleanupCliState(stateFile: string) {
|
||||||
try { fs.unlinkSync(`${stateFile}.settings.json`); } catch {}
|
try { fs.unlinkSync(`${stateFile}.settings.json`); } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPidAlive(pid: number): boolean {
|
||||||
|
try {
|
||||||
|
process.kill(pid, 0);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Navigation ─────────────────────────────────────────────────
|
// ─── Navigation ─────────────────────────────────────────────────
|
||||||
|
|
||||||
describe('Navigation', () => {
|
describe('Navigation', () => {
|
||||||
|
|
@ -800,6 +809,59 @@ describe('CLI lifecycle', () => {
|
||||||
await cleanupCliState(stateFile);
|
await cleanupCliState(stateFile);
|
||||||
}
|
}
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
test('status during shutdown reuses the live daemon instead of racing a replacement', async () => {
|
||||||
|
const root = fs.mkdtempSync('/tmp/gstack-browse-shutdown-window-');
|
||||||
|
const stateFile = path.join(root, 'browse-state.json');
|
||||||
|
const shutdownMarker = path.join(root, 'shutdown-started');
|
||||||
|
const wrapperPath = path.join(root, 'server-wrapper.ts');
|
||||||
|
const realServerPath = path.resolve(__dirname, '../src/server.ts');
|
||||||
|
const browserManagerPath = path.resolve(__dirname, '../src/browser-manager.ts');
|
||||||
|
const port = reservePort();
|
||||||
|
|
||||||
|
fs.writeFileSync(wrapperPath, `
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { BrowserManager } from ${JSON.stringify(browserManagerPath)};
|
||||||
|
|
||||||
|
const originalClose = BrowserManager.prototype.close;
|
||||||
|
BrowserManager.prototype.close = async function (...args) {
|
||||||
|
fs.writeFileSync(${JSON.stringify(shutdownMarker)}, 'closing');
|
||||||
|
await Bun.sleep(1500);
|
||||||
|
return await originalClose.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
await import(${JSON.stringify(realServerPath)});
|
||||||
|
`);
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
BROWSE_STATE_FILE: stateFile,
|
||||||
|
BROWSE_PORT: String(port),
|
||||||
|
BROWSE_SERVER_SCRIPT: wrapperPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const started = await runCliCommand(['status'], env);
|
||||||
|
const startedState = readJson<{ pid: number }>(stateFile);
|
||||||
|
expect(started.code).toBe(0);
|
||||||
|
expect(startedState?.pid).toBeTruthy();
|
||||||
|
|
||||||
|
const stopPromise = runCliCommand(['stop'], env, 15000);
|
||||||
|
const shutdownStarted = await waitFor(() => fs.existsSync(shutdownMarker) ? true : null, 5000);
|
||||||
|
expect(shutdownStarted).toBe(true);
|
||||||
|
expect(isPidAlive(startedState!.pid)).toBe(true);
|
||||||
|
|
||||||
|
const status = await runCliCommand(['status'], env, 12000);
|
||||||
|
const stop = await stopPromise;
|
||||||
|
|
||||||
|
expect(status.code).toBe(0);
|
||||||
|
expect(status.stdout).toContain('Status: healthy');
|
||||||
|
expect(stop.code).toBe(0);
|
||||||
|
expect(stop.stdout).toContain('Server stopped');
|
||||||
|
} finally {
|
||||||
|
await cleanupCliState(stateFile);
|
||||||
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Buffer bounds ──────────────────────────────────────────────
|
// ─── Buffer bounds ──────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue