vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.39k stars 26.78k forks source link

Docs: App Router docs for gracefully handling shutdowns still requires pages/_document.js #51404

Open osdiab opened 1 year ago

osdiab commented 1 year ago

What is the improvement or update you wish to see?

The docs for NextJS using the App Router for Manual graceful shutdowns use the pages/_document.js file to handle shutdown signals, though my project doesn't even have a pages/ directory anymore now that everything is moved over to App Router.

The migration docs from the Pages to App Router even has a section on getting rid of the _document.js file, so I would assume this is not supposed to be the way moving forward. Am I wrong?

Is it still the "proper" way to handle this, or is there some other App Router-native method of handling shutdown signals? Thanks!

Is there any context that might help us understand?

I posted a discussion about this but figured that this might be a useful docs improvement (or maybe a missing piece of functionality with the App Router).

Does the docs page already exist? Please link to it.

https://nextjs.org/docs/app/building-your-application/deploying#manual-graceful-shutdowns

DX-1692

balazsorban44 commented 1 year ago

Hi, when self-hosting, the best place to do this might be the emitted server.js file instead, exactly what's the current default already:

https://github.com/vercel/next.js/blob/0dd0ef226cd88a66dca94aa5ce4e55abd1750f1a/packages/next/src/build/utils.ts#L1949-L1954

If you need to override it, you can do so by adding the NEXT_MANUAL_SIG_HANDLE environment variable and adding your own handler.

The documentation on this can be updated to reference server.js instead of pages/_document

osdiab commented 1 year ago

Thanks for the reply - i'm not sure I follow, are you saying that one needs to write a build script to manually patch the generated server.js file? or that you can put a file called server.js in your project to override the default?

If that's the default behavior, it seems that deploying an app on Render's free tier doesn't cause it to be triggered gracefully, I just kept on getting errors reported whenever the app went to sleep.

ncrmro commented 1 year ago

I'm also wondering about this, was hoping to close a database connection on sigterm.

https://github.com/ncrmro/nextjs-sqlite

uyeong commented 1 year ago

In App routes, the code below doesn't seem to work, even though I specify NEXT_MANUAL_SIG_HANDLE as true.

It closes before the graceful shutdown code writed in server.ts (or js) is executed.

mdio commented 12 months ago

@balazsorban44 could you maybe give an example how your proposed solution would work? For standalone the build process will output a server.js which contains the default signal handlers. To register our own, we can set the environment variable NEXT_MANUAL_SIG_HANDLE but then where should the custom signal handling code be put? I can confirm that _document.js from the graceful shutdowns documentation is not working.

Without this we currently experience a downtime of a few seconds every time a Kubernetes pod with Next.js is shut down because it still receives new requests during the shutdown.

jschmidtmac commented 12 months ago

@balazsorban44 could you maybe give an example how your proposed solution would work? For standalone the build process will output a server.js which contains the default signal handlers. To register our own, we can set the environment variable NEXT_MANUAL_SIG_HANDLE but then where should the custom signal handling code be put? I can confirm that _document.js from the graceful shutdowns documentation is not working.

Without this we currently experience a downtime of a few seconds every time a Kubernetes pod with Next.js is shut down because it still receives new requests during the shutdown.

Including NEXT_MANUAL_SIG_HANDLE=true in my .env and setting the handler in the layout.tsx file like so solves the problem for me. Needs to be called in the layout function.

mdio commented 12 months ago

Thank you for help! We're using the Pages Router so layout.tsx is not available. But I tried _document.tsx and it doesn't matter if I put the signal handling code (below) inside the exported render function or outside of it. The result is always that "manual signal handling active" is logged (when passing NEXT_MANUAL_SIG_HANDLE via env) but e.g. "Received SIGINT..." is not logged.

If I manually edit the standalone server.js and add the same listener functions to the event handling there, the "Received SIGINT..." is logged. However, I don't think it's a very stable solution to monkey-patch a generated file. It would be great to have a proper way to hook into signals like it is documented.

The signal handling code I use:

if (process.env.NEXT_MANUAL_SIG_HANDLE) {
  console.log('manual signal handling active');
  process.on('SIGTERM', () => {
    console.log('Received SIGTERM: ', 'cleaning up');
    process.exit(0);
  });

  process.on('SIGINT', () => {
    console.log('Received SIGINT: ', 'cleaning up');
    process.exit(0);
  });
}
higgins commented 11 months ago

As many have observed in this issue, the feature appears to not work. Since this ticket was logged about changing the reference from pages/_document.js, I've reported the bug independently here:

https://github.com/vercel/next.js/issues/56810

higgins commented 11 months ago

@balazsorban44 could you maybe give an example how your proposed solution would work? For standalone the build process will output a server.js which contains the default signal handlers. To register our own, we can set the environment variable NEXT_MANUAL_SIG_HANDLE but then where should the custom signal handling code be put? I can confirm that _document.js from the graceful shutdowns documentation is not working. Without this we currently experience a downtime of a few seconds every time a Kubernetes pod with Next.js is shut down because it still receives new requests during the shutdown.

Including NEXT_MANUAL_SIG_HANDLE=true in my .env and setting the handler in the layout.tsx file like so solves the problem for me. Needs to be called in the layout function.

@jschmidtmac By registering the process.on event handler within the layout function, I suspect this will introduce a memory leak. Each request will register the handler, no?

jschmidtmac commented 11 months ago

@balazsorban44 could you maybe give an example how your proposed solution would work? For standalone the build process will output a server.js which contains the default signal handlers. To register our own, we can set the environment variable NEXT_MANUAL_SIG_HANDLE but then where should the custom signal handling code be put? I can confirm that _document.js from the graceful shutdowns documentation is not working. Without this we currently experience a downtime of a few seconds every time a Kubernetes pod with Next.js is shut down because it still receives new requests during the shutdown.

Including NEXT_MANUAL_SIG_HANDLE=true in my .env and setting the handler in the layout.tsx file like so solves the problem for me. Needs to be called in the layout function.

@jschmidtmac By registering the process.on event handler within the layout function, I suspect this will introduce a memory leak. Each request will register the handler, no?

Good point. We thought that and just tried it anyways, when testing we didn't notice any leaks, we have monitored this since the update as well and haven't noticed anything. This could be with the size of our application/user base. Would think on a larger scale it would cause that. With layout.js replacing both _app.js and _document.js that is kinda where our minds went.

lukevers commented 8 months ago

So... NEXT_MANUAL_SIG_HANDLE does not seem to be working for me. I just spent a few minutes on an idea, but am trying to figure out how crazy I am:

const process = require('process');
const spawn = require('child_process').spawn;

// Adding `detatched: true` makes sure the signal doesn't send down automatically
const server = spawn('pnpm', ['run', 'start'], { detached: true });
server.stdout.on('data', function(data) {
  process.stdout.write(data);
});

server.stderr.on('data', function (data) {
  process.stderr.write(data);
});

server.on('exit', function (code) {
  console.log('Application exited with code:', code.toString());
});

['SIGTERM', 'SIGINT'].forEach((signal) => {
  process.on(signal, function (signal) {
    console.log('Caught signal', signal, '-- Allowing requests to drain');

    setTimeout(() => {
      console.log('Terminating Application')
      server.kill(signal);
    }, 15 * 1000);

    setTimeout(function() {
      console.log('Terminating Wrapper');
      process.exit(0);
    }, 20 * 1000);
  });
});
yubinTW commented 6 months ago

I added process.on function in instrumentation.ts

// instrumentation.ts
export function register() {

  if (process.env.NEXT_RUNTIME === 'nodejs') {
       if (process.env.NEXT_MANUAL_SIG_HANDLE) {
        process.on('SIGTERM', () => {
          /** graceful shutdown **/
        })
      }
  }

}
zenvisuals commented 3 months ago

I added process.on function in instrumentation.ts

// instrumentation.ts
export function register() {

  if (process.env.NEXT_RUNTIME === 'nodejs') {
       if (process.env.NEXT_MANUAL_SIG_HANDLE) {
        process.on('SIGTERM', () => {
          /** graceful shutdown **/
        })
      }
  }

}

Can confirm this works - thanks @yubinTW ! This is probably the best workaround so far until the team adds in a shutdown hook. Remember to put instrumentation.ts in your src folder and enable the instrumentationHook on the config