>> I wish I could describe exactly what happened. I found >> https://debbugs.gnu.org/cgi/bugreport.cgi?bug=70541 which sounds >> very much like what I'm seeing, but I don't use any input method.
Thanks for your description of the problem. A reproducible recipe is of course better, but sadly, we can't always have what we want. Let's hope we find that eventually we'll get what we need. >> Eventually, this pops into *Messages*: >> >> #+begin_quote >> cl--assertion-failed: Assertion failed: (or track-changes--before-no (<= >> track-changes--before-beg (track-changes--state-beg track-changes--state) >> beg end (track-changes--state-end track-changes--state) >> track-changes--before-end)) >> #+end_quote Hmm... this clearly points to a bug in track-changes. >> #+begin_quote >> ⛔ Warning (emacs): Missing/incorrect calls to >> ‘before/after-change-functions’!! >> Details logged to ‘track-changes--error-log’ >> #+end_quote Barring bugs in track-changes, this points to a bug in some other part of Emacs, which prevents track-changes from getting the info it needs. Track-changes (and Eglot) are supposedly equipped to deal with such problems, because we know we still have many of them. >> Over time, they just keep repeating, Hmm... `track-changes--recover-from-error` should (as the name suggests) "recover" so they shouldn't just keep repeating, unless the source of the problem keeps repeating as well. Or unless there's a bug in track-changes. >> although I think killing the >> buffer and reopening the file makes it stop. >> >> If I have inspected ‘track-changes--error-log’ correctly, this is >> what it contains (looks like some binary, hope it comes through >> in some useful fashion or another): It's a list of debug info, one per "recovery". Each debug info is made of the buffer name, the inconsistency encountered, then a backtrace, and an extract of (raw) `view-lossage`. Here's your data reformatted: (("lib.rs" (buffer-size 5254 5218) ((t track-changes--recover-from-error ... nil) (t track-changes-fetch ... nil) (t eglot--track-changes-fetch ... nil) (t eglot--signal-textDocument/didChange nil nil) (t eglot--signal-textDocument/didSave nil nil) (t run-hooks ... nil) (t basic-save-buffer ... nil) (t save-buffer ... nil) (t funcall-interactively ... nil) (t call-interactively ... nil) (t command-execute ... nil)) [111 (nil . other-window) 103 (nil . recompile) 121 (nil . undefined) 24 96 (nil . next-error) 1 (nil . move-beginning-of-line) 11 ...]) ("lib.rs" (buffer-size 5256 5254) ((t track-changes--recover-from-error ... nil) (t track-changes-fetch ... nil) (t eglot--track-changes-fetch ... nil) (t eglot--signal-textDocument/didChange nil nil) (t eglot--signal-textDocument/didSave nil nil) (t run-hooks ... nil) (t basic-save-buffer ... nil) (t save-buffer nil nil) (t #[257 "r\211q\210\300 )\207" [save-buffer] 2 ...] (t map-y-or-n-p ... nil) (t save-some-buffers ... nil) (t recompile ... nil) ...) [19 (nil . save-buffer) 24 24 (nil . exchange-point-and-mark) 23 (nil . kill-region) 24 19 (nil . save-buffer) backspace (nil . delete-backward-char) ...]) ("lib.rs" (buffer-size 5278 5256) ((t track-changes--recover-from-error ... nil) (t track-changes-fetch ... nil) (t eglot--track-changes-fetch ... nil) (t eglot--signal-textDocument/didChange nil nil) (t eglot--signal-textDocument/didSave nil nil) (t run-hooks ... nil) (t basic-save-buffer ... nil) (t save-buffer ... nil) (t funcall-interactively ... nil) (t call-interactively ... nil) (t command-execute ... nil)) [(nil . backward-word) 2 (nil . backward-char) 67108896 (nil . set-mark-command) 134217734 (nil . forward-sexp) 23 (nil . kill-region) 24 19 (nil . save-buffer) ...]) ("lib.rs" (buffer-size 5324 5278) ((t track-changes--recover-from-error ... nil) (t track-changes-fetch ... nil) (t eglot--track-changes-fetch ... nil) (t eglot--track-changes-signal ... nil) (t #[771 "\211^BZ\211^HG^E] ...) ...) ...) ...) This suggests the error is always detected "late" when we find that the buffer-size is not the one we expected, so some buffer change presumably happened without informing track-changes. That doesn't explain the assertion failure. Also, I don't see anything "unusual" in the view-lossage. >> I wish I could reliably repeat it. Given that it occurs after >> many hours of work, `emacs -Q` isn't an option. Thanks. I stared at the code for a bit trying to see how that assertion failure can occur, but nothing came to mind yet. I think we'll need more data. Can you try to install the patch below, then run with `debug-on-error` enabled? Hopefully when the error occurs, this will give us more info (ideally including a backtrace). Stefan
diff --git a/lisp/emacs-lisp/track-changes.el b/lisp/emacs-lisp/track-changes.el index 92d14959763..325a92317ca 100644 --- a/lisp/emacs-lisp/track-changes.el +++ b/lisp/emacs-lisp/track-changes.el @@ -229,6 +229,7 @@ track-changes-register (push tracker track-changes--clean-trackers) (when disjoint (push tracker track-changes--disjoint-trackers)) + (cl-assert (track-changes--sane-state-p)) tracker)) (defun track-changes-unregister (id) @@ -253,7 +254,8 @@ track-changes-unregister track-changes--buffer-size track-changes--before-clean track-changes--state)) - (remove-hook 'after-change-functions #'track-changes--after t))) + (remove-hook 'after-change-functions #'track-changes--after t)) + (cl-assert (track-changes--sane-state-p))) (defun track-changes-fetch (id func) "Fetch the pending changes for tracker ID pass them to FUNC. @@ -372,7 +374,8 @@ track-changes-fetch (funcall func beg end (or before lenbefore))) ;; Re-enable the tracker's signal only after running `func', so ;; as to avoid nested invocations. - (cl-pushnew id track-changes--clean-trackers)))) + (cl-pushnew id track-changes--clean-trackers) + (cl-assert (track-changes--sane-state-p))))) (defun track-changes-inconsistent-state-p () "Return whether the current buffer is in an inconsistent state. @@ -387,6 +390,19 @@ track-changes-inconsistent-state-p ;;;; Auxiliary functions. +(defun track-changes--sane-state-p () + (and (equal track-changes--buffer-size (buffer-size)) + (or track-changes--before-no + (pcase track-changes--before-clean + ('unset t) + ('set (<= track-changes--before-beg track-changes--before-end)) + ('nil + (<= track-changes--before-beg + (track-changes--state-beg track-changes--state) + (track-changes--state-end track-changes--state) + track-changes--before-end)) + (_ nil))))) + (defun track-changes--clean-state () (cond ((null track-changes--state) @@ -551,7 +567,8 @@ track-changes--before (length track-changes--before-string)))))) (setf track-changes--before-end new-bend) (cl-callf concat track-changes--before-string - (buffer-substring-no-properties old-bend new-bend))))))))) + (buffer-substring-no-properties old-bend new-bend)))))))) + (cl-assert (track-changes--sane-state-p))) (defun track-changes--after (beg end len) (cl-assert track-changes--state) @@ -576,10 +593,26 @@ track-changes--after ;; BEG..END is not covered by previous `before-change-functions'!! (track-changes--recover-from-error `(unexpected-after ,beg ,end ,len)) ;; Note the new changes. + (let ((orig-beg (track-changes--state-beg track-changes--state)) + (orig-end (track-changes--state-end track-changes--state))) (when (< beg (track-changes--state-beg track-changes--state)) (setf (track-changes--state-beg track-changes--state) beg)) (cl-callf (lambda (old-end) (max end (+ old-end offset))) (track-changes--state-end track-changes--state)) + (cl-assert (or track-changes--before-no + (<= track-changes--before-beg + (track-changes--state-beg track-changes--state))) + nil "<=? %S %S (was %S)" + track-changes--before-beg + (track-changes--state-beg track-changes--state) + orig-beg) + (cl-assert (or track-changes--before-no + (<= (track-changes--state-end track-changes--state) + track-changes--before-end)) + nil "<=? %S %S (was %S)" + (track-changes--state-end track-changes--state) + track-changes--before-end + orig-end)) (cl-assert (or track-changes--before-no (<= track-changes--before-beg (track-changes--state-beg track-changes--state) @@ -591,7 +624,8 @@ track-changes--after (if (track-changes--tracker-immediate tracker) (funcall (track-changes--tracker-signal tracker) tracker) (run-with-timer 0 nil #'track-changes--call-signal - (current-buffer) tracker))))) + (current-buffer) tracker)))) + (cl-assert (track-changes--sane-state-p))) (defun track-changes--call-signal (buf tracker) (when (buffer-live-p buf) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 82e99a2c920..a2c9f73fc73 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -2813,6 +2813,8 @@ eglot--signal-textDocument/didChange (defun eglot--signal-textDocument/didOpen () "Send textDocument/didOpen to server." + ;; Flush any potential pending change. + (eglot--track-changes-fetch eglot--track-changes) (setq eglot--recent-changes nil eglot--versioned-identifier 0 eglot--TextDocumentIdentifier-cache nil)