diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 7ab2390823..c477423ed7 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6588,24 +6588,20 @@ A jump table for the options with a short description can be found at |Q_op|. *'swapfile'* *'swf'* *'noswapfile'* *'noswf'* 'swapfile' 'swf' boolean (default on) local to buffer - Use a swapfile for the buffer. This option can be reset when a - swapfile is not wanted for a specific buffer. For example, with - confidential information that even root must not be able to access. - Careful: All text will be in memory: - - Don't use this for big files. - - Recovery will be impossible! - A swapfile will only be present when 'updatecount' is non-zero and - 'swapfile' is set. - When 'swapfile' is reset, the swap file for the current buffer is - immediately deleted. When 'swapfile' is set, and 'updatecount' is - non-zero, a swap file is immediately created. - Also see |swap-file|. - If you want to open a new buffer without creating a swap file for it, - use the |:noswapfile| modifier. - See 'directory' for where the swap file is created. + Use a |swap-file| for the buffer (if 'updatecount' is non-zero). The + 'directory' option decides where swapfiles are stored. - This option is used together with 'bufhidden' and 'buftype' to - specify special kinds of buffers. See |special-buffers|. + To open a new buffer without creating a swapfile, use |:noswapfile|. + To disable for an existing buffer, reset its 'swapfile' option. + Careful: + - Recovery will be impossible! + - The entire file will be in memory. + + When reset, the swapfile for the current buffer is immediately + deleted. When re-enabled (and 'updatecount' is non-zero), a swapfile + is immediately created. + + Used with 'bufhidden' and 'buftype' to specify |special-buffers|. *'switchbuf'* *'swb'* 'switchbuf' 'swb' string (default "uselast") @@ -7135,17 +7131,15 @@ A jump table for the options with a short description can be found at |Q_op|. *'updatecount'* *'uc'* 'updatecount' 'uc' number (default 200) global - After typing this many characters the swap file will be written to - disk. When zero, no swap file will be created at all (see chapter on - recovery |crash-recovery|). 'updatecount' is set to zero by starting - Vim with the "-n" option, see |startup|. When editing in readonly - mode this option will be initialized to 10000. - The swapfile can be disabled per buffer with 'swapfile'. - When 'updatecount' is set from zero to non-zero, swap files are - created for all buffers that have 'swapfile' set. When 'updatecount' - is set to zero, existing swap files are not deleted. - This option has no meaning in buffers where 'buftype' is "nofile" or - "nowrite". + The |swap-file| will be written after typing this many characters. + + - Ignored in buffers where 'buftype' is "nofile" or "nowrite". + - Initialized to 10000 when editing in readonly |-R| mode. + - To disable swapfiles per-buffer, unset the 'swapfile' option. + - To disable swapfiles globally, set this option to zero (or start + with |-n|). See |crash-recovery|. Existing swapfiles are not deleted. + - When re-enabled (from zero to non-zero), swapfiles are created for + all buffers that have 'swapfile' set. *'updatetime'* *'ut'* 'updatetime' 'ut' number (default 4000) diff --git a/runtime/lua/vim/_core/proc.lua b/runtime/lua/vim/_core/proc.lua new file mode 100644 index 0000000000..36bde5309a --- /dev/null +++ b/runtime/lua/vim/_core/proc.lua @@ -0,0 +1,20 @@ +-- OS/process utils. + +local M = {} + +--- Counts (approximate) open file descriptors for this process. Works on Linux/macOS/BSD. +---@return integer? # Number of open fds, or nil if `/dev/fd` failed (fds exhausted, or unavailable/Windows). +function M.count_open_fds() + local n = 0 + for _, _, err in vim.fs.dir('/dev/fd', { err = true }) do + -- If `/dev/fd` scan failed (e.g. EMFILE when fds exhausted), count would be misleading. + if err then + return nil + end + n = n + 1 + end + -- Discount the scan's own descriptor. + return math.max(0, n - 1) +end + +return M diff --git a/runtime/lua/vim/_meta/options.gen.lua b/runtime/lua/vim/_meta/options.gen.lua index a647755522..d8773efc3b 100644 --- a/runtime/lua/vim/_meta/options.gen.lua +++ b/runtime/lua/vim/_meta/options.gen.lua @@ -7023,24 +7023,20 @@ vim.o.sua = vim.o.suffixesadd vim.bo.suffixesadd = vim.o.suffixesadd vim.bo.sua = vim.bo.suffixesadd ---- Use a swapfile for the buffer. This option can be reset when a ---- swapfile is not wanted for a specific buffer. For example, with ---- confidential information that even root must not be able to access. ---- Careful: All text will be in memory: ---- - Don't use this for big files. ---- - Recovery will be impossible! ---- A swapfile will only be present when 'updatecount' is non-zero and ---- 'swapfile' is set. ---- When 'swapfile' is reset, the swap file for the current buffer is ---- immediately deleted. When 'swapfile' is set, and 'updatecount' is ---- non-zero, a swap file is immediately created. ---- Also see `swap-file`. ---- If you want to open a new buffer without creating a swap file for it, ---- use the `:noswapfile` modifier. ---- See 'directory' for where the swap file is created. +--- Use a `swap-file` for the buffer (if 'updatecount' is non-zero). The +--- 'directory' option decides where swapfiles are stored. --- ---- This option is used together with 'bufhidden' and 'buftype' to ---- specify special kinds of buffers. See `special-buffers`. +--- To open a new buffer without creating a swapfile, use `:noswapfile`. +--- To disable for an existing buffer, reset its 'swapfile' option. +--- Careful: +--- - Recovery will be impossible! +--- - The entire file will be in memory. +--- +--- When reset, the swapfile for the current buffer is immediately +--- deleted. When re-enabled (and 'updatecount' is non-zero), a swapfile +--- is immediately created. +--- +--- Used with 'bufhidden' and 'buftype' to specify `special-buffers`. --- --- @type boolean vim.o.swapfile = true @@ -7702,17 +7698,15 @@ vim.o.ur = vim.o.undoreload vim.go.undoreload = vim.o.undoreload vim.go.ur = vim.go.undoreload ---- After typing this many characters the swap file will be written to ---- disk. When zero, no swap file will be created at all (see chapter on ---- recovery `crash-recovery`). 'updatecount' is set to zero by starting ---- Vim with the "-n" option, see `startup`. When editing in readonly ---- mode this option will be initialized to 10000. ---- The swapfile can be disabled per buffer with 'swapfile'. ---- When 'updatecount' is set from zero to non-zero, swap files are ---- created for all buffers that have 'swapfile' set. When 'updatecount' ---- is set to zero, existing swap files are not deleted. ---- This option has no meaning in buffers where 'buftype' is "nofile" or ---- "nowrite". +--- The `swap-file` will be written after typing this many characters. +--- +--- - Ignored in buffers where 'buftype' is "nofile" or "nowrite". +--- - Initialized to 10000 when editing in readonly `-R` mode. +--- - To disable swapfiles per-buffer, unset the 'swapfile' option. +--- - To disable swapfiles globally, set this option to zero (or start +--- with `-n`). See `crash-recovery`. Existing swapfiles are not deleted. +--- - When re-enabled (from zero to non-zero), swapfiles are created for +--- all buffers that have 'swapfile' set. --- --- @type integer vim.o.updatecount = 200 diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index 2b26baf3bd..09b3882d40 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -208,6 +208,61 @@ local function check_watchers() ) end +--- Reads a single integer from a file (e.g. a `/proc/sys` entry). +---@param path string +---@return integer? +local function read_int(path) + local ok, lines = pcall(vim.fn.readfile, path, '', 1) + local v = ok and lines[1] and tonumber(lines[1]) + return v or nil +end + +-- Note: this is part of check_performance(). +local function check_limits() + -- 'ulimit -n' (RLIMIT_NOFILE): each Nvim buffer may hold an open swapfile. Sockets, channels, filewatchers also consume file descriptors. + if vim.fn.has('win32') == 0 then + local ok, out = system({ 'sh', '-c', 'ulimit -Sn' }) -- Must use a shell. + out = vim.trim(out or '') + local soft = tonumber(out) + local used = require('vim._core.proc').count_open_fds() + local used_str = used and (', currently open: %d'):format(used) or '' + local raise_advice = { + '\'swapfile\' buffers, sockets, channels, and file-watchers consume file descriptors. A low limit causes "(libuv) kqueue(): Too many open files" (EMFILE) errors.', + 'Increase the limit: add `ulimit -n 8192` to your shell rc. On macOS see also `launchctl limit maxfiles`; on Linux see `/etc/security/limits.conf` and systemd `LimitNOFILE`.', + } + if out == 'unlimited' then + health.ok(('ulimit -n (max open files): unlimited%s'):format(used_str)) + elseif not ok or soft == nil then + health.info(('ulimit -n (max open files): unknown (%s)%s'):format(out, used_str)) + elseif soft < 1024 then + health.warn(('ulimit -n (max open files) is low: %d%s'):format(soft, used_str), raise_advice) + elseif used and used > soft * 0.8 then + health.warn( + ('Open files near the limit: %d of %d (ulimit -n)'):format(used, soft), + raise_advice + ) + else + health.ok(('ulimit -n (max open files): %d%s'):format(soft, used_str)) + end + end + + -- Linux inotify limits filewatchers (LSP, vim._watch). Exhausting them reports ENOSPC ("no space on device"). + if vim.uv.os_uname().sysname == 'Linux' then + local watches = read_int('/proc/sys/fs/inotify/max_user_watches') + local instances = read_int('/proc/sys/fs/inotify/max_user_instances') + if watches and watches < 8192 then + health.warn(('fs.inotify.max_user_watches is low: %d'):format(watches), { + 'Filewatchers may fail with ENOSPC. To increase the limit: `sysctl fs.inotify.max_user_watches=524288` (persist in /etc/sysctl.conf).', + }) + end + if instances and instances < 128 then + health.warn(('fs.inotify.max_user_instances is low: %d'):format(instances), { + 'To increase the limit: `sysctl fs.inotify.max_user_instances=512` (persist in /etc/sysctl.conf).', + }) + end + end +end + local function check_performance() health.start('Performance') @@ -240,6 +295,7 @@ local function check_performance() end check_watchers() + check_limits() end -- Load the remote plugin manifest file and check for unregistered plugins diff --git a/runtime/lua/vim/pack/health.lua b/runtime/lua/vim/pack/health.lua index 1590530fba..dff67ffffd 100644 --- a/runtime/lua/vim/pack/health.lua +++ b/runtime/lua/vim/pack/health.lua @@ -243,8 +243,13 @@ local function check_plug_dir() local is_good = true local plug_dir = get_plug_dir() - for plug_name, _ in vim.fs.dir(plug_dir) do - is_good = check_installed_plugin(plug_name) and is_good + for plug_name, _, err in vim.fs.dir(plug_dir, { err = true }) do + if err then + health.error(err) + is_good = false + else + is_good = check_installed_plugin(plug_name) and is_good + end end if is_good then diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 20c000479c..ab6d99f1ce 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -9071,24 +9071,20 @@ local options = { cb = 'did_set_swapfile', defaults = true, desc = [=[ - Use a swapfile for the buffer. This option can be reset when a - swapfile is not wanted for a specific buffer. For example, with - confidential information that even root must not be able to access. - Careful: All text will be in memory: - - Don't use this for big files. - - Recovery will be impossible! - A swapfile will only be present when 'updatecount' is non-zero and - 'swapfile' is set. - When 'swapfile' is reset, the swap file for the current buffer is - immediately deleted. When 'swapfile' is set, and 'updatecount' is - non-zero, a swap file is immediately created. - Also see |swap-file|. - If you want to open a new buffer without creating a swap file for it, - use the |:noswapfile| modifier. - See 'directory' for where the swap file is created. + Use a |swap-file| for the buffer (if 'updatecount' is non-zero). The + 'directory' option decides where swapfiles are stored. - This option is used together with 'bufhidden' and 'buftype' to - specify special kinds of buffers. See |special-buffers|. + To open a new buffer without creating a swapfile, use |:noswapfile|. + To disable for an existing buffer, reset its 'swapfile' option. + Careful: + - Recovery will be impossible! + - The entire file will be in memory. + + When reset, the swapfile for the current buffer is immediately + deleted. When re-enabled (and 'updatecount' is non-zero), a swapfile + is immediately created. + + Used with 'bufhidden' and 'buftype' to specify |special-buffers|. ]=], full_name = 'swapfile', redraw = { 'statuslines' }, @@ -9942,17 +9938,15 @@ local options = { cb = 'did_set_updatecount', defaults = 200, desc = [=[ - After typing this many characters the swap file will be written to - disk. When zero, no swap file will be created at all (see chapter on - recovery |crash-recovery|). 'updatecount' is set to zero by starting - Vim with the "-n" option, see |startup|. When editing in readonly - mode this option will be initialized to 10000. - The swapfile can be disabled per buffer with 'swapfile'. - When 'updatecount' is set from zero to non-zero, swap files are - created for all buffers that have 'swapfile' set. When 'updatecount' - is set to zero, existing swap files are not deleted. - This option has no meaning in buffers where 'buftype' is "nofile" or - "nowrite". + The |swap-file| will be written after typing this many characters. + + - Ignored in buffers where 'buftype' is "nofile" or "nowrite". + - Initialized to 10000 when editing in readonly |-R| mode. + - To disable swapfiles per-buffer, unset the 'swapfile' option. + - To disable swapfiles globally, set this option to zero (or start + with |-n|). See |crash-recovery|. Existing swapfiles are not deleted. + - When re-enabled (from zero to non-zero), swapfiles are created for + all buffers that have 'swapfile' set. ]=], full_name = 'updatecount', scope = { 'global' }, diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 7db780c425..90affba31b 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -228,6 +228,7 @@ describe('vim._core', function() 'vim._core.help', 'vim._core.log', 'vim._core.options', + 'vim._core.proc', 'vim._core.server', 'vim._core.shared', 'vim._core.spell',