1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-23 04:53:12 -08:00

Eglot: limit the number of file watches

Some language servers request file watching for a very large number of
directories (e.g. Python virtualenvs), which can exhaust system
resources and cause slow startup.

https://github.com/joaotavora/eglot/issues/1568

* lisp/progmodes/eglot.el (eglot-max-file-watches): New variable.
(eglot--count-file-watches): New function.
(eglot--watch-globs): Use them to limit watches.  Signal jsonrpc-error
when limit is reached.
(eglot-watch-files-outside-project-root): Fix docstring punctuation.

* etc/EGLOT-NEWS: Mention change.
This commit is contained in:
João Távora 2026-01-16 00:52:49 +00:00
parent a3ea65a984
commit d3548aea96
2 changed files with 38 additions and 5 deletions

View file

@ -20,6 +20,13 @@ https://github.com/joaotavora/eglot/issues/1234.
* Changes to upcoming Eglot
** File watch limits to prevent resource exhaustion (github#1568)
The new variable 'eglot-max-file-watches' limits the number of file
watches that can be created. Some language servers request watching
for a very large number of directories (e.g. Python virtualenvs), which
can exhaust system resources and cause slow startup.
** Support for complex workspace edits (create/rename/delete files)
Eglot now advertises support for file resource operations in workspace

View file

@ -4680,11 +4680,28 @@ at point. With prefix argument, prompt for ACTION-KIND."
;;; File watchers (aka didChangeWatchedFiles)
;;;
(defvar eglot-watch-files-outside-project-root t
"If non-nil, allow watching files outside project root")
"If non-nil, allow watching files outside project root.")
(defvar eglot-max-file-watches 10000
"Maximum number of file watches across all Eglot servers.
If this limit is reached, a warning is issued and further watches
are not added. Set to nil for unlimited watches.")
(defun eglot--count-file-watches ()
"Count total file watches across all Eglot servers."
(let ((count 0))
(maphash (lambda (_proj servers)
(dolist (server servers)
(maphash (lambda (_id descs)
(cl-incf count (length descs)))
(eglot--file-watches server))))
eglot--servers-by-project)
count))
(cl-defun eglot--watch-globs (server id globs dir in-root
&aux (project (eglot--project server))
success)
success
(watch-count (eglot--count-file-watches)))
"Set up file watching for relative file names matching GLOBS under DIR.
GLOBS is a list of (COMPILED-GLOB . KIND) pairs, where COMPILED-GLOB is
a compiled glob predicate and KIND is a bitmask of change types. DIR is
@ -4727,9 +4744,18 @@ happens to be inside or matching the project root."
(handle-event `(,desc deleted ,file))
(handle-event `(,desc created ,file1))))))
(add-watch (subdir)
(when (file-readable-p subdir)
(push (file-notify-add-watch subdir '(change) #'handle-event)
(gethash id (eglot--file-watches server))))))
(cond ((not (file-readable-p subdir)))
((and eglot-max-file-watches
(>= watch-count eglot-max-file-watches))
(eglot--warn "Reached `eglot-max-file-watches' limit of %d, \
not watching some directories" eglot-max-file-watches)
;; Could `(setq success t)' here to keep partial watches.
(jsonrpc-error "Reached `eglot-max-file-watches' limit of %d"
eglot-max-file-watches))
(t
(push (file-notify-add-watch subdir '(change) #'handle-event)
(gethash id (eglot--file-watches server)))
(cl-incf watch-count)))))
(let ((subdirs (if (or (null dir) in-root)
(subdirs-using-project)
(condition-case _ (subdirs-using-find)