search_text: cost of one query grows quadratically with the literal's length (Windows: a 400,000-character literal blocks the server for more than 30 s) #575
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#575
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while qualifying v0.41.50 (PR #574, Windows CI round 2). Present in v0.41.49 and earlier; not fixed in v0.41.50.
Observed
search_textwith a quoted literal of 400,000 characters did not answer within 30 s. The server process was alive and silent; every other call in the same session answered in about 1 ms.--no-daemon), literal ofarepeated n times:whole_word: falseCause (established by stack samples on Linux)
Every sample during the call is in SQLite's FTS5 query parser:
fts5ParseTokenizegrows the phrase bySZALLOC = 8terms per reallocation. A literal of n characters is n-2 trigram tokens, so n/8 reallocations of a block that grows to about 40 bytes per token (200 MB observed at 6.4 M characters). glibc resizes a large block withmremapand rarely copies; an allocator that copies on every growth (the Windows heap for large blocks) moves on the order of n²/16 × 40 bytes: about 400 GB for n = 400,000.Not established: the exact Windows duration (the CI harness gave up at 30 s), and whether other tools hand caller text to FTS5 the same way.
Why it matters
One request can keep a server busy for seconds (Linux) to minutes (Windows). No ordinary agent query is that long; a pasted file is.
Proposal
Bound the length of a
search_textliteral in the argument policy (#570 registry), so that the schema states it (maxLength), the refusal isinvalid_argument_valuewith the accepted length, anddocs/mcp-arguments.mdlists it. For scale: at 16 KiB the copying worst case is about 0.7 GB (well under a second); at 64 KiB about 11 GB.Consequences to handle in the same change:
whole_word_needle_unsupportedbecomes unreachable below about 31 KiB of literal (the regex engine's smallest compile limit, measured: 31,208 ×ι), so it moves to the unreachable kept codes inargument-legacy-acceptance.jsonif the bound is lower.search_symbolsand every other argument that reaches an FTS5MATCHfor the same growth.What v0.41.50 did instead
Only the test changed: the replayed case uses the shortest literal the regex engine refuses (40,000 ×
ι) instead of 400,000 ×a(commitb28850b0). The product path is unchanged.Released in v0.41.52 (
7fc7fb20, release run 8656, 15 assets). Verified on the installed Linux x86_64 binaries (code-index 0.41.52 (7fc7fb20)):search_textwith a query of 8,193 characters: refused,invalid_argument_value,received.characters: 8193,accepted.maxLength: 8192, exit status 1.entry: 1).maxLengthfor the query.