Skip to content

Commit 900a9b3

Browse files
Saghennsarrazin
andauthored
feat: allow disabling metrics server, consolidate exit handlers (#1201)
* feat: allow disabling metrics server, consolidate exit handlers * Make server off by default, enable in prods, update docs accordingly --------- Co-authored-by: Nathan Sarrazin <[email protected]>
1 parent f3f6a8b commit 900a9b3

File tree

10 files changed

+86
-27
lines changed

10 files changed

+86
-27
lines changed

.env

+2-1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to
159159
USAGE_LIMITS=`{}`
160160

161161
ALLOW_INSECURE_COOKIES=false # recommended to keep this to false but set to true if you need to run over http without tls
162-
METRICS_PORT=
162+
METRICS_ENABLED=false
163+
METRICS_PORT=5565
163164
LOG_LEVEL=info
164165
BODY_SIZE_LIMIT=15728640

chart/env/prod.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ envVars:
3535
EXPOSE_API: "true"
3636
METRICS_PORT: 5565
3737
LOG_LEVEL: "debug"
38+
METRICS_ENABLED: "true"
3839
MODELS: >
3940
[
4041
{

docs/source/_toctree.yml

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
title: OpenID
2121
- local: configuration/web-search
2222
title: Web Search
23+
- local: configuration/metrics
24+
title: Metrics
2325
- local: configuration/embeddings
2426
title: Text Embedding Models
2527
- title: Models

docs/source/configuration/metrics.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Metrics
2+
3+
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`.
4+
5+
<Tip>
6+
7+
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.
8+
9+
</Tip>

src/hooks.server.ts

+3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ import { refreshAssistantsCounts } from "$lib/assistantStats/refresh-assistants-
1313
import { logger } from "$lib/server/logger";
1414
import { AbortedGenerations } from "$lib/server/abortedGenerations";
1515
import { MetricsServer } from "$lib/server/metrics";
16+
import { initExitHandler } from "$lib/server/exitHandler";
1617
import { ObjectId } from "mongodb";
1718

1819
// TODO: move this code on a started server hook, instead of using a "building" flag
1920
if (!building) {
21+
initExitHandler();
22+
2023
await checkAndRunMigrations();
2124
if (env.ENABLE_ASSISTANTS) {
2225
refreshAssistantsCounts();

src/lib/server/abortedGenerations.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { logger } from "$lib/server/logger";
44
import { collections } from "$lib/server/database";
5+
import { onExit } from "./exitHandler";
56

67
export class AbortedGenerations {
78
private static instance: AbortedGenerations;
@@ -10,10 +11,7 @@ export class AbortedGenerations {
1011

1112
private constructor() {
1213
const interval = setInterval(this.updateList, 1000);
13-
14-
process.on("SIGINT", () => {
15-
clearInterval(interval);
16-
});
14+
onExit(() => clearInterval(interval));
1715
}
1816

1917
public static getInstance(): AbortedGenerations {

src/lib/server/database.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type { Semaphore } from "$lib/types/Semaphore";
1515
import type { AssistantStats } from "$lib/types/AssistantStats";
1616
import { logger } from "$lib/server/logger";
1717
import { building } from "$app/environment";
18+
import { onExit } from "./exitHandler";
1819

1920
export const CONVERSATION_STATS_COLLECTION = "conversations.stats";
2021

@@ -41,15 +42,8 @@ export class Database {
4142
this.client.db(env.MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : ""));
4243
this.client.on("open", () => this.initDatabase());
4344

44-
// Disconnect DB on process kill
45-
process.on("SIGINT", async () => {
46-
await this.client.close(true);
47-
48-
// https://github.com/sveltejs/kit/issues/9540
49-
setTimeout(() => {
50-
process.exit(0);
51-
}, 100);
52-
});
45+
// Disconnect DB on exit
46+
onExit(() => this.client.close(true));
5347
}
5448

5549
public static getInstance(): Database {

src/lib/server/exitHandler.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { randomUUID } from "$lib/utils/randomUuid";
2+
import { timeout } from "$lib/utils/timeout";
3+
import { logger } from "./logger";
4+
5+
type ExitHandler = () => void | Promise<void>;
6+
type ExitHandlerUnsubscribe = () => void;
7+
8+
const listeners = new Map<string, ExitHandler>();
9+
10+
export function onExit(cb: ExitHandler): ExitHandlerUnsubscribe {
11+
const uuid = randomUUID();
12+
listeners.set(uuid, cb);
13+
return () => {
14+
listeners.delete(uuid);
15+
};
16+
}
17+
18+
async function runExitHandler(handler: ExitHandler): Promise<void> {
19+
return timeout(Promise.resolve().then(handler), 30_000).catch((err) => {
20+
logger.error("Exit handler failed to run", err);
21+
});
22+
}
23+
24+
export function initExitHandler() {
25+
let signalCount = 0;
26+
const exitHandler = async () => {
27+
signalCount++;
28+
if (signalCount === 1) {
29+
logger.info("Received signal... Exiting");
30+
await Promise.all(Array.from(listeners.values()).map(runExitHandler));
31+
logger.info("All exit handlers ran... Waiting for svelte server to exit");
32+
}
33+
if (signalCount === 3) {
34+
logger.warn("Received 3 signals... Exiting immediately");
35+
process.exit(1);
36+
}
37+
};
38+
39+
process.on("SIGINT", exitHandler);
40+
process.on("SIGTERM", exitHandler);
41+
}

src/lib/server/metrics.ts

+21-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { logger } from "$lib/server/logger";
44
import { env } from "$env/dynamic/private";
55
import type { Model } from "$lib/types/Model";
66
import type { Tool } from "$lib/types/Tool";
7+
import { onExit } from "./exitHandler";
8+
import { promisify } from "util";
79

810
interface Metrics {
911
model: {
@@ -37,11 +39,26 @@ export class MetricsServer {
3739

3840
private constructor() {
3941
const app = express();
40-
const port = env.METRICS_PORT || "5565";
4142

42-
const server = app.listen(port, () => {
43-
logger.info(`Metrics server listening on port ${port}`);
44-
});
43+
const port = Number(env.METRICS_PORT || "5565");
44+
if (isNaN(port) || port < 0 || port > 65535) {
45+
logger.warn(`Invalid value for METRICS_PORT: ${env.METRICS_PORT}`);
46+
}
47+
48+
if (env.METRICS_ENABLED !== "false" && env.METRICS_ENABLED !== "true") {
49+
logger.warn(`Invalid value for METRICS_ENABLED: ${env.METRICS_ENABLED}`);
50+
}
51+
if (env.METRICS_ENABLED === "true") {
52+
const server = app.listen(port, () => {
53+
logger.info(`Metrics server listening on port ${port}`);
54+
});
55+
const closeServer = promisify(server.close);
56+
onExit(async () => {
57+
logger.info("Disconnecting metrics server ...");
58+
await closeServer();
59+
logger.info("Server stopped ...");
60+
});
61+
}
4562

4663
const register = new Registry();
4764
collectDefaultMetrics({ register });
@@ -160,14 +177,6 @@ export class MetricsServer {
160177
res.send(metrics);
161178
});
162179
});
163-
164-
process.on("SIGINT", async () => {
165-
logger.info("Sigint received, disconnect metrics server ...");
166-
server.close(() => {
167-
logger.info("Server stopped ...");
168-
});
169-
process.exit();
170-
});
171180
}
172181

173182
public static getInstance(): MetricsServer {

src/lib/server/websearch/scrape/playwright.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { PlaywrightBlocker } from "@cliqz/adblocker-playwright";
1010
import { env } from "$env/dynamic/private";
1111
import { logger } from "$lib/server/logger";
12+
import { onExit } from "$lib/server/exitHandler";
1213

1314
const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch)
1415
.then((blker) => {
@@ -24,7 +25,7 @@ const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch)
2425
let browserSingleton: Promise<Browser> | undefined;
2526
async function getBrowser() {
2627
const browser = await chromium.launch({ headless: true });
27-
process.on("SIGINT", () => browser.close());
28+
onExit(() => browser.close());
2829
browser.on("disconnected", () => {
2930
logger.warn("Browser closed");
3031
browserSingleton = undefined;

0 commit comments

Comments
 (0)