aboutsummaryrefslogtreecommitdiff
path: root/working/org-element-cache-persist-bug/README.org
blob: 2ad8b1bf303cf1fdba675ed37ae7a3b4b5b64ff0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#+TITLE: org-element persistent-cache corruption — reproduction evidence
#+AUTHOR: Craig Jennings
#+DATE: 2026-07-28

* What this is

Evidence for the org-element cache corruption I chased on 2026-07-28, kept
because the diagnosis outlived the fix. I shipped a fix as =d2103877=, put it
through four hostile reviews, and reverted it on their findings. These scripts
are what the reviews were arguing about.

Run any of them with:

: emacs --batch -q -l <script>

Several of the failing ones never terminate once corruption sets in, because
=org-element--cache-self-verify= floods warnings (one run reached 26 million
lines). Use =timeout=.

* The actual bug

=org-element--cache-persist-before-write= (org-element.el:7694) resolves
=(get-file-buffer ...)= and destructively nils =:buffer= on the *live* cache.
The next edit to that buffer then signals =wrong-type-argument stringp nil=
from =org-element--cache-after-change=.

It needs no second buffer and no dirvish. That is the finding that killed the
fix: I had diagnosed a *trigger* and written the fix against it.

Which buffer gets corrupted is decided by =buffer-list= order, since
=get-file-buffer= returns the first buffer visiting the file. That is why the
symptom is intermittent, and why any future fix needs a test that controls the
order rather than accepting whatever the harness happens to produce.

* The scripts

| File                       | What it shows                                             |
|----------------------------+-----------------------------------------------------------|
| =hostile-d-nopreview.el=   | The root cause. One org buffer, no preview, no dirvish.   |
|                            | =org-persist-write-all-buffer= alone strips =:buffer=     |
|                            | from 6134 elements and the next insert signals.           |
|----------------------------+-----------------------------------------------------------|
| =hostile-d-order.el=       | Corruption depends on =buffer-list= order. Bury the real  |
|                            | buffer and =get-file-buffer= returns the other one, and   |
|                            | the run comes back clean.                                 |
|----------------------------+-----------------------------------------------------------|
| =hostile-d-faithful.el=    | A faithful replay of dirvish's preview, with the real     |
|                            | =dirvish-preview-environment= bindings and buffer naming. |
|                            | Reproduces. My own replay below was looser.               |
|----------------------------+-----------------------------------------------------------|
| =hostile-c-repro.el=       | The regression the reverted fix introduced: with          |
|                            | =org-element-cache-persistent= nil in a preview buffer,   |
|                            | =org-element-cache-reset= unregisters the *real* file's   |
|                            | persisted cache and removes it from disk.                 |
|----------------------------+-----------------------------------------------------------|
| =hostile-a-errorbranch.el= | dirvish.el:671-680 kills the preview buffer on a signal   |
|                            | without ever running =dirvish-preview-setup-hook=, so a   |
|                            | hook-based fix is skipped there. Needs =so-long= enabled  |
|                            | to reach, which it is not here today.                     |
|----------------------------+-----------------------------------------------------------|
| =hostile-e-ffnoselect.el=  | =dirvish--find-file-temporarily= returns the user's       |
|                            | existing buffer, which dirvish later kills. Separate      |
|                            | pre-existing bug, tracked as its own task.                |
|----------------------------+-----------------------------------------------------------|
| =repro-preview-cache.el=   | My original replay. Kept as the record of what I ran,     |
|                            | not as good evidence: its control differs from it by      |
|                            | three variables, so on its own it cannot attribute cause. |
|----------------------------+-----------------------------------------------------------|
| =control-no-preview.el=    | That flawed control.                                      |
|----------------------------+-----------------------------------------------------------|
| =variant-no-kill.el=       | Preview created but not killed: clean. Supported the      |
|                            | "the kill is required" framing, which turned out to be    |
|                            | backwards. The persist *write* is causal; the kill only   |
|                            | triggers it.                                              |
|----------------------------+-----------------------------------------------------------|
| =variant-no-persist.el=    | Same sequence with persistence off: clean.                |
|----------------------------+-----------------------------------------------------------|
| =verify-shipped-fix.el=    | The reverted fix suppressing the corruption on the        |
|                            | dirvish path. It did work there. It was aimed one layer   |
|                            | too low and carried a regression.                         |
|----------------------------+-----------------------------------------------------------|
| =d2103877-reverted.patch=  | The reverted commit, kept so the approach is recoverable. |
|----------------------------+-----------------------------------------------------------|

* What does not reproduce it

Turning =org-element--cache-self-verify= on does *not* manufacture the error.
It reproduces at org's defaults; self-verify only makes it loud. I had worried
the instrumentation was creating the signal, and it is not.

* Filed as

See =todo.org=: the org-element persist task, and the separate dirvish
=find-file-noselect= buffer-kill task.