UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

94 lines 3.89 kB
import http from "node:http"; import { waitFor } from "@lodestar/utils"; // Use relatively short timeout to speed up shutdown const GRACEFUL_TERMINATION_TIMEOUT = 1_000; /** * From https://github.com/gajus/http-terminator/blob/aabca4751552e983f8a59ba896b7fb58ce3b4087/src/factories/createInternalHttpTerminator.ts#L24-L61 * But only handles HTTP sockets, exposes the count of sockets as metrics */ export class HttpActiveSocketsTracker { constructor(server, metrics) { this.server = server; this.sockets = new Set(); this.terminating = false; server.on("connection", (socket) => { if (this.terminating) { socket.destroy(Error("Closing")); } else { this.sockets.add(socket); socket.once("close", () => { this.sockets.delete(socket); if (metrics) { metrics.socketsBytesRead.inc(socket.bytesRead); metrics.socketsBytesWritten.inc(socket.bytesWritten); } }); } }); if (metrics) { metrics.activeSockets.addCollect(() => metrics.activeSockets.set(this.sockets.size)); // Note: After some testing seems that `socket.writableLength` does not provide useful data, it's always 0 } } /** * Wait for all connections to drain, forcefully terminate any open connections after timeout * * From https://github.com/gajus/http-terminator/blob/aabca4751552e983f8a59ba896b7fb58ce3b4087/src/factories/createInternalHttpTerminator.ts#L78-L165 * But only handles HTTP sockets and does not close server, immediately closes eventstream API connections */ async terminate() { if (this.terminating) return; this.terminating = true; // Can speed up shutdown by a few milliseconds this.server.closeIdleConnections(); // Inform new incoming requests on keep-alive connections that // the connection will be closed after the current response this.server.on("request", (_req, res) => { if (!res.headersSent) { res.setHeader("Connection", "close"); } }); for (const socket of this.sockets) { // This is the HTTP CONNECT request socket. // @ts-expect-error HTTP sockets have reference to server if (!(socket.server instanceof http.Server)) { continue; } // @ts-expect-error Internal property but only way to access response of socket const res = socket._httpMessage; if (res == null) { // Immediately destroy sockets without an attached HTTP request this.destroySocket(socket); } else if (res.getHeader("Content-Type") === "text/event-stream") { // eventstream API will never stop and must be forcefully closed socket.end(); } else if (!res.headersSent) { // Inform existing keep-alive connections that they will be closed after the current response res.setHeader("Connection", "close"); } } // Wait for all connections to drain, forcefully terminate after timeout try { await waitFor(() => this.sockets.size === 0, { timeout: GRACEFUL_TERMINATION_TIMEOUT, }); } catch (_e) { // Ignore timeout error } finally { for (const socket of this.sockets) { this.destroySocket(socket); } } } destroySocket(socket) { socket.destroy(Error("Closing")); this.sockets.delete(socket); } } //# sourceMappingURL=activeSockets.js.map