Skip to content

Commit

Permalink
feat: adapter-node shutdown event (#12153)
Browse files Browse the repository at this point in the history
Co-authored-by: Ben McCann <[email protected]>
  • Loading branch information
karimfromjordan and benmccann authored Jun 13, 2024
1 parent 50786af commit 1aa1f32
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-otters-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/adapter-node": minor
---

feat: add shutdown event
5 changes: 5 additions & 0 deletions .changeset/gold-turkeys-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/adapter-node": patch
---

fix: close keep-alive connections as soon as possible during graceful shutdown rather than accepting new requests
31 changes: 15 additions & 16 deletions documentation/docs/25-build-and-deploy/40-adapter-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ By default `adapter-node` gracefully shuts down the HTTP server when a `SIGTERM`

> If you want to customize this behaviour you can use a [custom server](#custom-server).
You can listen to the `sveltekit:shutdown` event which is emitted after the HTTP server has closed all connections. Unlike Node's `exit` event, the `sveltekit:shutdown` event supports asynchronous operations and is always emitted when all connections are closed even if the server has dangling work such as open database connections.

```js
process.on('sveltekit:shutdown', async (reason) => {
await jobs.stop();
await db.close();
});
```

The parameter `reason` has one of the following values:

- `SIGINT` - shutdown was triggered by a `SIGINT` signal
- `SIGTERM` - shutdown was triggered by a `SIGTERM` signal
- `IDLE` - shutdown was triggered by [`IDLE_TIMEOUT`](#environment-variables-idle-timeout)

## Socket activation

Most Linux operating systems today use a modern process manager called systemd to start the server and run and manage services. You can configure your server to allocate a socket and start and scale your app on demand. This is called [socket activation](http://0pointer.de/blog/projects/socket-activated-containers.html). In this case, the OS will pass two environment variables to your app — `LISTEN_PID` and `LISTEN_FDS`. The adapter will then listen on file descriptor 3 which refers to a systemd socket unit that you will have to create.
Expand Down Expand Up @@ -242,19 +257,3 @@ app.listen(3000, () => {
console.log('listening on port 3000');
});
```

## Troubleshooting

### Is there a hook for cleaning up before the app exits?

There's nothing built-in to SvelteKit for this, because such a cleanup hook depends highly on the execution environment you're on. For Node, you can use its built-in `process.on(...)` to implement a callback that runs before the app exits:

```js
// @errors: 2304 2580
function shutdownGracefully() {
// anything you need to clean up manually goes in here
db.shutdown();
}

process.on('exit', shutdownGracefully);
```
23 changes: 16 additions & 7 deletions packages/adapter-node/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,28 @@ if (socket_activation) {
});
}

function shutdown() {
/** @param {'SIGINT' | 'SIGTERM' | 'IDLE'} reason */
function graceful_shutdown(reason) {
if (shutdown_timeout_id) return;

// If a connection was opened with a keep-alive header close() will wait for the connection to
// time out rather than close it even if it is not handling any requests, so call this first
// @ts-expect-error this was added in 18.2.0 but is not reflected in the types
server.server.closeIdleConnections();

server.server.close(() => {
server.server.close((error) => {
// occurs if the server is already closed
if (error) return;

if (shutdown_timeout_id) {
shutdown_timeout_id = clearTimeout(shutdown_timeout_id);
}
if (idle_timeout_id) {
idle_timeout_id = clearTimeout(idle_timeout_id);
}

// @ts-expect-error custom events cannot be typed
process.emit('sveltekit:shutdown', reason);
});

shutdown_timeout_id = setTimeout(
Expand All @@ -77,19 +86,19 @@ server.server.on(
req.on('close', () => {
requests--;

if (requests === 0 && shutdown_timeout_id) {
// when all requests are done, close the connections, so the app shuts down without delay
if (shutdown_timeout_id) {
// close connections as soon as they become idle, so they don't accept new requests
// @ts-expect-error this was added in 18.2.0 but is not reflected in the types
server.server.closeIdleConnections();
}
if (requests === 0 && socket_activation && idle_timeout) {
idle_timeout_id = setTimeout(shutdown, idle_timeout * 1000);
idle_timeout_id = setTimeout(() => graceful_shutdown('IDLE'), idle_timeout * 1000);
}
});
}
);

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGTERM', graceful_shutdown);
process.on('SIGINT', graceful_shutdown);

export { server };

0 comments on commit 1aa1f32

Please sign in to comment.