v0.261.0 — the controller no longer swaps itself out from under an app update (R-608, R-609)
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
The controller self-updates daily at 04:30 by default, and after any hub report once a floor sits above the box. That swap restarts the controller container. The window proposed for automatic app updates is 02:30-05:00. It contains 04:30. R-608 — a two-way lock, wired in main.go (stacks never imports selfupdate): - stacks.Manager.AnyUpdating() -> Updater.SetAppUpdatingCheck, consulted in the same three places as the existing backupRunning gate. - Updater.IsUpdateRunning -> Manager.SetSelfUpdatingCheck; UpdatePreflight refuses `self_updating`. - MEASURED: the gap was narrower than assumed. The update's `backing-up` phase already takes the backup single-flight, so that one phase was covered. The other six were not, and `starting`/`verifying` are where data may have moved. - The lock must NOT latch: a held app does not block the controller's own updates, including the release that might fix the hold. R-609 — the 409 carries `data.reason`, additively. transient (busy, updating, deploying, migrating, self_updating) vs terminal (held, downgrade). Found while writing the test: the router refuses a HELD app on its own line before the preflight, so `held` would have been the one reason missing. Five red-proofs, each seen to fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -659,6 +659,20 @@ func main() {
|
||||
updater.SetBackupRunningCheck(func() bool {
|
||||
return backupMgr != nil && backupMgr.IsRunning()
|
||||
})
|
||||
// v0.261.0 — the two-way lock between the controller's own update and a guarded app update.
|
||||
// BOTH directions are wired here because this is the only place that holds both objects, and
|
||||
// the dependency must not exist in either package (stacks never imports selfupdate).
|
||||
//
|
||||
// The app-update side is NOT redundant with the backup check above: the update's `backing-up`
|
||||
// phase does take the backup single-flight, but `checking`, `safety-dump`, `pinning`,
|
||||
// `pulling`, `starting` and `verifying` do not — and the last two are where the new version
|
||||
// may already have touched the customer's data.
|
||||
updater.SetAppUpdatingCheck(func() bool {
|
||||
return stackMgr != nil && stackMgr.AnyUpdating()
|
||||
})
|
||||
if stackMgr != nil {
|
||||
stackMgr.SetSelfUpdatingCheck(updater.IsUpdateRunning)
|
||||
}
|
||||
// Check for post-update state (did a previous update succeed or fail?)
|
||||
if state := updater.VerifyStartup(); state != nil {
|
||||
notifier.NotifyControllerUpdated(state.PreviousVersion, state.TargetVersion, state.Status == "success")
|
||||
|
||||
Reference in New Issue
Block a user