diff options
| author | Stefan Monnier | 2024-10-03 14:32:09 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2024-10-03 14:32:09 -0400 |
| commit | ef587bf6b46b2ea3ef91b260ac2542666081260d (patch) | |
| tree | 54a0cd1e6d8b938ddb2bec1b0defd05636a2a2eb | |
| parent | 03a1e942c0b116b847832e5aa28e737547ea7f09 (diff) | |
| download | emacs-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.el | 56 |
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. |
| 171 | This is used to try and detect cases where buffer modifications are \"lost\".") | 171 | This 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. | ||
| 175 | Each 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. |
| 181 | The errors are kept in `track-changes--error-log'.") | 185 | The errors are kept in `track-changes--error-log'. |
| 186 | If 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 | |||
| 213 | that may block, do as little work as possible, ... | 218 | that may block, do as little work as possible, ... |
| 214 | When IMMEDIATE is non-nil, the SIGNAL should probably not always call | 219 | When 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 | |||
| 236 | Trackers can consume resources (especially if `track-changes-fetch' is | 242 | Trackers can consume resources (especially if `track-changes-fetch' is |
| 237 | not called), so it is good practice to unregister them when you don't | 243 | not called), so it is good practice to unregister them when you don't |
| 238 | need them any more." | 244 | need 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. | |||
| 270 | If no changes occurred since the last time, it doesn't call FUNC and | 277 | If no changes occurred since the last time, it doesn't call FUNC and |
| 271 | returns nil, otherwise it returns the value returned by FUNC | 278 | returns nil, otherwise it returns the value returned by FUNC |
| 272 | and re-enable the TRACKER corresponding to ID." | 279 | and 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. |
| 445 | Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).") | 476 | Each element is a tuple [BUFFER-NAME BACKTRACE RECENT-KEYS TRACE]. |
| 477 | where both RECENT-KEYS and TRACE are sorted oldest-first and | ||
| 478 | backtraces 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'!! |
| 455 | Details logged to `track-changes--error-log'") | 488 | Details 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) |