diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9e5a3631fb..4ddea931a1 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b76244cc2d..93a4fa574a 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -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; diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index e31c215817..049398fc74 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -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, diff --git a/test/functional/vimscript/wait_spec.lua b/test/functional/vimscript/wait_spec.lua index 0932bd3593..3ac7399c52 100644 --- a/test/functional/vimscript/wait_spec.lua +++ b/test/functional/vimscript/wait_spec.lua @@ -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)