mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
perf: Check socket is still connected before querying db (#11620)
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import type { Next } from "koa";
|
||||
import type { AppContext } from "@server/types";
|
||||
import { requestContext } from "@server/storage/requestContext";
|
||||
|
||||
/**
|
||||
* Middleware that wraps the request in an AsyncLocalStorage context, making the
|
||||
* current request available to Sequelize hooks so that queries can be
|
||||
* short-circuited when the socket has been destroyed (e.g. after a timeout).
|
||||
*
|
||||
* @returns The middleware function.
|
||||
*/
|
||||
export default function requestContextMiddleware() {
|
||||
return (ctx: AppContext, next: Next) =>
|
||||
requestContext.run({ req: ctx.req }, next);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import env from "@server/env";
|
||||
import { NotFoundError } from "@server/errors";
|
||||
import { apiContext } from "@server/middlewares/apiContext";
|
||||
import coalesceBody from "@server/middlewares/coaleseBody";
|
||||
import requestContextMiddleware from "@server/middlewares/requestContext";
|
||||
import requestTracer from "@server/middlewares/requestTracer";
|
||||
import { verifyCSRFToken } from "@server/middlewares/csrf";
|
||||
import type { AppState, AppContext } from "@server/types";
|
||||
@@ -55,6 +56,7 @@ const api = new Koa<AppState, AppContext>();
|
||||
const router = new Router();
|
||||
|
||||
// middlewares
|
||||
api.use(requestContextMiddleware());
|
||||
api.use(
|
||||
bodyParser({
|
||||
multipart: true,
|
||||
|
||||
@@ -6,9 +6,11 @@ import { Sequelize } from "sequelize-typescript";
|
||||
import type { MigrationError } from "umzug";
|
||||
import { Umzug, SequelizeStorage } from "umzug";
|
||||
import env from "@server/env";
|
||||
import { ClientClosedRequestError } from "@server/errors";
|
||||
import type Model from "@server/models/base/Model";
|
||||
import Logger from "../logging/Logger";
|
||||
import * as models from "../models";
|
||||
import { requestContext } from "./requestContext";
|
||||
import { getConnectionName } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -107,6 +109,18 @@ export function createDatabaseInstance(
|
||||
instance = monkeyPatchSequelizeErrorsForJest(instance);
|
||||
}
|
||||
|
||||
// Skip queries when the originating HTTP request socket has been destroyed
|
||||
// (e.g. client disconnected or server timeout). This avoids wasting database
|
||||
// resources on work whose response can never be delivered.
|
||||
const assertConnectionOpen = () => {
|
||||
const store = requestContext.getStore();
|
||||
if (store?.req.socket.destroyed) {
|
||||
throw ClientClosedRequestError();
|
||||
}
|
||||
};
|
||||
instance.addHook("beforeFind", assertConnectionOpen);
|
||||
instance.addHook("beforeCount", assertConnectionOpen);
|
||||
|
||||
// Add hooks to warn about write operations on read-only connections
|
||||
if (isReadOnly) {
|
||||
const warnWriteOperation = (operation: string) => {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import type { IncomingMessage } from "node:http";
|
||||
|
||||
/**
|
||||
* Async local storage for the current HTTP request context. This allows
|
||||
* downstream code (e.g. Sequelize hooks) to check whether the originating
|
||||
* request is still alive without explicitly threading `ctx` through every call.
|
||||
*/
|
||||
export const requestContext = new AsyncLocalStorage<{
|
||||
req: IncomingMessage;
|
||||
}>();
|
||||
Reference in New Issue
Block a user