Skip to content

fix: infinite hang on input chars sorting past the last full-dict word (#669) - #670

Merged
ddehilster merged 2 commits into
masterfrom
fix/full-dict-binary-search-hang
Jun 17, 2026
Merged

ddehilster merged 2 commits into
masterfrom
fix/full-dict-binary-search-hang

Conversation

@ddehilster

Copy link
Copy Markdown
Member

Closes #669.

Symptom

nlp.exe hangs forever (you observed 11+ hours) when input contains an em-dash — (U+2014) or en-dash – (U+2013). Only ana001.tree is written, then it spins. ASCII hyphen is fine. Reproduces with the parse-pt-br analyzer (Portuguese dialogue uses travessões).

Root cause — the lazy full-dict binary search (#649)

The hang is in findWordConcept → findFullWord → searchDictFile/searchKBBFile, the binary search over a sorted *full.dict / *full.kbb (parse-pt-br ships a 17 MB pt-full.dict).

nextDictKey/nextKBBKey set afterKey = f.stream.tellg(). Reading the file's last line (no trailing newline) leaves the stream at EOF, where tellg() returns −1. An em-dash (bytes e2 80 94) sorts after every word, so the lower-bound search walks to that last line, gets afterKey = -1, and does lo = -1 — lo < hi stays true and the search restarts from the middle forever:

SDF lo=17743888 hi=17743915 mid=17743901 key='úvulas' cmp=-1 afterKey=-1   ← last word, tellg()=-1
SDF lo=-1       hi=17743915 mid=8871957  key='esborregamo' ...              ← lo=-1 → restarts, forever

Normal words sort within the file and converge before reaching the last line, so only past-the-end code points (em/en-dash, etc.) trigger it. This is new because it rides on the lazy-full-dict feature (#649), shipped in 3.5.x.

Fix

  • nextDictKey / nextKBBKey: clamp afterKey to f.size when tellg() returns negative — the true "position past the last key" — so the search terminates and reports the word absent.
  • searchDictFile / searchKBBFile: defensive if (afterKey <= lo) break; in the key<str branch (never move lo backwards/sideways).

Verification (parse-pt-br, which ships pt-full.dict)

input before after
— Bom dia. (em-dash) hang ✅ completes
– Bom dia. (en-dash) hang ✅ completes
full travessão dialogue (3 em-dashes) hang ✅ completes
control / parse-en-us ok ✅ ok

In-dict words still resolve (Bom → ("s" 1)("adj" 1)("meaning" concept:"bom")), so normal dictionary lookup is unaffected. Bumps engine to 3.5.7.

Found by instrumenting the tokenizer to localize the loop to findWordConcept, then logging the binary-search bounds — the afterKey=-1 → lo=-1 restart was conclusive.

🤖 Generated with Claude Code

ddehilster and others added 2 commits June 17, 2026 13:24
…ord (#669)

nlp.exe hung forever (observed 11+ hours) when input contained an em-dash
(U+2014) or en-dash (U+2013) -- and, generally, any character that sorts after
every word in a lazily-loaded "*full" dictionary.

Root cause: the lazy full-dict/full-kbb binary search (findFullWord, added in
#649). nextDictKey/nextKBBKey set `afterKey = f.stream.tellg()`. Reading the
file's last line (no trailing newline) leaves the stream at EOF, where tellg()
returns -1. For a search key greater than every word, the lower-bound search
walks to that last line; `afterKey = -1` then makes searchDictFile/searchKBBFile
do `lo = -1`, so `lo < hi` stays true and the search restarts from the middle
forever. Normal words sort within the file and converge before hitting this, so
only past-the-end keys (em/en-dash, other high code points) trigger it.

Fix:
- nextDictKey / nextKBBKey: clamp `afterKey` to `f.size` when tellg() returns
  negative (EOF after the last line). That is the true "position past the last
  key", so the search terminates cleanly and reports the word absent.
- searchDictFile / searchKBBFile: defensive guard -- if `afterKey <= lo` in the
  key<str branch, break instead of moving lo backwards/sideways (belt and
  suspenders against any future non-progress).

Verified with the parse-pt-br analyzer (which ships pt-full.dict): em-dash,
en-dash, and a full Portuguese travessao dialogue all previously hung and now
complete; in-dict words still get their POS/meaning attributes; parse-en-us
unaffected. Bumps engine to 3.5.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a self-contained fixture analyzer that reproduces the infinite hang: a tiny
sorted "zz-full.dict" (last line intentionally has no trailing newline, so the
final getline hits EOF and tellg() returns -1) plus an em-dash input (which
sorts after every dict word, driving the lower-bound search to that last line).

Validated against the pre-fix engine: the fixture hangs (timeout) without the
fix and completes with it. Wired into the Windows and Linux builds; a hang trips
the step timeout and fails the job. A .gitattributes marks the dict binary so
git never appends a newline or normalizes EOLs (which would mask the bug).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ddehilster
ddehilster merged commit 31a70ff into master Jun 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Engine hangs indefinitely on Unicode dash characters (em-dash U+2014 / en-dash U+2013) in input

1 participant