Skip to content

Commit 6134070

Browse files
committed
fix(token): ensure that running states are guarded by run tokens
1 parent 3d1762b commit 6134070

4 files changed

Lines changed: 109 additions & 15 deletions

File tree

lua/fuzzy/match.lua

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,8 @@ function Match:match(list, pattern, callback, transform)
335335
end
336336

337337
-- init core match context
338+
self._state.token = (self._state.token or 0) + 1
339+
local token = self._state.token
338340
self.list = assert(list)
339341
self.pattern = assert(pattern)
340342
self.callback = assert(callback)
@@ -390,9 +392,12 @@ function Match:match(list, pattern, callback, transform)
390392
self._state.timer = vim.loop.new_timer()
391393
self._state.timer:start(0,
392394
self._options.timer,
393-
vim.schedule_wrap(self:_bind_method(
394-
Match._match_worker
395-
))
395+
vim.schedule_wrap(function()
396+
if self._state.token ~= token then
397+
return
398+
end
399+
Match._match_worker(self)
400+
end)
396401
)
397402

398403
-- run one cycle immediately now

lua/fuzzy/stream.lua

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ function Stream:start(cmd, opts)
419419
end
420420
end
421421

422+
self._state.token = (self._state.token or 0) + 1
423+
local token = self._state.token
424+
422425
self.transform = opts.transform
423426
self.callback = assert(opts.callback)
424427

@@ -441,6 +444,9 @@ function Stream:start(cmd, opts)
441444
if type(cmd) == "function" then
442445
local did_finalize = false
443446
local callback = function(data)
447+
if self._state.token ~= token then
448+
return
449+
end
444450
if not did_finalize and data ~= nil then
445451
self:_handle_data(
446452
data,
@@ -460,9 +466,11 @@ function Stream:start(cmd, opts)
460466
)
461467
local code = not ok and 1 or 0
462468
if did_finalize == false then
463-
self._state.stdouteof = true
464-
self._state.stderreof = true
465-
stream:_handle_out(code, err, 3)
469+
if stream._state.token == token then
470+
self._state.stdouteof = true
471+
self._state.stderreof = true
472+
stream:_handle_out(code, err, 3)
473+
end
466474
did_finalize = true
467475
end
468476
end)
@@ -479,22 +487,28 @@ function Stream:start(cmd, opts)
479487
env = opts.env,
480488
stdio = stdio,
481489
hide = true,
482-
}, self:_bind_method(
483-
Stream._handle_exit)
484-
))
490+
}, function(...)
491+
if self._state.token == token then
492+
return Stream._handle_exit(self, ...)
493+
end
494+
end))
485495

486496
vim.loop.read_start(
487497
self._state.stdout,
488-
self:_bind_method(
489-
Stream._handle_stdout
490-
)
498+
function(...)
499+
if self._state.token == token then
500+
return Stream._handle_stdout(self, ...)
501+
end
502+
end
491503
)
492504

493505
vim.loop.read_start(
494506
self._state.stderr,
495-
self:_bind_method(
496-
Stream._handle_stderr
497-
)
507+
function(...)
508+
if self._state.token == token then
509+
return Stream._handle_stderr(self, ...)
510+
end
511+
end
498512
)
499513
end
500514

tests/match_spec.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,35 @@ local function run_wait_timeout_case()
227227
helpers.assert_ok(saw_results, "wait timeout saw results")
228228
end
229229

230+
local function run_restart_token_case()
231+
local match = new_match({ step = 1, timer = 50 })
232+
local list = {}
233+
for i = 1, 5000 do
234+
list[i] = "item-" .. i
235+
end
236+
list[100] = "alpha"
237+
list[200] = "beta"
238+
239+
local late_calls = 0
240+
local blocked = false
241+
242+
match:match(list, "alpha", function(results)
243+
if blocked and results ~= nil then
244+
late_calls = late_calls + 1
245+
end
246+
end)
247+
248+
helpers.assert_ok(helpers.wait_for(function()
249+
return match:running()
250+
end, 500), "restart token running")
251+
252+
blocked = true
253+
match:match(list, "beta", function() end)
254+
match:wait(1500)
255+
256+
helpers.eq(late_calls, 0, "restart token stale callback")
257+
end
258+
230259
local function run_restart_ephemeral_true_case()
231260
local match = new_match({ step = 1, timer = 2, ephemeral = true })
232261
local first = {}
@@ -345,6 +374,7 @@ function M.run()
345374
helpers.run_test_case("match_multi_chunk", run_multi_chunk_case)
346375
helpers.run_test_case("match_transform_function", run_transform_function_case)
347376
helpers.run_test_case("match_wait_timeout", run_wait_timeout_case)
377+
helpers.run_test_case("match_restart_token", run_restart_token_case)
348378
helpers.run_test_case("match_restart_ephemeral_true", run_restart_ephemeral_true_case)
349379
helpers.run_test_case("match_restart_ephemeral_false", run_restart_ephemeral_false_case)
350380
helpers.run_test_case("match_tail_chunk", run_tail_chunk_case)

tests/stream_spec.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,50 @@ local function run_restart_case()
166166
stream:destroy()
167167
end
168168

169+
local function run_restart_token_case()
170+
local Async = require("fuzzy.async")
171+
local stream = Stream.new({
172+
lines = true,
173+
step = 2,
174+
ephemeral = true,
175+
})
176+
local state = { allow_old_finish = false }
177+
178+
stream:start(function(cb)
179+
cb("old-1")
180+
while not state.allow_old_finish do
181+
Async.yield()
182+
end
183+
cb("old-2")
184+
cb(nil)
185+
end, {
186+
callback = function() end,
187+
})
188+
189+
helpers.assert_ok(helpers.wait_for(function()
190+
return stream:running()
191+
end, 500), "restart token running")
192+
193+
stream:start(function(cb)
194+
cb("new-1")
195+
cb("new-2")
196+
cb(nil)
197+
end, {
198+
callback = function() end,
199+
})
200+
201+
state.allow_old_finish = true
202+
helpers.assert_ok(helpers.wait_for(function()
203+
return not stream:running()
204+
end, 1500), "restart token done")
205+
206+
local results = stream.results or {}
207+
helpers.eq(#results, 2, "restart token count")
208+
helpers.eq(results[1], "new-1", "restart token first")
209+
helpers.eq(results[2], "new-2", "restart token second")
210+
stream:destroy()
211+
end
212+
169213
local function run_context_case()
170214
local stream = Stream.new({ lines = true, step = 1 })
171215
helpers.assert_ok(vim.fn.executable("awk") == 1, "awk missing")
@@ -480,6 +524,7 @@ function M.run()
480524
helpers.run_test_case("stream_bytes", run_bytes_case)
481525
helpers.run_test_case("stream_trim_results_case", run_trim_results_case)
482526
helpers.run_test_case("stream_restart", run_restart_case)
527+
helpers.run_test_case("stream_restart_token", run_restart_token_case)
483528
helpers.run_test_case("stream_context", run_context_case)
484529
helpers.run_test_case("stream_partial_line", run_partial_line_case)
485530
helpers.run_test_case("stream_trailing_partial", run_trailing_partial_case)

0 commit comments

Comments
 (0)