carlos / platform Public

Merge pull request #362 from carlosframework/move-serial-nudge

moves: serial nudges — cut chain in seconds (#331 rehearsal measurement)
Paul Campbell pushed by paul@keymail.dev 223fc5669f7b69f75732e4a6b50197158e7cecc8 parent ae64ea5parent 3d24a10
1 file changed, +29 −1
  • internal/host/movespass.go +29 −1
diff --git a/internal/host/movespass.go b/internal/host/movespass.go
index 1772374c5..f61190ba9 100644
--- a/internal/host/movespass.go
+++ b/internal/host/movespass.go
@@ -216,6 +216,7 @@ func runMoveRound(ctx context.Context, s release.Store, cfg moveCfg, rec provisi
if err != nil && !isMoveConflict(err) {
return err
}
+ nudgeSerial(ctx, s)
log.Printf("moves: %s: staged replica caught up in %s/%s — ready to cut", rec.Host, tgt.DataBucket, dstPrefix)
}
return nil
@@ -237,6 +238,7 @@ func runMoveRound(ctx context.Context, s release.Store, cfg moveCfg, rec provisi
if err != nil && !isMoveConflict(err) {
return err
}
+ nudgeSerial(ctx, s)
log.Printf("moves: %s: abort complete — staged copy deleted, instance stays in %q", rec.Host, rec.Region)
return nil
@@ -247,7 +249,10 @@ func runMoveRound(ctx context.Context, s release.Store, cfg moveCfg, rec provisi
return fmt.Errorf("set route status: %w", err)
}
if !cfg.Quiesced(rec.Host) {
- return nil // the retire is in flight; next pass re-enters here
+ if _, done := movingStatusNudged.LoadOrStore(rec.Host, true); !done {
+ nudgeSerial(ctx, s)
+ }
+ return nil // the retire is in flight; the nudged pass re-enters here
}
// Step 3: prove the own replica carries the tail. The activator's
// stop SWALLOWS a failed final checkpoint (safe pre-move: the warm
@@ -274,12 +279,35 @@ func runMoveRound(ctx context.Context, s release.Store, cfg moveCfg, rec provisi
if err != nil && !isMoveConflict(err) {
return err
}
+ movingStatusNudged.Delete(rec.Host)
+ // The flip's nudge is what turns the TARGET's mint from a
+ // 5-minute wait into a seconds-cadence converge.
+ nudgeSerial(ctx, s)
log.Printf("moves: %s: cut complete — now homed in %s (route retires on the next reconcile pass)", rec.Host, rec.Move.To)
return nil
}
return nil
}
+// nudgeSerial bumps control/serial after a box-side move write, so the
+// NEXT actor — this box's own cut continuation, the target's mint, the
+// tenant's poll — reacts on the fast-converge path instead of the 5-minute
+// floor. The live rehearsal measured that floor three times over in one
+// cut (status→quiesce→flip 331s, target mint+wake 700s); with the nudge,
+// each hop is a serial-triggered pass. Best-effort like the console's own
+// bumpSerial: a failed bump costs cadence, never correctness.
+func nudgeSerial(ctx context.Context, s release.Store) {
+ if err := release.BumpSerial(ctx, s, time.Now()); err != nil {
+ log.Printf("moves: serial nudge: %v (falling back to the timer floor)", err)
+ }
+}
+
+// movingStatusNudged remembers which hosts this process already nudged for
+// the status→quiesce hop, so a child that refuses to die does not turn the
+// fast-converge loop into a 2s self-triggering cycle: ONE nudge per host
+// per process, then the 5-minute floor stands under a wedged stop.
+var movingStatusNudged sync.Map
+
// logMoveTrouble rate-limits per-host move errors to one line a minute.
var moveTroubleLast sync.Map // host → time.Time