Skip to content

fix(babel-register): preserve app-managed graceful shutdown - #18137

Merged
nicolo-ribaudo merged 5 commits into
babel:mainfrom
jibin7jose:fix-babel-register-graceful-shutdown
Aug 7, 2026
Merged

nicolo-ribaudo merged 5 commits into
babel:mainfrom
jibin7jose:fix-babel-register-graceful-shutdown

Conversation

@jibin7jose

@jibin7jose jibin7jose commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #18134

Description:
This PR addresses an issue where @babel/register forces an early process.exit(0) when receiving SIGTERM or SIGINT signals, which inadvertently interrupts any application-managed graceful shutdown routines.

Changes:

  • Updated the signal listener in packages/babel-register/src/index.ts to check process.listenerCount(signal).
  • If @babel/register is the only listener, it removes itself and re-raises the signal using process.kill() to preserve default Node.js termination behavior.
  • If there are other listeners present, it simply cleans up its worker client and avoids forcing process.exit(0), allowing the rest of the application's shutdown handlers to run naturally.

@babel-bot

babel-bot commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/61948

@pkg-pr-new

pkg-pr-new Bot commented Jul 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

commit: 246e809

@JLHwung

JLHwung commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Thank you. Recently we have added a test framework for -r @babel/register in #18131, could you rebase on the main and add a new test for the change?

@jibin7jose
jibin7jose force-pushed the fix-babel-register-graceful-shutdown branch from 6edcf71 to b80ff22 Compare July 17, 2026 15:25
@jibin7jose

Copy link
Copy Markdown
Contributor Author

@JLHwung Thanks for the review!

I've rebased the branch on top of main and added a new test for the graceful shutdown behavior in packages/babel-register/test/fixtures/preload/--require/graceful-shutdown using the new test framework as requested.

However, it looks like the CI is having some infrastructure issues:

One of the jobs threw a 403 Forbidden error with actions/download-artifact (likely a fork PR permission issue).
The test262 (4) shard failed after 50 minutes (likely a flaky timeout or related to the artifact download issue).
Could you take a look or re-run the failed jobs when you have a moment? Let me know if there's anything else you need me to change on my end!

@JLHwung JLHwung 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.

Thank you. This PR does reveal a question that we never think about before:

Does user expect @babel/register to still work in the shutdown listener?

Currently when @babel/register is preloaded, the babel-register listener is very likely the first registered listener and thus executed earlier than any other user-defined listeners. At the point when user-defined listener runs, @babel/register has already reverted, so any require to other files will not be transpiled.

If @babel/register should be the last listener being removed, we should run the whole client teardown logic only when the listenerCount is 1, though if a user-defined listener does not de-register itself, the @babel/register will never get a chance to be removed.

@nicolo-ribaudo @liuxingbaoyu WDYT?

Comment thread packages/babel-register/src/index.ts Outdated
// eslint-disable-next-line n/no-process-exit
process.exit(0);
if (process.listenerCount(signalOrCode) === 1) {
process.off(signalOrCode, listener!);

@JLHwung JLHwung Jul 17, 2026

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.

We should remove listeners for the other two signals as well, because at this point the client has been closed and re-entry to the listener via a different signal will exit early but we didn't get a chance to remove the listener for that signal.

@JLHwung

JLHwung commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@JLHwung Thanks for the review!

I've rebased the branch on top of main and added a new test for the graceful shutdown behavior in packages/babel-register/test/fixtures/preload/--require/graceful-shutdown using the new test framework as requested.

However, it looks like the CI is having some infrastructure issues:

One of the jobs threw a 403 Forbidden error with actions/download-artifact (likely a fork PR permission issue). The test262 (4) shard failed after 50 minutes (likely a flaky timeout or related to the artifact download issue). Could you take a look or re-run the failed jobs when you have a moment? Let me know if there's anything else you need me to change on my end!

Thanks, the CI error is probably not related. You can naturally retry the CI after addressing my review comments above.

@jibin7jose

Copy link
Copy Markdown
Contributor Author

@JLHwung Thanks for the explanation! I've updated the shutdown logic to cleanly remove the listeners for exit, SIGINT, and SIGTERM as you suggested.

@liuxingbaoyu

Copy link
Copy Markdown
Member

@JLHwung
Is listening exit enough?
Uninstalling the hook might not be very important, as long as we save the cache correctly.

@JLHwung

JLHwung commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@JLHwung Is listening exit enough?

No. Per Node.js docs https://nodejs.org/api/process.html#process_event_exit, the exit event will be fired when

  • The process.exit() method being called explicitly;
  • The Node.js event loop no longer having any additional work to perform.

The default SIGINT handler does not fire the exit event: nodejs/node#2853.

Uninstalling the hook might not be very important, as long as we save the cache correctly.

The timing of hook uninstalling does yield observable behaviour differences: The current behaviour is any user-land exit listener will not be run with babel-register, because at the moment when they run, babel-register has been uninstalled.

@liuxingbaoyu

Copy link
Copy Markdown
Member

Oops, I realize we didn't actually uninstall the hook, it just saved the cache.
I pushed a commit to reflect this.

@liuxingbaoyu

Copy link
Copy Markdown
Member

This PR also fixes another issue: previously, the SIGINT signal would always cause the program to exit, but now users can catch it without exiting.
Thanks! @jibin7jose

@liuxingbaoyu liuxingbaoyu added PR: Bug Fix 🐛 A type of pull request used for our changelog categories pkg: register labels Jul 20, 2026
@nicolo-ribaudo

Copy link
Copy Markdown
Member

Looking at the code I cannot figure out where in the listener we would be uninstalling @babel/register, was that about an older version of the patch and now it's not happening anymore?

@liuxingbaoyu

Copy link
Copy Markdown
Member

In the past, we thought that @babel/register would be unloaded when exit was called, but we later found that we never automatically called revert, and ACTIONS.CLOSE only saved the cache.
Since that's the case, the original behavior doesn't need to be changed.

@nicolo-ribaudo

Copy link
Copy Markdown
Member

Thanks!

@nicolo-ribaudo
nicolo-ribaudo merged commit 36057c7 into babel:main Aug 7, 2026
57 checks passed
@jibin7jose

Copy link
Copy Markdown
Contributor Author

Hi @nicolo-ribaudo — thank you for reviewing and merging the PR. Since the core fix and implementation originated from my contribution in this PR, I’d like to be formally credited as a co-author.
Could you please add the following line to the commit message?
Co-authored-by: Jibin Jose [email protected]
This will ensure my contribution is properly attributed to my GitHub profile and contribution history. Thank you!

@nicolo-ribaudo

nicolo-ribaudo commented Aug 7, 2026

Copy link
Copy Markdown
Member

You are already credited as the main author of the commit: https://github.com/babel/babel/commit/36057c706ce82a0d0a1c8a5f7721e6fcae4ab1b6.patch

@jibin7jose

Copy link
Copy Markdown
Contributor Author

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: register PR: Bug Fix 🐛 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@babel/register forces process.exit(0) on SIGTERM/SIGINT and breaks app-managed graceful shutdown

5 participants