-
-
Notifications
You must be signed in to change notification settings - Fork 73
Fix gestures #321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix gestures #321
Changes from 4 commits
161bd29
50df3da
3823d2d
9e4e778
b7d2dbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,21 +27,63 @@ if (!gotLock) { | |
| process.exit(0); | ||
| } | ||
|
|
||
| // Centralized server cleanup (idempotent) | ||
| function stopServer() { | ||
| if (serverProcess) { | ||
| try { | ||
| serverProcess.kill('SIGTERM'); | ||
| } catch (e) { | ||
| console.warn('Error stopping server:', e.message); | ||
| } | ||
| serverProcess = null; | ||
| } | ||
| } | ||
|
|
||
| // Wait until server is ready | ||
| function waitForServer(url) { | ||
| return new Promise((resolve) => { | ||
| function waitForServer(url, maxRetries = 20, delay = 500) { | ||
| return new Promise((resolve, reject) => { | ||
| let retries = 0; | ||
|
|
||
| const check = () => { | ||
| http | ||
| .get(url, () => resolve()) | ||
| .on('error', () => setTimeout(check, 500)); | ||
| const req = http | ||
| .get(url, (res) => { | ||
| res.resume(); | ||
|
|
||
| if (res.statusCode === 200) { | ||
| console.log('Server is ready'); | ||
| resolve(); | ||
| } else { | ||
| retry(); | ||
| } | ||
| }) | ||
| .on('error', retry); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Add timeout to prevent hanging | ||
| req.setTimeout(delay, () => { | ||
| req.destroy(new Error('Request timeout')); | ||
| }); | ||
| }; | ||
|
Comment on lines
47
to
65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against double settlement when request completes near timeout boundary. If the response arrives just before the timeout fires, Suggested fix const check = () => {
+ let done = false;
const req = http
.get(url, (res) => {
res.resume();
if (res.statusCode === 200) {
console.log('Server is ready');
+ done = true;
resolve();
} else {
retry();
}
})
- .on('error', retry);
+ .on('error', () => {
+ if (!done) retry();
+ });
// Add timeout to prevent hanging
req.setTimeout(delay, () => {
- req.destroy(new Error('Request timeout'));
+ if (!done) req.destroy(new Error('Request timeout'));
});
};🤖 Prompt for AI Agents |
||
|
|
||
| const retry = () => { | ||
| retries++; | ||
| console.log(`Waiting for server... (${retries}/${maxRetries})`); | ||
|
|
||
| if (retries >= maxRetries) { | ||
| return reject( | ||
| new Error(`Server failed to start after ${maxRetries} attempts`) | ||
| ); | ||
| } | ||
|
|
||
| setTimeout(check, delay); | ||
| }; | ||
|
|
||
| check(); | ||
| }); | ||
| } | ||
|
|
||
| // Start Nitro server (production) | ||
| // Start Nitro server | ||
| function startServer() { | ||
| return new Promise((resolve) => { | ||
| return new Promise((resolve, reject) => { | ||
| const serverPath = path.join( | ||
| process.resourcesPath, | ||
| 'app.asar.unpacked', | ||
|
|
@@ -50,19 +92,49 @@ function startServer() { | |
| 'index.mjs' | ||
| ); | ||
|
|
||
| console.log("Starting server from:", serverPath); | ||
| console.log('Starting server from:', serverPath); | ||
|
|
||
| serverProcess = spawn('node', [serverPath], { | ||
| stdio: 'ignore', // no terminal | ||
| windowsHide: true, // hide CMD | ||
| stdio: 'ignore', | ||
| windowsHide: true, | ||
| env: { | ||
| ...process.env, | ||
| HOST: serverHost, | ||
| PORT: serverPort.toString(), | ||
| }, | ||
|
Comment on lines
97
to
104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a derived connect URL instead of hardcoded This startup path binds Nitro to Suggested fix+function getServerUrl() {
+ const connectHost =
+ serverHost === '0.0.0.0'
+ ? '127.0.0.1'
+ : serverHost === '::'
+ ? '[::1]'
+ : serverHost;
+
+ return `http://${connectHost}:${serverPort}`;
+}
+
...
- waitForServer(`http://localhost:${serverPort}`)
+ waitForServer(getServerUrl())Update Also applies to: 130-137 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| waitForServer(`http://localhost:${serverPort}`).then(resolve); | ||
| // Robust startup handling | ||
| let settled = false; | ||
|
|
||
| const fail = (err) => { | ||
| if (!settled) { | ||
| settled = true; | ||
| stopServer(); | ||
| reject(err); | ||
| } | ||
| }; | ||
|
|
||
| // Handle spawn errors | ||
| serverProcess.once('error', (err) => { | ||
| fail(new Error(`Failed to start Nitro server: ${err.message}`)); | ||
| }); | ||
|
|
||
| // Handle early exit | ||
| serverProcess.once('exit', (code) => { | ||
| if (!settled) { | ||
| fail(new Error(`Nitro server exited early with code ${code}`)); | ||
| } | ||
| }); | ||
|
|
||
| waitForServer(`http://localhost:${serverPort}`) | ||
| .then(() => { | ||
| if (!settled) { | ||
| settled = true; | ||
| resolve(); | ||
| } | ||
| }) | ||
| .catch(fail); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -78,25 +150,40 @@ function createWindow() { | |
|
|
||
| mainWindow.loadURL(`http://localhost:${serverPort}`); | ||
|
|
||
| // Show when ready | ||
| mainWindow.once('ready-to-show', () => { | ||
| mainWindow.show(); | ||
| }); | ||
|
|
||
| // Debug only if needed | ||
| mainWindow.webContents.on('did-fail-load', (e, code, desc) => { | ||
| console.log("LOAD FAILED:", code, desc); | ||
| console.log('LOAD FAILED:', code, desc); | ||
| }); | ||
| } | ||
|
|
||
| // Graceful shutdown | ||
| function shutdown() { | ||
| console.log('Shutting down...'); | ||
| stopServer(); | ||
| app.quit(); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| process.on('SIGINT', shutdown); | ||
| process.on('SIGTERM', shutdown); | ||
|
|
||
| // App start | ||
| app.whenReady().then(async () => { | ||
| await startServer(); | ||
| createWindow(); | ||
| try { | ||
| await startServer(); | ||
| createWindow(); | ||
| } catch (err) { | ||
| console.error('Failed to start server:', err.message); | ||
| stopServer(); | ||
| app.quit(); | ||
| } | ||
| }); | ||
|
|
||
| // Cleanup | ||
| // Cleanup on window close | ||
| app.on('window-all-closed', () => { | ||
| if (serverProcess) serverProcess.kill(); | ||
| stopServer(); | ||
|
|
||
| if (process.platform !== 'darwin') app.quit(); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
In current Node.js, does child_process.ChildProcess.kill('SIGTERM') wait for the child to exit, or does it only send the signal and return immediately? Also, what does a false return value from kill() mean?💡 Result:
In current Node.js, child_process.ChildProcess.kill('SIGTERM') sends the SIGTERM signal to the child process and returns immediately without waiting for the child to exit. The child process may handle the signal, exit gracefully, or ignore it depending on its implementation. A false return value from kill means the underlying kill(2) system call failed (e.g., the process no longer exists, invalid signal on the platform, or permission denied). It does not throw an error synchronously but may emit an 'error' event asynchronously if the signal could not be delivered.
Citations:
Wait for child process exit before clearing the reference.
stopServer()sendsSIGTERMand immediately nullsserverProcess, butkill('SIGTERM')returns instantly without waiting for the child to exit. If Nitro ignores the signal or takes time to shut down, the reference is lost and cannot be used to observe exit or escalate toSIGKILL. This allows the child process to be orphaned even though you intend to terminate it.Keep the reference, listen for the
'exit'event, then apply a timeout-based force-kill if the child doesn't terminate within a reasonable window (e.g., 5 seconds).🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed review!
I've addressed all the reported issues:
waitForServerto prevent hanging requests when the server accepts a connection but does not respond.serverProcess.once('error')to properly catch spawn failures and avoid unhandled crashes.stopServer()helper to encapsulate teardown logic and ensure idempotent cleanup.serverProcess.killcalls acrossshutdown,window-all-closed, and startup failure paths with the new helper.These changes make the startup flow more robust and prevent edge-case hangs and resource leaks.
Please let me know if anything else needs improvement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Shashank-Mengani, thank you for the detailed summary — the improvements you've made are solid. However, the core concern from the original review comment is still unaddressed in the currentstopServer():kill('SIGTERM')returns instantly — it only sends the signal. If Nitro takes time to exit (or ignoresSIGTERM), the reference is gone and you can no longer observe the exit or escalate toSIGKILL, risking an orphaned process.Here is the recommended fix — wait for
'exit', and force-kill after a timeout:Key points:
serverProcessis nulled at entry to prevent re-entrant calls (idempotency preserved).procholds the reference so it can be used in listeners and the force-kill timer.SIGKILLis sent.