feat(after): wait for after-callbacks before server shutdown (#72590)
### What
This PR improves the reliability of `unstable_after` in a self-hosted
context, i.e. on a long-running server (`next start`).
when the server receives a signal to gracefully shutdown, we'll wait for
any operations scheduled with `unstable_after` to finish, and then exit.
### Why
It's less surprising than dropping pending operations on the floor if an
instance is getting shut down. This mirrors the standard behavior of
finishing handling pending requests before exiting -- we're just
extending this a bit to include `after` tasks.
### How
Previously `waitUntil` was just a noop in `next start`, so passing a
promise to it did nothing. This PR changes the implementation to instead
use an "awaiter" that stores promises passed to it and lets us wait for
all of them to complete. We then use some existing `onCleanup` machinery
to make sure that they're actually awaited before shutdown.
Note that I've now made `NextServer.close()` execute before shutdown.
This is notable because AFAICT this wasn't being called anywhere before,
which is a bit suspicious.
### How (in a bit more detail)
This is all a bit hard to follow so here's an overview of how it works:
- We have two entrypoints: `startServer` and `NextCustomServer`
- `startServer`:
- creates a cleanup set (`AsyncCallbackSet`)
- calls `createRequestHandlers`, which...
- passes `onDevServerCleanup` to turbopack's hot reloader (previously
`onCleanup`)
- creates a `NextServer`
- which will create a `NextNodeServer`
- which sets up an `Awaiter` to ensure all `after` callbacks finished
running.
- adds a call to `NextServer.close()` to the cleanup set
- when we receive SIGINT/SIGTERM, we run `NextServer.close()`, which
runs `NextNodeServer.close()`, which waits for pending `after` callbacks
to finish using the Awaiter.
- `NextCustomServer`:
- does basically the same setup with a cleanup set and
`createRequestHandlers`
- but it does not set up a SIGINT/SIGTERM handler to run the cleanups.
instead, it expects the user to call its `cleanup()` method when
appropriate (basically like we do in `startServer`)
- i'm gonna do a follow up and update the custom server docs to reflect
this
- also going to try to warn if a cleanup was needed but the process
exited without it calling it