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:
zeertzjq
2026-02-01 21:29:19 +08:00
committed by GitHub
parent 0501c5fd09
commit 1906da52db
6 changed files with 41 additions and 11 deletions

View File

@@ -509,25 +509,28 @@ end
--- Defers calling {fn} until {timeout} ms passes.
---
--- 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.
---@param fn function Callback to call once `timeout` expires
---@param timeout integer Number of milliseconds to wait before calling `fn`
---@return table timer luv timer object
function vim.defer_fn(fn, timeout)
vim.validate('fn', fn, 'callable', true)
local timer = assert(vim.uv.new_timer())
timer:start(
timeout,
0,
vim.schedule_wrap(function()
timer:start(timeout, 0, function()
local _, err = vim.schedule(function()
if not timer:is_closing() then
timer:close()
end
fn()
end)
)
if err then
timer:close()
end
end)
return timer
end

View File

@@ -178,6 +178,8 @@ function vim.iconv(str, from, to, opts) end
--- Schedules {fn} to be invoked soon by the main event-loop. Useful
--- to avoid |textlock| or other temporary restrictions.
--- @param fn fun()
--- @return nil result
--- @return string? err Error message if scheduling failed, `nil` otherwise.
function vim.schedule(fn) end
--- Waits up to `time` milliseconds, until `callback` returns `true` (success). Executes