Skip to content

Fix: Prevent server process from hanging during stop operation - #1067

Merged
bcotrim merged 4 commits into
trunkfrom
fix/server-stop-hanging
Mar 14, 2025
Merged

Fix: Prevent server process from hanging during stop operation#1067
bcotrim merged 4 commits into
trunkfrom
fix/server-stop-hanging

Conversation

@bcotrim

@bcotrim bcotrim commented Mar 13, 2025

Copy link
Copy Markdown
Contributor

Related issues

Proposed Changes

  • Added isResolved flag to prevent multiple promise resolutions in the #killProcess method
  • Added force kill with SIGKILL if graceful shutdown fails
  • Improved process cleanup by clearing timeouts and handling process state properly

Testing Instructions

Follow the steps mentioned in the issue

  • Setup default WordPress site.
  • Install Remote Data Blocks Plugin.
  • Setup third party data ingestion via Google Sheets (https://github.com/Automattic/remote-data-blocks/blob/trunk/docs/tutorials/google-sheets.md).
  • When the site eventually fails (This site can’t be reached - localhost refused to connect.). Attempt to stop the site.
  • The site stops after the timeout timer runs out, instead of hanging.
  • Confirm that there are no pending processes for that site

More details on reproducing the issue (thanks @ppolo99): p1741865890679379/1741864717.468099-slack-C04GESRBWKW

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
@bcotrim
bcotrim requested a review from a team March 13, 2025 18:41

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does what it says on the tin 👍

I'll create a separate issue for investigating why the Remote Data Blocks plugin throws an error like this, but this is still a great improvement.

Comment thread src/lib/site-server-process.ts Outdated
Comment on lines +152 to +159
if ( process && process.pid ) {
require( 'process' ).kill( process.pid, 'SIGKILL' );
} else {
if ( ! isResolved ) {
isResolved = true;
resolve();
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( process && process.pid ) {
require( 'process' ).kill( process.pid, 'SIGKILL' );
} else {
if ( ! isResolved ) {
isResolved = true;
resolve();
}
}
if ( process.pid ) {
kill( process.pid, 'SIGKILL' );
} else {
isResolved = true;
resolve();
}

Nit, but we already know that process is defined at this point, and we also know that isResolved is false because this callback returns early if that's the case.

I also don't see a good reason to use the require pattern here – we might as well do import { kill } from 'process';

Comment thread src/lib/site-server-process.ts Outdated
}
resolve();
} );
process.kill();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process.kil() returns a boolean per the Electron docs. Is that a useful signal for us maybe..?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice catch!
I've made some adjustments to use the return value from process.kill() instead of the TIMEOUT.
Could you take another look, please? 🙏

@wojtekn wojtekn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @bcotrim .

Could you please update description to account for the timeout removal?

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍 New solution also works as expected.

Comment thread src/lib/site-server-process.ts Outdated
} );
process.kill();
// process.kill() returns false if we're not able to kill the process.
if ( ! process.kill() && ! isResolved ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( ! process.kill() && ! isResolved ) {
if ( ! process.kill() ) {

I can't really imagine a scenario in which isResolved would be true at this point 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I was being extra careful with that condition, but it doesn't make sense anymore with the new approach.
I have removed isResolved check and works as expected.
Thanks for the feedback!

Comment thread src/lib/site-server-process.ts Outdated
reject( new Error( `Site server process exited with code ${ code } upon stopping` ) );
return;
if ( ! isResolved ) {
isResolved = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
isResolved = true;

Nit, but given that we're only listening to the exit event once, do we really need to modify isResolved here?

@bcotrim
bcotrim merged commit da6be10 into trunk Mar 14, 2025
@bcotrim
bcotrim deleted the fix/server-stop-hanging branch March 14, 2025 16:48
bgrgicak pushed a commit that referenced this pull request Mar 20, 2025
* prevent the server from hanging on stop

* adjust forcekill process

* adjust forcekill process

* remove isResolved check
@bcotrim bcotrim self-assigned this Apr 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants