aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Schneider <asn@cryptomilk.org>2024-07-06 11:44:19 +0200
committerGitHub <noreply@github.com>2024-07-06 11:44:19 +0200
commit55e4301036bb938474fc9768c41e28df867d9286 (patch)
treed83f3c3d1bccb1ad97197f322fe5c689c4aef8fd
parent91e5dcae3d47e7eaf25537471288c27055fdddbe (diff)
downloadrneovim-55e4301036bb938474fc9768c41e28df867d9286.tar.gz
rneovim-55e4301036bb938474fc9768c41e28df867d9286.tar.bz2
rneovim-55e4301036bb938474fc9768c41e28df867d9286.zip
feat(lsp): drop fswatch, use inotifywait (#29374)
This patch replaces fswatch with inotifywait from inotify-toools: https://github.com/inotify-tools/inotify-tools fswatch takes ~1min to set up recursively for the Samba source code directory. inotifywait needs less than a second to do the same thing. https://github.com/emcrisostomo/fswatch/issues/321 Also it fswatch seems to be unmaintained in the meantime. Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
-rwxr-xr-x.github/scripts/install_deps.sh2
-rw-r--r--runtime/doc/lua.txt17
-rw-r--r--runtime/lua/vim/_watch.lua61
-rw-r--r--runtime/lua/vim/lsp/_watchfiles.lua4
-rw-r--r--runtime/lua/vim/lsp/health.lua6
-rw-r--r--test/functional/lua/watch_spec.lua9
-rw-r--r--test/functional/plugin/lsp_spec.lua8
7 files changed, 54 insertions, 53 deletions
diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh
index 66f418eb10..b90a84fc24 100755
--- a/.github/scripts/install_deps.sh
+++ b/.github/scripts/install_deps.sh
@@ -30,7 +30,7 @@ if [[ $os == Linux ]]; then
fi
if [[ -n $TEST ]]; then
- sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb fswatch
+ sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb inotify-tools
# Use default CC to avoid compilation problems when installing Python modules
CC=cc python3 -m pip -q install --user --upgrade pynvim
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 39a047cbab..dfaf666874 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -543,16 +543,19 @@ Example: File-change detection *watch-file*
vim.api.nvim_command(
"command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))")
<
- *fswatch-limitations*
-When on Linux and using fswatch, you may need to increase the maximum number
-of `inotify` watches and queued events as the default limit can be too low. To
-increase the limit, run: >sh
- sysctl fs.inotify.max_user_watches=100000
- sysctl fs.inotify.max_queued_events=100000
+ *inotify-limitations*
+When on Linux you may need to increase the maximum number of `inotify` watches
+and queued events as the default limit can be too low. To increase the limit,
+run: >sh
+ sysctl fs.inotify.max_user_watches=494462
<
-This will increase the limit to 100000 watches and queued events. These lines
+This will increase the limit to 494462 watches and queued events. These lines
can be added to `/etc/sysctl.conf` to make the changes persistent.
+Note that each watch is a structure in the Kernel, thus available memory is
+also a bottleneck for using inotify. In fact, a watch can take up to 1KB of
+space. This means a million watches could result in 1GB of extra RAM usage.
+
Example: TCP echo-server *tcp-server*
1. Save this code to a file.
2. Execute it with ":luafile %".
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index 02b3f536c2..40f18ce5b0 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -227,11 +227,12 @@ end
--- @param data string
--- @param opts vim._watch.Opts?
--- @param callback vim._watch.Callback
-local function fswatch_output_handler(data, opts, callback)
+local function on_inotifywait_output(data, opts, callback)
local d = vim.split(data, '%s+')
-- only consider the last reported event
- local fullpath, event = d[1], d[#d]
+ local path, event, file = d[1], d[2], d[#d]
+ local fullpath = vim.fs.joinpath(path, file)
if skip(fullpath, opts) then
return
@@ -240,20 +241,16 @@ local function fswatch_output_handler(data, opts, callback)
--- @type integer
local change_type
- if event == 'Created' then
+ if event == 'CREATE' then
change_type = M.FileChangeType.Created
- elseif event == 'Removed' then
+ elseif event == 'DELETE' then
change_type = M.FileChangeType.Deleted
- elseif event == 'Updated' then
+ elseif event == 'MODIFY' then
change_type = M.FileChangeType.Changed
- elseif event == 'Renamed' then
- local _, staterr, staterrname = uv.fs_stat(fullpath)
- if staterrname == 'ENOENT' then
- change_type = M.FileChangeType.Deleted
- else
- assert(not staterr, staterr)
- change_type = M.FileChangeType.Created
- end
+ elseif event == 'MOVED_FROM' then
+ change_type = M.FileChangeType.Deleted
+ elseif event == 'MOVED_TO' then
+ change_type = M.FileChangeType.Created
end
if change_type then
@@ -265,24 +262,22 @@ end
--- @param opts vim._watch.Opts?
--- @param callback vim._watch.Callback Callback for new events
--- @return fun() cancel Stops the watcher
-function M.fswatch(path, opts, callback)
- -- debounce isn't the same as latency but close enough
- local latency = 0.5 -- seconds
- if opts and opts.debounce then
- latency = opts.debounce / 1000
- end
-
+function M.inotify(path, opts, callback)
local obj = vim.system({
- 'fswatch',
- '--event=Created',
- '--event=Removed',
- '--event=Updated',
- '--event=Renamed',
- '--event-flags',
+ 'inotifywait',
+ '--quiet', -- suppress startup messages
+ '--no-dereference', -- don't follow symlinks
+ '--monitor', -- keep listening for events forever
'--recursive',
- '--latency=' .. tostring(latency),
- '--exclude',
- '/.git/',
+ '--event',
+ 'create',
+ '--event',
+ 'delete',
+ '--event',
+ 'modify',
+ '--event',
+ 'move',
+ '@.git', -- ignore git directory
path,
}, {
stderr = function(err, data)
@@ -292,11 +287,11 @@ function M.fswatch(path, opts, callback)
if data and #vim.trim(data) > 0 then
vim.schedule(function()
- if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then
- data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.'
+ if vim.fn.has('linux') == 1 and vim.startswith(data, 'Failed to watch') then
+ data = 'inotify(7) limit reached, see :h inotify-limitations for more info.'
end
- vim.notify('fswatch: ' .. data, vim.log.levels.ERROR)
+ vim.notify('inotify: ' .. data, vim.log.levels.ERROR)
end)
end
end,
@@ -306,7 +301,7 @@ function M.fswatch(path, opts, callback)
end
for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do
- fswatch_output_handler(line, opts, callback)
+ on_inotifywait_output(line, opts, callback)
end
end,
-- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point.
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 49328fbe9b..98e9818bcd 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -9,8 +9,8 @@ local M = {}
if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then
M._watchfunc = watch.watch
-elseif vim.fn.executable('fswatch') == 1 then
- M._watchfunc = watch.fswatch
+elseif vim.fn.executable('inotifywait') == 1 then
+ M._watchfunc = watch.inotify
else
M._watchfunc = watch.watchdirs
end
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index ffe595ab37..18066a84db 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -90,8 +90,8 @@ local function check_watcher()
watchfunc_name = 'libuv-watch'
elseif watchfunc == vim._watch.watchdirs then
watchfunc_name = 'libuv-watchdirs'
- elseif watchfunc == vim._watch.fswatch then
- watchfunc_name = 'fswatch'
+ elseif watchfunc == vim._watch.inotifywait then
+ watchfunc_name = 'inotifywait'
else
local nm = debug.getinfo(watchfunc, 'S').source
watchfunc_name = string.format('Custom (%s)', nm)
@@ -99,7 +99,7 @@ local function check_watcher()
report_info('File watch backend: ' .. watchfunc_name)
if watchfunc_name == 'libuv-watchdirs' then
- report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.')
+ report_warn('libuv-watchdirs has known performance issues. Consider installing inotify-tools.')
end
end
diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua
index bd8faadf5b..3d2dda716e 100644
--- a/test/functional/lua/watch_spec.lua
+++ b/test/functional/lua/watch_spec.lua
@@ -23,10 +23,13 @@ describe('vim._watch', function()
local function run(watchfunc)
it('detects file changes (watchfunc=' .. watchfunc .. '())', function()
- if watchfunc == 'fswatch' then
+ if watchfunc == 'inotify' then
skip(is_os('win'), 'not supported on windows')
skip(is_os('mac'), 'flaky test on mac')
- skip(not is_ci() and n.fn.executable('fswatch') == 0, 'fswatch not installed and not on CI')
+ skip(
+ not is_ci() and n.fn.executable('inotifywait') == 0,
+ 'inotify-tools not installed and not on CI'
+ )
end
if watchfunc == 'watch' then
@@ -123,5 +126,5 @@ describe('vim._watch', function()
run('watch')
run('watchdirs')
- run('fswatch')
+ run('inotify')
end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 0630df65d5..2b8a7aed9e 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -5128,12 +5128,12 @@ describe('LSP', function()
it(
string.format('sends notifications when files change (watchfunc=%s)', watchfunc),
function()
- if watchfunc == 'fswatch' then
+ if watchfunc == 'inotify' then
skip(is_os('win'), 'not supported on windows')
skip(is_os('mac'), 'flaky test on mac')
skip(
- not is_ci() and fn.executable('fswatch') == 0,
- 'fswatch not installed and not on CI'
+ not is_ci() and fn.executable('inotifywait') == 0,
+ 'inotify-tools not installed and not on CI'
)
end
@@ -5265,7 +5265,7 @@ describe('LSP', function()
test_filechanges('watch')
test_filechanges('watchdirs')
- test_filechanges('fswatch')
+ test_filechanges('inotify')
it('correctly registers and unregisters', function()
local root_dir = '/some_dir'