fix(web): order overlapping settings writes by issue, not by completion
Review round 2 on #2091. Recording on completion made the *last to finish* the winner, and `updateServerSettings` waits for a list read after its PUT — so an older write with a slow read finishes last and would overwrite the record with a value a newer write had already replaced on disk. Both writers can overlap: two quick toggles, or the settings modal's debounced flush. A write is now announced with `begin` when it is issued and confirmed with `landed` when it completes, and a confirmation is dropped if a later-issued write for that server has already confirmed. The high-water mark is kept apart from the record so a straggler cannot resurrect a record a fresh list read dropped. Issue order is a proxy for disk order rather than a proof of it — HTTP promises nothing about two in-flight PUTs — but it is the best signal on this side until the servers route returns a revision to order by. Noted on the hook alongside the other consequence of that gap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
e359368892
commit
7fa42299c7
1 changed files with 121 additions and 50 deletions
|
|
@ -3769,6 +3769,10 @@ function App() {
|
|||
// server's rehydrated secrets, so it can trigger the pending
|
||||
// plaintext-to-encrypted upgrade even though the user only toggled
|
||||
// pagination (#1950 review r22).
|
||||
// Announced before the request goes out, so two toggles in flight at once
|
||||
// are ordered by when they were issued rather than by which one's list
|
||||
// reload finished first (#2089).
|
||||
const write = lastPersistedSettings.begin(activeServerId);
|
||||
void refreshingPersist(updateServerSettings, refreshInitialConfig)(
|
||||
activeServerId,
|
||||
next,
|
||||
|
|
@ -3778,7 +3782,7 @@ function App() {
|
|||
// whatever is written next, since the `servers` entry it was derived
|
||||
// from will keep describing the old value if the reload behind this
|
||||
// write — or any later one — fails (#2089).
|
||||
lastPersistedSettings.record(activeServerId, next);
|
||||
write.landed(next);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
// Persist failed: revert the optimistic override and roll the live
|
||||
|
|
@ -4168,17 +4172,21 @@ function App() {
|
|||
// outside the coverage gate, so wiring written here is tested by nothing
|
||||
// (#1950 review r17).
|
||||
onPersist: async (id: string, value: InspectorServerSettings) => {
|
||||
// Announced before the request, like the toggle's own write: the debounced
|
||||
// flush can put two saves for one server in flight, and the later-issued
|
||||
// one describes disk however the two happen to finish (#2089).
|
||||
const write = lastPersistedSettings.begin(id);
|
||||
await refreshingPersist(updateServerSettings, refreshInitialConfig)(
|
||||
id,
|
||||
value,
|
||||
);
|
||||
// Recorded for the same reason the pagination toggle records its own
|
||||
// write (#2089): this is what disk holds now, and the `servers` entry it
|
||||
// was edited from will keep describing the previous value for as long as
|
||||
// list reads keep failing. Both settings writers feed the same per-server
|
||||
// write: this is what disk holds now, and the `servers` entry it was
|
||||
// edited from will keep describing the previous value for as long as list
|
||||
// reads keep failing. Both settings writers feed the same per-server
|
||||
// record, so a rollback in either takes the most recent write for that
|
||||
// server, not just the most recent write *of its own kind*.
|
||||
lastPersistedSettings.record(id, value);
|
||||
write.landed(value);
|
||||
},
|
||||
// Surface failures via toast — the modal usually closes
|
||||
// immediately on user dismiss, so a silent fail-on-flush would
|
||||
|
|
|
|||
Loading…
Reference in a new issue