diff options
| author | João Távora | 2026-01-16 00:52:49 +0000 |
|---|---|---|
| committer | João Távora | 2026-01-21 12:26:24 +0000 |
| commit | d3548aea9683736ed17ff49f144df6a8a7a8c56f (patch) | |
| tree | 1933db0fa6b301d8a6e392154c740afc27318fd2 | |
| parent | a3ea65a984ff8b27b3698045a682d51ddcf20fbc (diff) | |
| download | emacs-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-NEWS | 7 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 36 |
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 | |||
| 25 | The new variable 'eglot-max-file-watches' limits the number of file | ||
| 26 | watches that can be created. Some language servers request watching | ||
| 27 | for a very large number of directories (e.g. Python virtualenvs), which | ||
| 28 | can 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 | ||
| 25 | Eglot now advertises support for file resource operations in workspace | 32 | Eglot 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. | ||
| 4687 | If this limit is reached, a warning is issued and further watches | ||
| 4688 | are 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. |
| 4689 | GLOBS is a list of (COMPILED-GLOB . KIND) pairs, where COMPILED-GLOB is | 4706 | GLOBS is a list of (COMPILED-GLOB . KIND) pairs, where COMPILED-GLOB is |
| 4690 | a compiled glob predicate and KIND is a bitmask of change types. DIR is | 4707 | 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." | |||
| 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, \ | ||
| 4751 | not 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) |