Commit f63f8df
committed
fix: serialise materialisation and settle the marker on every exit
Three round-three review findings, two of them in this repo.
Materialisation raced the serialised install. `Castle.install/1` composed
`Commands.materialise/3` and then `Commands.install/4`, so two callers
both configured the target before either reached the lock. The argument
for keeping it outside was that its primitives refuse rather than
replace; that is true of the staging and of `sys.config.pristine` and
false of the step that matters, because materialising *ends* in a rename
onto `sys.config` - a replace by design, since that is the file
`:release_handler` reads. So the loser's providers could overwrite the
configuration the winner's provisional release was about to boot, after
which the loser was refused for the winner's marker: a refused install
decided what a successful one booted. Providers are not obliged to agree
across evaluations, which is why `sys.config.pristine` exists at all.
Materialising is now the third step inside `Commands.install/5`, after
the record check and after the pending-marker refusal, so a caller that
is going to be refused configures nothing. Inside the lock alone would
not have been enough: ahead of `unclaimed/3` it would still have replaced
the configuration on its way to being told no, and there is a test whose
only job is to fail against that arrangement. The boundary moved out with
it - `Castle.install/1` is one call, so "an install is serialised" is
true of the public entry point rather than of a part of it. Only the ERTS
guard stays outside, which changes what an ERTS-less deployment is told
by `install`: "Cannot install", not "Cannot configure".
An exceptional failure left an actionable marker pair. Cleanup ran only
when `install_release/1` returned an error or an unrecognised value, so
an exit, a throw or a raise skipped it - and where
`prepare_restart_new_emulator/7` had already written
`new_start_erl.data`, that left the complete pair the launcher acts on.
The region is now an implicit `try` with `catch` and `else` clauses, not
`after`: an `after` cannot see which way the block went and would disarm
the successful restart install too, taking away the marker whose whole
purpose is to outlive the call.
Failing to settle the marker is now reported rather than swallowed.
Ignoring `File.rm/1` rested on a directory the marker cannot be removed
from being one it could not have been linked into, which holds only if
nothing changed in between - and `install_release/1` runs in between.
An unreadable marker was worse than ignored, it was classified as another
attempt's and left alone. `disarm/3` now answers four ways: ours is
removed and a failed removal reported, theirs is left, gone is success,
and unverifiable is reported. What the operator is told names the file,
says OTP's own file may be beside it, says an ordinary restart will
therefore boot the version the install did not finish, and asks for the
marker to be removed first.
The read and the removal go through `Castle.Deployment`, for the reason
`stat/1` is there: the answers that decide what Castle says are the
failing ones, and every fixture that makes them fail uses a mode, which
root and some filesystems ignore.
Claude-Session: https://claude.ai/code/session_01C4oaMvbR1cbxrZBj8qwkqN1 parent 6ea1fc2 commit f63f8df
9 files changed
Lines changed: 1109 additions & 210 deletions
File tree
- lib
- castle
- test
- castle
- support
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
152 | 175 | | |
153 | 176 | | |
154 | 177 | | |
| |||
161 | 184 | | |
162 | 185 | | |
163 | 186 | | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
164 | 205 | | |
165 | 206 | | |
166 | 207 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
45 | 59 | | |
46 | | - | |
47 | 60 | | |
48 | 61 | | |
49 | 62 | | |
| |||
65 | 78 | | |
66 | 79 | | |
67 | 80 | | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
78 | 85 | | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
85 | 98 | | |
86 | 99 | | |
87 | 100 | | |
| |||
0 commit comments