diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-25 21:51:13 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-25 21:51:13 -0500 |
| commit | 33c5da1bc33b59f2969f176f434ca66050deab23 (patch) | |
| tree | 3e342d597eb69b2818127301c84a789f42a3e7b1 /pearl.el | |
| parent | dc2ca47ea5ab053722bf59ae1fde92e3786f6f41 (diff) | |
| download | pearl-33c5da1bc33b59f2969f176f434ca66050deab23.tar.gz pearl-33c5da1bc33b59f2969f176f434ca66050deab23.zip | |
fix(save): guard atomic-saver commit callbacks against a killed buffer
The per-field atomic savers advance the baseline and rewrite the display through the marker (org-entry-put marker / org-with-point-at marker) inside the async push callback. If the buffer is killed before the push lands, those touch a dead marker and signal. The deleted pearl--push-issue-field carried this guard, but it went away with the immediate-push setters and the atomic engine never picked it up.
pearl--run-atomic-field-save now skips the commit when the marker's buffer is dead (a new pearl--marker-live-p, which also passes a nil marker for synthetic callers) and still reports the remote outcome -- the push already landed, so only the local buffer update is moot. All three commit sites are guarded: the clean push, the converged-remote case, and the use-remote conflict resolution.
Diffstat (limited to 'pearl.el')
| -rw-r--r-- | pearl.el | 21 |
1 files changed, 18 insertions, 3 deletions
@@ -3447,6 +3447,15 @@ Surface BUFFER when any field pushed. The shared close-out for (message "%s: %s" label (pearl--save-summary-message outcomes)) (when pushed (pearl--surface-buffer buffer)))) +(defun pearl--marker-live-p (marker) + "Non-nil if MARKER is nil, or points into a still-live buffer. +A nil MARKER (synthetic callers with no buffer) passes; a marker into a killed +buffer fails. Guards async commit callbacks that touch the buffer after a push +the buffer may not have survived." + (or (null marker) + (let ((buffer (marker-buffer marker))) + (and buffer (buffer-live-p buffer))))) + (defun pearl--run-atomic-field-save (spec callback) "Save one atomic (enum/relation) field per SPEC, calling CALLBACK once. SPEC keys: :issue-id :identifier :field :marker :label; :live and :baseline @@ -3467,11 +3476,15 @@ baseline is `unchanged'; otherwise the remote is fetched (`:error' -> (let* ((live (plist-get spec :live)) (baseline (plist-get spec :baseline)) (equal-fn (or (plist-get spec :equal) #'equal)) + (marker (plist-get spec :marker)) (push-then-commit (lambda (cb) (funcall (plist-get spec :push) (lambda (r) - (when (plist-get r :success) + ;; The push can land after the buffer was killed; only run + ;; the buffer-touching commit when it's still live, but + ;; still report the remote success either way. + (when (and (plist-get r :success) (pearl--marker-live-p marker)) (funcall (plist-get spec :commit-local))) (funcall cb (plist-get r :success))))))) (cond @@ -3487,7 +3500,8 @@ baseline is `unchanged'; otherwise the remote is fetched (`:error' -> ((eq remote :error) (funcall callback (pearl--save-outcome spec 'failed 'fetch-failed))) ((funcall equal-fn remote live) - (funcall (plist-get spec :commit-local)) + (when (pearl--marker-live-p marker) + (funcall (plist-get spec :commit-local))) (funcall callback (pearl--save-outcome spec 'unchanged nil))) ((funcall equal-fn remote baseline) (funcall push-then-commit @@ -3501,7 +3515,8 @@ baseline is `unchanged'; otherwise the remote is fetched (`:error' -> (plist-get spec :live-display) (funcall (plist-get spec :remote-display) remote) push-then-commit - (lambda () (funcall (plist-get spec :commit-remote) remote)) + (lambda () (when (pearl--marker-live-p marker) + (funcall (plist-get spec :commit-remote) remote))) (lambda (status reason) (funcall callback (pearl--save-outcome spec status reason)))))))))))) |
