aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2024-10-03 14:32:09 -0400
committerStefan Monnier2024-10-03 14:32:09 -0400
commitef587bf6b46b2ea3ef91b260ac2542666081260d (patch)
tree54a0cd1e6d8b938ddb2bec1b0defd05636a2a2eb
parent03a1e942c0b116b847832e5aa28e737547ea7f09 (diff)
downloademacs-ef587bf6b46b2ea3ef91b260ac2542666081260d.tar.gz
emacs-ef587bf6b46b2ea3ef91b260ac2542666081260d.zip
track-changes.el: Improve error tracing to help debugging
Add a new `trace` setting for `track-changes-record-errors` to record more information in order to try and help find the root cause of errors. * lisp/emacs-lisp/track-changes.el (track-changes--trace): New var. (track-changes-record-errors): Document new `trace` setting. (track-change--backtrace, track-changes--trace): New functions. (track-changes--recover-from-error): Use them. (track-changes--error-log): Document new format. (track-changes-register, track-changes-unregister) (track-changes-fetch, track-changes--before, track-changes--after): Call `track-changes--trace`.
-rw-r--r--lisp/emacs-lisp/track-changes.el56
1 files changed, 46 insertions, 10 deletions
diff --git a/lisp/emacs-lisp/track-changes.el b/lisp/emacs-lisp/track-changes.el
index 92d14959763..1b0f64f544d 100644
--- a/lisp/emacs-lisp/track-changes.el
+++ b/lisp/emacs-lisp/track-changes.el
@@ -170,6 +170,10 @@ More specifically it indicates which \"before\" they hold.
170 "Current size of the buffer, as far as this library knows. 170 "Current size of the buffer, as far as this library knows.
171This is used to try and detect cases where buffer modifications are \"lost\".") 171This is used to try and detect cases where buffer modifications are \"lost\".")
172 172
173(defvar track-changes--trace nil
174 "Ring holding a trace of recent calls to the API.
175Each call is recorded as a (BUFFER-NAME . BACKTRACE).")
176
173;;;; Exposed API. 177;;;; Exposed API.
174 178
175(defvar track-changes-record-errors 179(defvar track-changes-record-errors
@@ -178,7 +182,8 @@ This is used to try and detect cases where buffer modifications are \"lost\".")
178 ;; annoy the user too much about errors. 182 ;; annoy the user too much about errors.
179 (string-match "\\..*\\." emacs-version) 183 (string-match "\\..*\\." emacs-version)
180 "If non-nil, keep track of errors in `before/after-change-functions' calls. 184 "If non-nil, keep track of errors in `before/after-change-functions' calls.
181The errors are kept in `track-changes--error-log'.") 185The errors are kept in `track-changes--error-log'.
186If set to `trace', then we additionally keep a trace of recent calls to the API.")
182 187
183(cl-defun track-changes-register ( signal &key nobefore disjoint immediate) 188(cl-defun track-changes-register ( signal &key nobefore disjoint immediate)
184 "Register a new tracker whose change-tracking function is SIGNAL. 189 "Register a new tracker whose change-tracking function is SIGNAL.
@@ -213,6 +218,7 @@ and should thus be extra careful: don't modify the buffer, don't call a function
213that may block, do as little work as possible, ... 218that may block, do as little work as possible, ...
214When IMMEDIATE is non-nil, the SIGNAL should probably not always call 219When IMMEDIATE is non-nil, the SIGNAL should probably not always call
215`track-changes-fetch', since that would defeat the purpose of this library." 220`track-changes-fetch', since that would defeat the purpose of this library."
221 (track-changes--trace)
216 (when (and nobefore disjoint) 222 (when (and nobefore disjoint)
217 ;; FIXME: Without `before-change-functions', we can discover 223 ;; FIXME: Without `before-change-functions', we can discover
218 ;; a disjoint change only after the fact, which is not good enough. 224 ;; a disjoint change only after the fact, which is not good enough.
@@ -236,6 +242,7 @@ When IMMEDIATE is non-nil, the SIGNAL should probably not always call
236Trackers can consume resources (especially if `track-changes-fetch' is 242Trackers can consume resources (especially if `track-changes-fetch' is
237not called), so it is good practice to unregister them when you don't 243not called), so it is good practice to unregister them when you don't
238need them any more." 244need them any more."
245 (track-changes--trace)
239 (unless (memq id track-changes--trackers) 246 (unless (memq id track-changes--trackers)
240 (error "Unregistering a non-registered tracker: %S" id)) 247 (error "Unregistering a non-registered tracker: %S" id))
241 (setq track-changes--trackers (delq id track-changes--trackers)) 248 (setq track-changes--trackers (delq id track-changes--trackers))
@@ -270,6 +277,7 @@ This reflects a bug somewhere, so please report it when it happens.
270If no changes occurred since the last time, it doesn't call FUNC and 277If no changes occurred since the last time, it doesn't call FUNC and
271returns nil, otherwise it returns the value returned by FUNC 278returns nil, otherwise it returns the value returned by FUNC
272and re-enable the TRACKER corresponding to ID." 279and re-enable the TRACKER corresponding to ID."
280 (track-changes--trace)
273 (cl-assert (memq id track-changes--trackers)) 281 (cl-assert (memq id track-changes--trackers))
274 (unless (equal track-changes--buffer-size (buffer-size)) 282 (unless (equal track-changes--buffer-size (buffer-size))
275 (track-changes--recover-from-error 283 (track-changes--recover-from-error
@@ -387,6 +395,29 @@ returned to a consistent state."
387 395
388;;;; Auxiliary functions. 396;;;; Auxiliary functions.
389 397
398(defun track-change--backtrace (n &optional base)
399 (let ((frames nil))
400 (catch 'done
401 (mapbacktrace (lambda (&rest frame)
402 (if (>= (setq n (- n 1)) 0)
403 (push frame frames)
404 (push '... frames)
405 (throw 'done nil)))
406 (or base #'track-change--backtrace)))
407 (nreverse frames)))
408
409(defun track-changes--trace ()
410 (when (eq 'trace track-changes-record-errors)
411 (require 'ring)
412 (declare-function ring-insert "ring" (ring item))
413 (declare-function make-ring "ring" (size))
414 (unless track-changes--trace
415 (setq track-changes--trace (make-ring 10)))
416 (ring-insert track-changes--trace
417 (cons (buffer-name)
418 (track-change--backtrace
419 10 #'track-changes--trace)))))
420
390(defun track-changes--clean-state () 421(defun track-changes--clean-state ()
391 (cond 422 (cond
392 ((null track-changes--state) 423 ((null track-changes--state)
@@ -442,7 +473,9 @@ returned to a consistent state."
442 473
443(defvar track-changes--error-log () 474(defvar track-changes--error-log ()
444 "List of errors encountered. 475 "List of errors encountered.
445Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).") 476Each element is a tuple [BUFFER-NAME BACKTRACE RECENT-KEYS TRACE].
477where both RECENT-KEYS and TRACE are sorted oldest-first and
478backtraces have the deepest frame first.")
446 479
447(defun track-changes--recover-from-error (&optional info) 480(defun track-changes--recover-from-error (&optional info)
448 ;; We somehow got out of sync. This is usually the result of a bug 481 ;; We somehow got out of sync. This is usually the result of a bug
@@ -453,14 +486,15 @@ Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).")
453 (message "Recovering from confusing calls to `before/after-change-functions'!") 486 (message "Recovering from confusing calls to `before/after-change-functions'!")
454 (warn "Missing/incorrect calls to `before/after-change-functions'!! 487 (warn "Missing/incorrect calls to `before/after-change-functions'!!
455Details logged to `track-changes--error-log'") 488Details logged to `track-changes--error-log'")
456 (push (list (buffer-name) info 489 (push (vector (buffer-name) info
457 (let* ((bf (backtrace-frames 490 (track-change--backtrace
458 #'track-changes--recover-from-error)) 491 50 #'track-changes--recover-from-error)
459 (tail (nthcdr 50 bf))) 492 (let ((rk (recent-keys 'include-cmds)))
460 (when tail (setcdr tail '...)) 493 (if (< (length rk) 20) rk (substring rk -20)))
461 bf) 494 (when (and (eq 'trace track-changes-record-errors)
462 (let ((rk (recent-keys 'include-cmds))) 495 (fboundp 'ring-elements))
463 (if (< (length rk) 20) rk (substring rk -20)))) 496 (apply #'vector
497 (nreverse (ring-elements track-changes--trace)))))
464 track-changes--error-log)) 498 track-changes--error-log))
465 (setq track-changes--before-clean 'unset) 499 (setq track-changes--before-clean 'unset)
466 (setq track-changes--buffer-size (buffer-size)) 500 (setq track-changes--buffer-size (buffer-size))
@@ -470,6 +504,7 @@ Details logged to `track-changes--error-log'")
470 (setq track-changes--state (track-changes--state))) 504 (setq track-changes--state (track-changes--state)))
471 505
472(defun track-changes--before (beg end) 506(defun track-changes--before (beg end)
507 (track-changes--trace)
473 (cl-assert track-changes--state) 508 (cl-assert track-changes--state)
474 (cl-assert (<= beg end)) 509 (cl-assert (<= beg end))
475 (let* ((size (- end beg)) 510 (let* ((size (- end beg))
@@ -554,6 +589,7 @@ Details logged to `track-changes--error-log'")
554 (buffer-substring-no-properties old-bend new-bend))))))))) 589 (buffer-substring-no-properties old-bend new-bend)))))))))
555 590
556(defun track-changes--after (beg end len) 591(defun track-changes--after (beg end len)
592 (track-changes--trace)
557 (cl-assert track-changes--state) 593 (cl-assert track-changes--state)
558 (and (eq track-changes--before-clean 'unset) 594 (and (eq track-changes--before-clean 'unset)
559 (not track-changes--before-no) 595 (not track-changes--before-no)