Skip to content

[audit] tabline_timer handle is never stopped — leaks if options.lua is re-sourced #265

Description

@stanfish06

What

options.lua:224–232 creates a repeating libuv timer for the custom tabline:

local tabline_timer = vim.uv.new_timer()
tabline_timer:start(2000, 2000, vim.schedule_wrap(function()
    ...
end))

tabline_timer is a module-local variable. If options.lua is re-executed (:luafile %, hot-reload, or a plugin that re-requires config modules), a new timer is started but the previous handle is no longer reachable — it can never be stopped and will fire every 2 seconds indefinitely. Over several reloads the leaked timers accumulate in the libuv event loop.

Where

lua/config/options.lua:224

Why it matters

Each leaked timer calls vim.cmd.redrawtabline() on its tick, causing redundant redraws. In a long session with multiple reloads the effect is background noise; in a session that hot-reloads config frequently (e.g. during config development) it can cause noticeable CPU usage.

Recommended action

Guard the timer creation with a global so it survives re-requires and is stopped before a new one is created:

if _G._tabline_timer then
    _G._tabline_timer:stop()
    _G._tabline_timer:close()
end
_G._tabline_timer = vim.uv.new_timer()
_G._tabline_timer:start(2000, 2000, vim.schedule_wrap(function()
    for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        if vim.bo[buf].buftype == "terminal" then
            vim.cmd.redrawtabline()
            return
        end
    end
end))

Using _G._tabline_timer (a well-namespaced global) lets re-execution clean up the previous handle before creating a new one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions