fix: wait() checks condition twice on each interval (#37837)

Problem:  wait() checks condition twice on each interval.
Solution: Don't schedule the due callback. Also fix memory leak when
          Nvim exits while waiting.

No test that the condition isn't checked twice, as testing for that can
be flaky when there are libuv events from other sources.
This commit is contained in:
zeertzjq
2026-02-13 21:02:40 +08:00
committed by GitHub
parent a7a7cdbcda
commit 9c5ade9212
4 changed files with 41 additions and 10 deletions

View File

@@ -2567,6 +2567,12 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Dummy timer callback. Used by f_wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
{
// If the main loop is closing, the condition won't be checked again.
// Close the timer to avoid leaking resources.
if (main_loop.closing) {
time_watcher_stop(tw);
time_watcher_close(tw, dummy_timer_close_cb);
}
}
/// Dummy timer close callback. Used by f_wait().
@@ -2600,8 +2606,9 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Start dummy timer.
time_watcher_init(&main_loop, tw, NULL);
tw->events = main_loop.events;
tw->blockable = true;
// Don't schedule the due callback, as that'll lead to two different types of events
// on each interval, causing the condition to be checked twice.
tw->events = NULL;
time_watcher_start(tw, dummy_timer_due_cb, (uint64_t)interval, (uint64_t)interval);
typval_T argv = TV_INITIAL_VALUE;

View File

@@ -423,12 +423,18 @@ static int nlua_schedule(lua_State *const lstate)
return 2;
}
// Dummy timer callback. Used by f_wait().
// Dummy timer callback. Used by vim.wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
{
// If the main loop is closing, the condition won't be checked again.
// Close the timer to avoid leaking resources.
if (main_loop.closing) {
time_watcher_stop(tw);
time_watcher_close(tw, dummy_timer_close_cb);
}
}
// Dummy timer close callback. Used by f_wait().
// Dummy timer close callback. Used by vim.wait().
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
{
xfree(tw);
@@ -512,12 +518,10 @@ static int nlua_wait(lua_State *lstate)
// Start dummy timer.
time_watcher_init(&main_loop, tw, NULL);
tw->events = loop_events;
tw->blockable = true;
time_watcher_start(tw,
dummy_timer_due_cb,
(uint64_t)interval,
(uint64_t)interval);
// Don't schedule the due callback, as that'll lead to two different types of events
// on each interval, causing the condition to be checked twice.
tw->events = NULL;
time_watcher_start(tw, dummy_timer_due_cb, (uint64_t)interval, (uint64_t)interval);
int pcall_status = 0;
bool callback_result = false;

View File

@@ -2379,6 +2379,15 @@ stack traceback:
)
end)
it('does not leak when Nvim exits while waiting', function()
n.expect_exit(500, exec_lua, function()
vim.defer_fn(function()
vim.cmd('qall!')
end, 10)
vim.wait(10000)
end)
end)
it('plays nice with `not` when fails', function()
eq(
true,

View File

@@ -78,4 +78,15 @@ describe('wait()', function()
eq('Vim:E475: Invalid value for argument 3', pcall_err(call, 'wait', 0, 1, 0))
eq('Vim:E475: Invalid value for argument 3', pcall_err(call, 'wait', 0, 1, ''))
end)
it('does not leak when Nvim exits while waiting', function()
n.expect_exit(
500,
source,
[[
call timer_start(10, {-> execute('qall!')})
call wait(10000, 0)
]]
)
end)
end)