diff --git a/app/services/corpus.ts b/app/services/corpus.ts index ca8ca62..80212ae 100644 --- a/app/services/corpus.ts +++ b/app/services/corpus.ts @@ -9,6 +9,8 @@ import type { ChunkSpan } from '#services/spool' const inflate = promisify(unzip) const deflate = promisify(deflateCb) const gzip = promisify(gzipCb) +/** The largest inflated chunk the filter reads as one string (V8's limit is 0x1fffffe8 characters). */ +const MAX_FILTER_BYTES = 256 * 1024 * 1024 /** What a filtered chunk was filtered with (CORPUS_FILTER): the rule set and the group priorities of its lease. */ export interface CorpusFilter { @@ -91,7 +93,9 @@ class Corpus { const file = this.path(range, span) await mkdir(dir, { recursive: true }) let stored = data - if (filter) { + // Past V8's longest string (0x1fffffe8) the text can't be filtered; such a chunk is stored as it came (the + // worker drops the same lines itself). Seen once: a chunk inflating past 512 MB (LOG.md 2026-10-24 (102)). + if (filter && raw.length < MAX_FILTER_BYTES) { // Latin-1 both ways: every byte back as it was. const { kept, drops } = filterOverviews(raw.toString('latin1'), range.group, filter.priorities, filter.ruleVersion) stored = await deflate(Buffer.from(kept, 'latin1'), { level: 9 }) diff --git a/lib/edge/scan.ts b/lib/edge/scan.ts index 006ace8..cc8c0c3 100644 --- a/lib/edge/scan.ts +++ b/lib/edge/scan.ts @@ -47,6 +47,8 @@ interface Counter { function findCounter(subject: string, after: number): Counter | null { let open = subject.length while (true) { + // lastIndexOf('(', -1) searches from 0 again: a "(" at the start that isn't a counter looped forever. + if (open === 0) return null open = subject.lastIndexOf('(', open - 1) if (open <= after) return null let i = open + 1 diff --git a/tests/unit/edge/scan.spec.ts b/tests/unit/edge/scan.spec.ts index 5e78d6a..7e5da49 100644 --- a/tests/unit/edge/scan.spec.ts +++ b/tests/unit/edge/scan.spec.ts @@ -37,5 +37,8 @@ test.group('edge / scanOverviewText and SegmentScanner', () => { // Obfuscated, or a counter only before the name: no segment. assert.isNull(scanner.segment(9, 'Xq8r2KdLm3Zw91PqA7zz', 'p', '', '', '1', xref, 'alt.binaries.test')) assert.isNull(scanner.segment(9, '(1/2) "x.bin" yEnc', 'p', '', '', '1', xref, 'alt.binaries.test')) + // A "(" at the start that isn't a counter, no quote: no segment (it looped forever, LOG.md 2026-10-24 (102)). + assert.isNull(scanner.segment(9, '(Some Show) S01E01 x264', 'p', '', '', '1', xref, 'alt.binaries.test')) + assert.equal(scanner.segment(9, '(Some Show) S01E01.mkv (3/9)', 'p', '', '', '1', xref, 'alt.binaries.test')?.number, 3) }) })