From 9f9e5077504028c474782d4ed585b18485c9a544 Mon Sep 17 00:00:00 2001 From: Liam Dyer Date: Tue, 28 May 2024 14:01:11 -0400 Subject: [PATCH 1/2] feat: allow disabling metrics server, consolidate exit handlers --- .env | 3 +- docs/source/_toctree.yml | 2 + docs/source/configuration/metrics.md | 9 ++++ src/hooks.server.ts | 3 ++ src/lib/server/abortedGenerations.ts | 6 +-- src/lib/server/database.ts | 12 ++---- src/lib/server/exitHandler.ts | 41 +++++++++++++++++++ src/lib/server/metrics.ts | 33 +++++++++------ src/lib/server/websearch/scrape/playwright.ts | 3 +- 9 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 docs/source/configuration/metrics.md create mode 100644 src/lib/server/exitHandler.ts diff --git a/.env b/.env index cb79087b5a4..0273b579e03 100644 --- a/.env +++ b/.env @@ -156,5 +156,6 @@ ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to USAGE_LIMITS=`{}` ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls -METRICS_PORT= +METRICS_ENABLED=true +METRICS_PORT=5565 LOG_LEVEL=info diff --git a/docs/source/_toctree.yml b/docs/source/_toctree.yml index 2fc10656dd3..6287065386e 100644 --- a/docs/source/_toctree.yml +++ b/docs/source/_toctree.yml @@ -20,6 +20,8 @@ title: OpenID - local: configuration/web-search title: Web Search + - local: configuration/metrics + title: Metrics - local: configuration/embeddings title: Text Embedding Models - title: Models diff --git a/docs/source/configuration/metrics.md b/docs/source/configuration/metrics.md new file mode 100644 index 00000000000..ddf45a6bd7b --- /dev/null +++ b/docs/source/configuration/metrics.md @@ -0,0 +1,9 @@ +# Metrics + +The server exposes prometheus metrics on port `5565` by default. You may disable the metrics server with `METRICS_ENABLED=false` and change the port with `METRICS_PORT=1234`. + + + +In development with `npm run dev`, the metrics server does not shutdown gracefully due to Sveltekit not providing hooks for restart. It's recommended to disable the metrics server in this case. + + diff --git a/src/hooks.server.ts b/src/hooks.server.ts index ff4f044eca4..d9eb20c3750 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -13,9 +13,12 @@ import { refreshAssistantsCounts } from "$lib/assistantStats/refresh-assistants- import { logger } from "$lib/server/logger"; import { AbortedGenerations } from "$lib/server/abortedGenerations"; import { MetricsServer } from "$lib/server/metrics"; +import { initExitHandler } from "$lib/server/exitHandler"; // TODO: move this code on a started server hook, instead of using a "building" flag if (!building) { + initExitHandler(); + await checkAndRunMigrations(); if (env.ENABLE_ASSISTANTS) { refreshAssistantsCounts(); diff --git a/src/lib/server/abortedGenerations.ts b/src/lib/server/abortedGenerations.ts index 548809d6b6c..6ff15d917d4 100644 --- a/src/lib/server/abortedGenerations.ts +++ b/src/lib/server/abortedGenerations.ts @@ -2,6 +2,7 @@ import { logger } from "$lib/server/logger"; import { collections } from "$lib/server/database"; +import { onExit } from "./exitHandler"; export class AbortedGenerations { private static instance: AbortedGenerations; @@ -10,10 +11,7 @@ export class AbortedGenerations { private constructor() { const interval = setInterval(this.updateList, 1000); - - process.on("SIGINT", () => { - clearInterval(interval); - }); + onExit(() => clearInterval(interval)); } public static getInstance(): AbortedGenerations { diff --git a/src/lib/server/database.ts b/src/lib/server/database.ts index 8a3fe361c24..1725cb19ba8 100644 --- a/src/lib/server/database.ts +++ b/src/lib/server/database.ts @@ -15,6 +15,7 @@ import type { Semaphore } from "$lib/types/Semaphore"; import type { AssistantStats } from "$lib/types/AssistantStats"; import { logger } from "$lib/server/logger"; import { building } from "$app/environment"; +import { onExit } from "./exitHandler"; export const CONVERSATION_STATS_COLLECTION = "conversations.stats"; @@ -41,15 +42,8 @@ export class Database { this.client.db(env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : "")); this.client.on("open", () => this.initDatabase()); - // Disconnect DB on process kill - process.on("SIGINT", async () => { - await this.client.close(true); - - // https://github.com/sveltejs/kit/issues/9540 - setTimeout(() => { - process.exit(0); - }, 100); - }); + // Disconnect DB on exit + onExit(() => this.client.close(true)); } public static getInstance(): Database { diff --git a/src/lib/server/exitHandler.ts b/src/lib/server/exitHandler.ts new file mode 100644 index 00000000000..a28b2bd10f5 --- /dev/null +++ b/src/lib/server/exitHandler.ts @@ -0,0 +1,41 @@ +import { randomUUID } from "$lib/utils/randomUuid"; +import { timeout } from "$lib/utils/timeout"; +import { logger } from "./logger"; + +type ExitHandler = () => void | Promise; +type ExitHandlerUnsubscribe = () => void; + +const listeners = new Map(); + +export function onExit(cb: ExitHandler): ExitHandlerUnsubscribe { + const uuid = randomUUID(); + listeners.set(uuid, cb); + return () => { + listeners.delete(uuid); + }; +} + +async function runExitHandler(handler: ExitHandler): Promise { + return timeout(Promise.resolve().then(handler), 30_000).catch((err) => { + logger.error("Exit handler failed to run", err); + }); +} + +export function initExitHandler() { + let signalCount = 0; + const exitHandler = async () => { + signalCount++; + if (signalCount === 1) { + logger.info("Received signal... Exiting"); + await Promise.all(Array.from(listeners.values()).map(runExitHandler)); + logger.info("All exit handlers ran... Waiting for svelte server to exit"); + } + if (signalCount === 3) { + logger.warn("Received 3 signals... Exiting immediately"); + process.exit(1); + } + }; + + process.on("SIGINT", exitHandler); + process.on("SIGTERM", exitHandler); +} diff --git a/src/lib/server/metrics.ts b/src/lib/server/metrics.ts index 29930e3d7b0..1f20c338f05 100644 --- a/src/lib/server/metrics.ts +++ b/src/lib/server/metrics.ts @@ -4,6 +4,8 @@ import { logger } from "$lib/server/logger"; import { env } from "$env/dynamic/private"; import type { Model } from "$lib/types/Model"; import type { Tool } from "$lib/types/Tool"; +import { onExit } from "./exitHandler"; +import { promisify } from "util"; interface Metrics { model: { @@ -37,11 +39,26 @@ export class MetricsServer { private constructor() { const app = express(); - const port = env.METRICS_PORT || "5565"; - const server = app.listen(port, () => { - logger.info(`Metrics server listening on port ${port}`); - }); + const port = Number(env.METRICS_PORT || "5565"); + if (isNaN(port) || port < 0 || port > 65535) { + logger.warn(`Invalid value for METRICS_PORT: ${env.METRICS_PORT}`); + } + + if (env.METRICS_ENABLED !== "false" && env.METRICS_ENABLED !== "true") { + logger.warn(`Invalid value for METRICS_ENABLED: ${env.METRICS_ENABLED}`); + } + if (env.METRICS_ENABLED === "true") { + const server = app.listen(port, () => { + logger.info(`Metrics server listening on port ${port}`); + }); + const closeServer = promisify(server.close); + onExit(async () => { + logger.info("Disconnecting metrics server ..."); + await closeServer(); + logger.info("Server stopped ..."); + }); + } const register = new Registry(); collectDefaultMetrics({ register }); @@ -160,14 +177,6 @@ export class MetricsServer { res.send(metrics); }); }); - - process.on("SIGINT", async () => { - logger.info("Sigint received, disconnect metrics server ..."); - server.close(() => { - logger.info("Server stopped ..."); - }); - process.exit(); - }); } public static getInstance(): MetricsServer { diff --git a/src/lib/server/websearch/scrape/playwright.ts b/src/lib/server/websearch/scrape/playwright.ts index 73f26b36672..63e414ccb26 100644 --- a/src/lib/server/websearch/scrape/playwright.ts +++ b/src/lib/server/websearch/scrape/playwright.ts @@ -9,6 +9,7 @@ import { import { PlaywrightBlocker } from "@cliqz/adblocker-playwright"; import { env } from "$env/dynamic/private"; import { logger } from "$lib/server/logger"; +import { onExit } from "$lib/server/exitHandler"; const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch).then((blker) => { const mostBlocked = blker.blockFonts().blockMedias().blockFrames().blockImages(); @@ -19,7 +20,7 @@ const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch).then(( let browserSingleton: Promise | undefined; async function getBrowser() { const browser = await chromium.launch({ headless: true }); - process.on("SIGINT", () => browser.close()); + onExit(() => browser.close()); browser.on("disconnected", () => { logger.warn("Browser closed"); browserSingleton = undefined; From 09b611cf3ff823e74654744f47f0a8f5de00649d Mon Sep 17 00:00:00 2001 From: Nathan Sarrazin Date: Mon, 1 Jul 2024 10:26:39 +0200 Subject: [PATCH 2/2] Make server off by default, enable in prods, update docs accordingly --- .env | 2 +- chart/env/prod.yaml | 1 + docs/source/configuration/metrics.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env b/.env index e67366c957e..93fc4af51bc 100644 --- a/.env +++ b/.env @@ -159,7 +159,7 @@ ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to USAGE_LIMITS=`{}` ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls -METRICS_ENABLED=true +METRICS_ENABLED=false METRICS_PORT=5565 LOG_LEVEL=info BODY_SIZE_LIMIT=15728640 diff --git a/chart/env/prod.yaml b/chart/env/prod.yaml index d5523dbc6d4..ec5676b025f 100644 --- a/chart/env/prod.yaml +++ b/chart/env/prod.yaml @@ -35,6 +35,7 @@ envVars: EXPOSE_API: "true" METRICS_PORT: 5565 LOG_LEVEL: "debug" + METRICS_ENABLED: "true" MODELS: > [ { diff --git a/docs/source/configuration/metrics.md b/docs/source/configuration/metrics.md index ddf45a6bd7b..45ad3e368ba 100644 --- a/docs/source/configuration/metrics.md +++ b/docs/source/configuration/metrics.md @@ -1,6 +1,6 @@ # Metrics -The server exposes prometheus metrics on port `5565` by default. You may disable the metrics server with `METRICS_ENABLED=false` and change the port with `METRICS_PORT=1234`. +The server can expose prometheus metrics on port `5565` but is off by default. You may enable the metrics server with `METRICS_ENABLED=true` and change the port with `METRICS_PORT=1234`.