R-1253 (P2): an export that stops its app refuses while another operation (a restore) holds the app-stop marker — its Undo is never replaced. Commit only.
gates / gates (push) Successful in 1m28s
gates / gates (push) Successful in 1m28s
This commit is contained in:
@@ -138,6 +138,11 @@ type AppStopGuard struct {
|
||||
holds AppStopHolds
|
||||
undoReachable func(stack, dir string) bool
|
||||
|
||||
// liveOp (R-1253) is the operation of THIS process that holds the marker now (set by Begin, cleared by End). The
|
||||
// marker has one slot; BeginExclusive refuses to replace a live one. A marker left by a crash has no live op.
|
||||
liveMu sync.Mutex
|
||||
liveOp string
|
||||
|
||||
// suppressed (R-330) is the in-memory "the alarm must not fire for these, we are holding them"
|
||||
// set. It is deliberately NOT persisted: after a restart the guard no longer holds anything —
|
||||
// Recover() either brings the apps back or leaves them genuinely down, and a down app must alarm.
|
||||
@@ -315,6 +320,9 @@ func (g *AppStopGuard) Begin(opID string, reason AppStopReason, stackNames []str
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
g.liveMu.Lock()
|
||||
g.liveOp = opID
|
||||
g.liveMu.Unlock()
|
||||
// R-330: the marker is on disk, so the caller is now cleared to stop these apps — which is
|
||||
// exactly the moment the app-down alarm must stop counting them. Marked AFTER the write, so a
|
||||
// Begin that failed (and therefore stopped nothing) suppresses nothing either.
|
||||
@@ -335,6 +343,9 @@ func (g *AppStopGuard) End() {
|
||||
// still owes its suppressed stacks a release — otherwise an unwired-path guard would hold them
|
||||
// until appStopMaxHold.
|
||||
g.releaseStarted()
|
||||
g.liveMu.Lock()
|
||||
g.liveOp = ""
|
||||
g.liveMu.Unlock()
|
||||
if g.path == "" {
|
||||
return
|
||||
}
|
||||
@@ -343,6 +354,35 @@ func (g *AppStopGuard) End() {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrAppStopBusy (R-1253): another operation of this process holds the app-stop marker; replacing it would lose that
|
||||
// operation's crash record (a restore's Undo among it).
|
||||
var ErrAppStopBusy = errors.New("another operation holds the app-stop marker")
|
||||
|
||||
// BusyWith names the operation of THIS process that holds the marker now ("" = none). A marker a crash left is not
|
||||
// busy: nothing in this process will End it, and the next operation may replace it (Recover already ran).
|
||||
func (g *AppStopGuard) BusyWith() string {
|
||||
if g == nil {
|
||||
return ""
|
||||
}
|
||||
g.liveMu.Lock()
|
||||
defer g.liveMu.Unlock()
|
||||
return g.liveOp
|
||||
}
|
||||
|
||||
// BeginExclusive is Begin that refuses while another operation of this process holds the marker (R-1253: an export
|
||||
// with „stop the app first" during another app's restore replaced the restore's record and its Undo). Used by the
|
||||
// exporter; the backup manager's own operations are already serialised by its running flag.
|
||||
func (g *AppStopGuard) BeginExclusive(opID string, reason AppStopReason, stackNames []string) error {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
if busy := g.BusyWith(); busy != "" && busy != opID {
|
||||
g.logger.Printf("[WARN] [appstop] %s refused: %s holds the app-stop marker (R-1253)", opID, busy)
|
||||
return fmt.Errorf("%w: %s", ErrAppStopBusy, busy)
|
||||
}
|
||||
return g.Begin(opID, reason, stackNames)
|
||||
}
|
||||
|
||||
// Recover restarts any apps left stopped by an operation that died before restarting them, then
|
||||
// clears the marker. Call ONCE at startup, and — critically — call it to COMPLETION before the boot
|
||||
// reconciler is launched, so an app this marker explains is not also reported as an unexplained boot
|
||||
|
||||
Reference in New Issue
Block a user