Commit fea0681
fix(edit_block): run fuzzy search in a worker thread to keep event lo… (#500)
* fix(edit_block): run fuzzy search in a worker thread to keep event loop responsive
When edit_block found no exact match, the fuzzy-search fallback ran
recursiveFuzzyIndexOf() synchronously on the main thread. On large files
this pinned the event loop for seconds, and several parallel edit_block
calls serialized their scans back-to-back, freezing pings and all other
tool calls — the server appeared hung.
The scan now runs in a worker thread via runFuzzySearchInWorker():
- inline eval worker that imports this module, so no separate worker
file ships with the build
- 30s timeout terminates runaway scans (errors surface via the existing
handleEditBlock catch)
- worker and timer are unref'd so a running scan never delays shutdown
Measured: 4 parallel 2MB scans drop from 16.0s (serialized) to 4.8s
(concurrent), with max ping latency 1-14ms during scans vs ~3.5s before.
Adds a regression scenario to the edit-block performance integration
test that pings every 200ms during a deliberately slow scan and fails
if max latency exceeds 500ms (the existing 5s responsiveness probe was
too loose to catch this).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: terminate fuzzy search worker on success
Without an explicit terminate the worker lingers until its module graph
drains (the in-worker telemetry HTTPS call alone holds it ~3s), keeping a
structured-clone copy of the full file content in memory per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract fuzzy search into a dependency-free core module
The worker previously imported fuzzySearch.ts, which pulls in capture.js
and through it server.ts — booting the entire app module graph per fuzzy
search. That cost ~275ms per call on small files and fired the search
telemetry from inside the worker, where the client identity is never
initialized.
fuzzySearchCore.ts now holds the pure search functions with
fastest-levenshtein as its only import; the worker loads just that and
returns timing metrics as data, which the main thread captures with the
real client id (same event names and payloads as before). Worker
round-trip drops to ~18ms. The worker snippet also gained a .catch so
import/search failures surface as a structured error instead of an
opaque worker crash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: handle rejection on the detached editPromise.finally chain
The .finally() call created a derived promise with no rejection handler;
if edit_block failed it raised unhandledRejection even though editPromise
itself is awaited later. Swallow rejections on the detached chain only —
errors still surface via the awaited editPromise.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: seed iterativeReduction with the measured distance of its slice
In all recursive paths parentDistance is exact — the parent passes the
distance it measured on precisely the child's slice, so the original
branch-and-bound seeding was sound there. The one broken path is a
top-level call where the whole text fits the small-segment branch
(text <= 2x query length): parentDistance defaults to Infinity, the
first shrink check always passes, and a match at position 0 can never
be returned at position 0 — e.g. matching 'the quick brown fox' in
'the quick brwn fox jumps' returned start 1 / distance 2 instead of
start 0 / distance 1.
Measuring the slice distance on entry fixes that case and is a no-op
for recursive calls (the computed seed equals the value the parent
passed; scan timings are unchanged). Costs one extra distance() call
per search, the same size as a single shrink-loop iteration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 8f425e2 commit fea0681
4 files changed
Lines changed: 328 additions & 134 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
254 | | - | |
255 | | - | |
256 | | - | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
257 | 258 | | |
258 | 259 | | |
259 | 260 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | 1 | | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
3 | 10 | | |
4 | 11 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
12 | 17 | | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
64 | 30 | | |
65 | 31 | | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
73 | 37 | | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
118 | 79 | | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | 80 | | |
127 | 81 | | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
0 commit comments