From 50f1e9e009e44dff10373efd5fef26ac9f148cd3 Mon Sep 17 00:00:00 2001 From: mi <123757457+soyelmismo@users.noreply.github.com> Date: Fri, 22 May 2026 05:59:21 -0500 Subject: [PATCH] fix: cache compiled RegExp in RTK compression hot path (#2553) Integrated into release/v3.8.2 --- .../compression/engines/rtk/filterLoader.ts | 29 +++++++++---- .../compression/engines/rtk/lineFilter.ts | 41 ++++++++++++------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/open-sse/services/compression/engines/rtk/filterLoader.ts b/open-sse/services/compression/engines/rtk/filterLoader.ts index 8c776a9b85..d8879a78cd 100644 --- a/open-sse/services/compression/engines/rtk/filterLoader.ts +++ b/open-sse/services/compression/engines/rtk/filterLoader.ts @@ -10,6 +10,24 @@ let cache: RtkFilterDefinition[] | null = null; let cacheKey: string | null = null; let diagnostics: RtkFilterLoadDiagnostic[] = []; +// RegExp cache for matchPatterns — same pattern strings are tested against +// many different lines across requests; avoid compiling them every time. +const regexCache = new Map(); + +function cachedMatchPattern(pattern: string, value: string): boolean { + const key = `${pattern}::im`; + let re = regexCache.get(key); + if (!re) { + try { + re = new RegExp(pattern, "im"); + regexCache.set(key, re); + } catch { + return false; + } + } + return re.test(value); +} + export interface RtkFilterLoadDiagnostic { source: "project" | "global" | "builtin"; path?: string; @@ -196,23 +214,16 @@ export function matchRtkFilter( ): RtkFilterDefinition | null { const detection = detectCommandType(text, command); const detectedCommand = detection.command ?? command ?? ""; - const matchesPattern = (pattern: string, value: string): boolean => { - try { - return new RegExp(pattern, "im").test(value); - } catch { - return false; - } - }; const filters = loadRtkFilters(options); return ( filters.find((filter) => filter.commandTypes.includes(detection.type)) ?? filters.find( (filter) => detectedCommand && - filter.commandPatterns.some((pattern) => matchesPattern(pattern, detectedCommand)) + filter.commandPatterns.some((pattern) => cachedMatchPattern(pattern, detectedCommand)) ) ?? filters.find((filter) => - filter.matchPatterns.some((pattern) => matchesPattern(pattern, text)) + filter.matchPatterns.some((pattern) => cachedMatchPattern(pattern, text)) ) ?? filters.find((filter) => filter.commandTypes.includes("generic-output")) ?? null diff --git a/open-sse/services/compression/engines/rtk/lineFilter.ts b/open-sse/services/compression/engines/rtk/lineFilter.ts index 589b618086..5be2b1a93f 100644 --- a/open-sse/services/compression/engines/rtk/lineFilter.ts +++ b/open-sse/services/compression/engines/rtk/lineFilter.ts @@ -8,30 +8,41 @@ export interface LineFilterResult { appliedRules: string[]; } +// ──────────────── RegExp cache ──────────────── +// +// Patterns are static (loaded from filter JSON files on boot) but were being +// compiled via `new RegExp(...)` on every call to applyLineFilter(). For a +// busy proxy this means thousands of redundant RegExp instantiations per +// second. Cache them here once. + +const regexCache = new Map(); + +function cachedRegExp(pattern: string, flags: string): RegExp | null { + const key = `${pattern}::${flags}`; + const cached = regexCache.get(key); + if (cached) return cached; + try { + const re = new RegExp(pattern, flags); + regexCache.set(key, re); + return re; + } catch { + return null; + } +} + function compilePatterns(patterns: string[]): RegExp[] { return patterns.flatMap((pattern) => { - try { - return [new RegExp(pattern, "i")]; - } catch { - return []; - } + const re = cachedRegExp(pattern, "i"); + return re ? [re] : []; }); } function compileGlobalPattern(pattern: string): RegExp | null { - try { - return new RegExp(pattern, "g"); - } catch { - return null; - } + return cachedRegExp(pattern, "g"); } function compileBlobPattern(pattern: string): RegExp | null { - try { - return new RegExp(pattern, "im"); - } catch { - return null; - } + return cachedRegExp(pattern, "im"); } function stripAnsi(text: string): string {