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

This commit is contained in:
2026-10-24 20:29:59 +02:00
parent 518a13592d
commit c9b4b82dc7
9 changed files with 178 additions and 1 deletions
+10
View File
@@ -120,6 +120,9 @@ type appStopGuard interface {
// same reason Begin is: the guard owns the "we are holding this app" fact, and a second owner
// would drift from it.
ReleaseFailed(stacks ...string)
// BusyWith (R-1253) names another operation that holds the marker now ("" = none): an export that stops its app
// refuses then, because the marker has one slot and a restore's crash record (its Undo) would be replaced.
BusyWith() string
}
// NewExporter creates a new export/import engine.
@@ -183,6 +186,13 @@ func (e *Exporter) StartExport(req ExportRequest) error {
e.debugf("StartExport rejected: stack %q is not deployed", req.StackName)
return fmt.Errorf("stack %q is not deployed", req.StackName)
}
if req.StopApp && e.stopGuard != nil {
if busy := e.stopGuard.BusyWith(); busy != "" {
e.mu.Unlock()
e.logger.Printf("[WARN] Export of %s refused: %s holds the app-stop marker — an export that stops the app would replace its crash record (R-1253)", req.StackName, busy)
return util.MsgError("err.appexport.another_operation_running")
}
}
e.debugf("StartExport: stack=%q dest=%q password=%v stopApp=%v",
req.StackName, req.DestDrive, req.Password != "", req.StopApp)