Skip to content
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

Fix process not ending correctly with shutdown hooks #14433

Closed
wants to merge 3 commits into from

Conversation

thomaschaaf
Copy link

@thomaschaaf thomaschaaf commented Jan 16, 2025

fixes #14432

https://codesandbox.io/p/sandbox/sad-noyce-6zrk6c

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #14432

What is the new behavior?

https://nodejs.org/api/process.html#event-exit is now called when shutdown is complete

Does this PR introduce a breaking change?

  • Yes
  • No

@coveralls
Copy link

coveralls commented Jan 16, 2025

@@ -368,7 +368,7 @@ export class NestApplicationContext<
await this.dispose();
await this.callShutdownHook(signal);
signals.forEach(sig => process.removeListener(sig, cleanup));
process.kill(process.pid, signal);
process.exit(0);
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if I'm following. process.exit (force exit) shouldn't be necessary. We're also overriding the signal (the reason to kill the process) here

Copy link
Author

Choose a reason for hiding this comment

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

console.log("Hello CodeSandbox");

process.on("SIGTERM", () => {
  console.log("SIGTERM received");
  process.kill(process.pid, "SIGTERM");
});

process.on("exit", () => {
  console.log("about to exit");
});

setTimeout(() => {
  console.log("killing");
  process.kill(process.pid, "SIGTERM");
}, 1000);

There "about to exit" is called. I don't understand why my unit test is failing without changing to process.exit(0).

I understand you'd want to wait for node to start the shutdown process - maybe the problem is coming from nestjs/cli / commander not waiting or not emitting the exit event on the child process.

Copy link
Member

Choose a reason for hiding this comment

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

This doesn't work even if you run node dist/main (no NestJS CLI involved)

Copy link
Author

Choose a reason for hiding this comment

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

thomaschaaf@MacBookPro sigterm % cat index.js
console.log("Hello CodeSandbox");

process.on("SIGTERM", () => {
  console.log("SIGTERM received");
  process.kill(process.pid, "SIGTERM");
});

process.on("exit", () => {
  console.log("about to exit");
});

setTimeout(() => {
  console.log("killing");
  process.kill(process.pid, "SIGTERM");
}, 1000);

thomaschaaf@MacBookPro sigterm % node index.js
Hello CodeSandbox
killing
SIGTERM received
about to exit
thomaschaaf@MacBookPro sigterm % node -v
v18.18.2

Copy link
Member

Choose a reason for hiding this comment

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

image

Code

console.log('Hello Node.js');

process.on('exit', () => {
  console.log('about to exit');
});

setTimeout(() => {
  console.log('killing');
  process.kill(process.pid, 'SIGTERM');
}, 1000);

Node v20

Copy link
Member

@kamilmysliwiec kamilmysliwiec Jan 20, 2025

Choose a reason for hiding this comment

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

I still cannot reproduce your issue with the following code:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();
  await app.listen(3000);

  console.log(`Application is running on: ${await app.getUrl()}`);

  process.on('SIGTERM', async () => {
    await app.close();
    console.log('SIGTERM received');
    process.kill(process.pid, 'SIGTERM');
  });

  process.on('exit', () => {
    console.log('about to exit');
  });

  setTimeout(() => {
    console.log('killing');
    process.kill(process.pid, 'SIGTERM');
  }, 1000);
}
bootstrap();

With node dist/main:

Application is running on: http://[::1]:3000
killing
SIGTERM received
about to exit

image

With nest start

image

Same with nest start --watch (npm run start:dev)

image

Copy link
Author

Choose a reason for hiding this comment

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

I know you just released your own json logger - so it might not be your issue.

In the end I only care the exit listener because pino is using https://www.npmjs.com/package/on-exit-leak-free to flush the logs in the shutdown scenario to clear the buffers of the logger. Pino is using a worker thread to do the writing to stdout to be more performant. One alternative solution I came up with was calling logger.flush() before the kill.

iamolegga/nestjs-pino#1859

Copy link
Author

Choose a reason for hiding this comment

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

I will try out my example again and upload it to github - maybe it's something that got changed with v11

Copy link
Author

@thomaschaaf thomaschaaf Jan 20, 2025

Choose a reason for hiding this comment

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

I think you accidentally found the issue 🤯

I created https://github.com/thomaschaaf/debug-nest-js-on-exit you can start it with either

SIGTERM=1 ./node_modules/.bin/nest start

or

SIGTERM=0 ./node_modules/.bin/nest start

This toggles: https://github.com/thomaschaaf/debug-nest-js-on-exit/blob/main/src/main.ts#L30-L33

I have also added an extra console log in pino: https://github.com/thomaschaaf/debug-nest-js-on-exit/blob/main/node_modules/pino/lib/transport.js#L62

The interesting bit is if you have no handler for sigterm the process.on('exit') is not called. I don't understand why.. With the change in the pr it is "fixed" but I understand it seems to be more of a workaround. I guess we will just add a handler

process.on('SIGTERM', () => {
  // fix 'exit' event not triggered without this.
});

Copy link
Member

Choose a reason for hiding this comment

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

I can replicate the exact same behavior using plain Node.js (without the Nest framework/CLI), so I'm not sure if there's anything we need to change on our end.

@thomaschaaf
Copy link
Author

Setting a

process.on('SIGTERM', () => {
  // fix 'exit' event not triggered without this.
});

seems to fix the issue that process.on('exit') does not get triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Shutdown hooks do not trigger process "exit" event
3 participants