fix: cache compiled RegExp in RTK compression hot path (#2553)

Integrated into release/v3.8.2
This commit is contained in:
mi
2026-05-22 05:59:21 -05:00
committed by GitHub
parent 0f36e77ae0
commit 50f1e9e009
2 changed files with 46 additions and 24 deletions

View File

@@ -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<string, RegExp>();
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

View File

@@ -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<string, RegExp>();
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 {