mirror of
https://github.com/neovim/neovim.git
synced 2026-04-25 00:35:36 +00:00
fix(lua): close vim.defer_fn() timer if vim.schedule() failed (#37647)
Problem: Using vim.defer_fn() just before Nvim exit leaks luv handles. Solution: Make vim.schedule() return an error message if scheduling failed. Make vim.defer_fn() close timer if vim.schedule() failed.
This commit is contained in:
@@ -722,6 +722,10 @@ vim.schedule({fn}) *vim.schedule()*
|
|||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {fn} (`fun()`)
|
• {fn} (`fun()`)
|
||||||
|
|
||||||
|
Return (multiple): ~
|
||||||
|
(`nil`) result
|
||||||
|
(`string?`) err Error message if scheduling failed, `nil` otherwise.
|
||||||
|
|
||||||
vim.str_utf_end({str}, {index}) *vim.str_utf_end()*
|
vim.str_utf_end({str}, {index}) *vim.str_utf_end()*
|
||||||
Gets the distance (in bytes) from the last byte of the codepoint
|
Gets the distance (in bytes) from the last byte of the codepoint
|
||||||
(character) that {index} points to.
|
(character) that {index} points to.
|
||||||
@@ -1272,7 +1276,7 @@ vim.defer_fn({fn}, {timeout}) *vim.defer_fn()*
|
|||||||
Defers calling {fn} until {timeout} ms passes.
|
Defers calling {fn} until {timeout} ms passes.
|
||||||
|
|
||||||
Use to do a one-shot timer that calls {fn} Note: The {fn} is
|
Use to do a one-shot timer that calls {fn} Note: The {fn} is
|
||||||
|vim.schedule_wrap()|ped automatically, so API functions are safe to call.
|
|vim.schedule()|d automatically, so API functions are safe to call.
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {fn} (`function`) Callback to call once `timeout` expires
|
• {fn} (`function`) Callback to call once `timeout` expires
|
||||||
|
|||||||
@@ -509,25 +509,28 @@ end
|
|||||||
--- Defers calling {fn} until {timeout} ms passes.
|
--- Defers calling {fn} until {timeout} ms passes.
|
||||||
---
|
---
|
||||||
--- Use to do a one-shot timer that calls {fn}
|
--- Use to do a one-shot timer that calls {fn}
|
||||||
--- Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions are
|
--- Note: The {fn} is |vim.schedule()|d automatically, so API functions are
|
||||||
--- safe to call.
|
--- safe to call.
|
||||||
---@param fn function Callback to call once `timeout` expires
|
---@param fn function Callback to call once `timeout` expires
|
||||||
---@param timeout integer Number of milliseconds to wait before calling `fn`
|
---@param timeout integer Number of milliseconds to wait before calling `fn`
|
||||||
---@return table timer luv timer object
|
---@return table timer luv timer object
|
||||||
function vim.defer_fn(fn, timeout)
|
function vim.defer_fn(fn, timeout)
|
||||||
vim.validate('fn', fn, 'callable', true)
|
vim.validate('fn', fn, 'callable', true)
|
||||||
|
|
||||||
local timer = assert(vim.uv.new_timer())
|
local timer = assert(vim.uv.new_timer())
|
||||||
timer:start(
|
timer:start(timeout, 0, function()
|
||||||
timeout,
|
local _, err = vim.schedule(function()
|
||||||
0,
|
|
||||||
vim.schedule_wrap(function()
|
|
||||||
if not timer:is_closing() then
|
if not timer:is_closing() then
|
||||||
timer:close()
|
timer:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
fn()
|
fn()
|
||||||
end)
|
end)
|
||||||
)
|
|
||||||
|
if err then
|
||||||
|
timer:close()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
return timer
|
return timer
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ function vim.iconv(str, from, to, opts) end
|
|||||||
--- Schedules {fn} to be invoked soon by the main event-loop. Useful
|
--- Schedules {fn} to be invoked soon by the main event-loop. Useful
|
||||||
--- to avoid |textlock| or other temporary restrictions.
|
--- to avoid |textlock| or other temporary restrictions.
|
||||||
--- @param fn fun()
|
--- @param fn fun()
|
||||||
|
--- @return nil result
|
||||||
|
--- @return string? err Error message if scheduling failed, `nil` otherwise.
|
||||||
function vim.schedule(fn) end
|
function vim.schedule(fn) end
|
||||||
|
|
||||||
--- Waits up to `time` milliseconds, until `callback` returns `true` (success). Executes
|
--- Waits up to `time` milliseconds, until `callback` returns `true` (success). Executes
|
||||||
|
|||||||
@@ -407,17 +407,20 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
return lua_error(lstate);
|
return lua_error(lstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lua_pushnil(lstate);
|
||||||
// If main_loop is closing don't schedule tasks to run in the future,
|
// If main_loop is closing don't schedule tasks to run in the future,
|
||||||
// otherwise any refs allocated here will not be cleaned up.
|
// otherwise any refs allocated here will not be cleaned up.
|
||||||
if (main_loop.closing) {
|
if (main_loop.closing) {
|
||||||
return 0;
|
lua_pushliteral(lstate, "main loop is closing");
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaRef cb = nlua_ref_global(lstate, 1);
|
LuaRef cb = nlua_ref_global(lstate, 1);
|
||||||
// Pass along UI event handler to disable on error.
|
// Pass along UI event handler to disable on error.
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb,
|
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb,
|
||||||
(void *)(ptrdiff_t)ui_event_ns_id);
|
(void *)(ptrdiff_t)ui_event_ns_id);
|
||||||
return 0;
|
lua_pushnil(lstate);
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy timer callback. Used by f_wait().
|
// Dummy timer callback. Used by f_wait().
|
||||||
|
|||||||
@@ -1786,6 +1786,24 @@ describe('lua stdlib', function()
|
|||||||
eq(true, exec_lua [[return vim.g.test]])
|
eq(true, exec_lua [[return vim.g.test]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('nested vim.defer_fn does not leak handles on exit #19727', function()
|
||||||
|
n.expect_exit(exec_lua, function()
|
||||||
|
vim.defer_fn(function()
|
||||||
|
vim.defer_fn(function()
|
||||||
|
vim.defer_fn(function() end, 0)
|
||||||
|
end, 0)
|
||||||
|
end, 0)
|
||||||
|
vim.cmd('qall')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('vim.defer_fn with timeout does not leak handles on exit', function()
|
||||||
|
n.expect_exit(exec_lua, function()
|
||||||
|
vim.defer_fn(function() end, 50)
|
||||||
|
vim.cmd('qall')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe('vim.region', function()
|
describe('vim.region', function()
|
||||||
it('charwise', function()
|
it('charwise', function()
|
||||||
insert(dedent([[
|
insert(dedent([[
|
||||||
|
|||||||
@@ -510,8 +510,8 @@ function M.new_session(keep, ...)
|
|||||||
end
|
end
|
||||||
if delta > 500 then
|
if delta > 500 then
|
||||||
print(
|
print(
|
||||||
('Nvim session %s took %d milliseconds to exit\n'):format(test_id, delta)
|
('\nNvim session %s took %d milliseconds to exit\n'):format(test_id, delta)
|
||||||
.. 'This indicates a likely problem with the test even if it passed!\n'
|
.. 'This indicates a likely problem with the test even if it passed!'
|
||||||
)
|
)
|
||||||
io.stdout:flush()
|
io.stdout:flush()
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user