aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2026-01-16 00:52:49 +0000
committerJoão Távora2026-01-21 12:26:24 +0000
commitd3548aea9683736ed17ff49f144df6a8a7a8c56f (patch)
tree1933db0fa6b301d8a6e392154c740afc27318fd2
parenta3ea65a984ff8b27b3698045a682d51ddcf20fbc (diff)
downloademacs-d3548aea9683736ed17ff49f144df6a8a7a8c56f.tar.gz
emacs-d3548aea9683736ed17ff49f144df6a8a7a8c56f.zip
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.
-rw-r--r--etc/EGLOT-NEWS7
-rw-r--r--lisp/progmodes/eglot.el36
2 files changed, 38 insertions, 5 deletions
diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS
index ffe45baad0a..f050e0bc294 100644
--- a/etc/EGLOT-NEWS
+++ b/etc/EGLOT-NEWS
@@ -20,6 +20,13 @@ https://github.com/joaotavora/eglot/issues/1234.
20 20
21* Changes to upcoming Eglot 21* Changes to upcoming Eglot
22 22
23** File watch limits to prevent resource exhaustion (github#1568)
24
25The new variable 'eglot-max-file-watches' limits the number of file
26watches that can be created. Some language servers request watching
27for a very large number of directories (e.g. Python virtualenvs), which
28can exhaust system resources and cause slow startup.
29
23** Support for complex workspace edits (create/rename/delete files) 30** Support for complex workspace edits (create/rename/delete files)
24 31
25Eglot now advertises support for file resource operations in workspace 32Eglot now advertises support for file resource operations in workspace
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index dc30a4e1d34..6c10f9a5512 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -4680,11 +4680,28 @@ at point. With prefix argument, prompt for ACTION-KIND."
4680;;; File watchers (aka didChangeWatchedFiles) 4680;;; File watchers (aka didChangeWatchedFiles)
4681;;; 4681;;;
4682(defvar eglot-watch-files-outside-project-root t 4682(defvar eglot-watch-files-outside-project-root t
4683 "If non-nil, allow watching files outside project root") 4683 "If non-nil, allow watching files outside project root.")
4684
4685(defvar eglot-max-file-watches 10000
4686 "Maximum number of file watches across all Eglot servers.
4687If this limit is reached, a warning is issued and further watches
4688are not added. Set to nil for unlimited watches.")
4689
4690(defun eglot--count-file-watches ()
4691 "Count total file watches across all Eglot servers."
4692 (let ((count 0))
4693 (maphash (lambda (_proj servers)
4694 (dolist (server servers)
4695 (maphash (lambda (_id descs)
4696 (cl-incf count (length descs)))
4697 (eglot--file-watches server))))
4698 eglot--servers-by-project)
4699 count))
4684 4700
4685(cl-defun eglot--watch-globs (server id globs dir in-root 4701(cl-defun eglot--watch-globs (server id globs dir in-root
4686 &aux (project (eglot--project server)) 4702 &aux (project (eglot--project server))
4687 success) 4703 success
4704 (watch-count (eglot--count-file-watches)))
4688 "Set up file watching for relative file names matching GLOBS under DIR. 4705 "Set up file watching for relative file names matching GLOBS under DIR.
4689GLOBS is a list of (COMPILED-GLOB . KIND) pairs, where COMPILED-GLOB is 4706GLOBS is a list of (COMPILED-GLOB . KIND) pairs, where COMPILED-GLOB is
4690a compiled glob predicate and KIND is a bitmask of change types. DIR is 4707a 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."
4727 (handle-event `(,desc deleted ,file)) 4744 (handle-event `(,desc deleted ,file))
4728 (handle-event `(,desc created ,file1)))))) 4745 (handle-event `(,desc created ,file1))))))
4729 (add-watch (subdir) 4746 (add-watch (subdir)
4730 (when (file-readable-p subdir) 4747 (cond ((not (file-readable-p subdir)))
4731 (push (file-notify-add-watch subdir '(change) #'handle-event) 4748 ((and eglot-max-file-watches
4732 (gethash id (eglot--file-watches server)))))) 4749 (>= watch-count eglot-max-file-watches))
4750 (eglot--warn "Reached `eglot-max-file-watches' limit of %d, \
4751not watching some directories" eglot-max-file-watches)
4752 ;; Could `(setq success t)' here to keep partial watches.
4753 (jsonrpc-error "Reached `eglot-max-file-watches' limit of %d"
4754 eglot-max-file-watches))
4755 (t
4756 (push (file-notify-add-watch subdir '(change) #'handle-event)
4757 (gethash id (eglot--file-watches server)))
4758 (cl-incf watch-count)))))
4733 (let ((subdirs (if (or (null dir) in-root) 4759 (let ((subdirs (if (or (null dir) in-root)
4734 (subdirs-using-project) 4760 (subdirs-using-project)
4735 (condition-case _ (subdirs-using-find) 4761 (condition-case _ (subdirs-using-find)