[client] Keep a converging map when an older one arrives

The first pass of a new target replaced syncTargetMap before the serial
check in updateNetworkMap ran. An outdated map arriving mid-convergence
took the place of the newer one, was then ignored as stale and reported as
converged, so the newer map's remaining peer batches were never applied
until the next update. A stale map now leaves the pending one in place.
This commit is contained in:
riccardom
2026-10-20 12:24:28 +02:00
parent 4b5c087828
commit 73bb865a8a
2 changed files with 29 additions and 0 deletions
+9
View File
@@ -1075,6 +1075,7 @@ func (e *Engine) applySyncPass(update *mgmProto.SyncResponse, firstPass bool) (b
// when the update carries one. A config-only update leaves syncTargetMap nil. The
// caller holds syncMsgMux.
func (e *Engine) loadSyncTarget(update *mgmProto.SyncResponse) error {
pending := e.syncTargetMap
e.syncTargetMap = nil
e.ApplySessionDeadline(update.GetSessionExpiresAt())
@@ -1158,6 +1159,14 @@ func (e *Engine) loadSyncTarget(update *mgmProto.SyncResponse) error {
e.latestComponents = components
}
// updateNetworkMap ignores a stale map and reports convergence, which would
// strand the remaining peer batches of a newer map still being applied.
if pending != nil && nm.GetSerial() < e.networkSerial {
log.Debugf("ignoring network map with serial %d while serial %d is converging", nm.GetSerial(), e.networkSerial)
e.syncTargetMap = pending
return nil
}
e.syncTargetMap = nm
return nil
}
+20
View File
@@ -1798,3 +1798,23 @@ func TestEngine_ApplySyncPassDecodesOncePerTarget(t *testing.T) {
_, err = e.applySyncPass(undecodable, true)
assert.Error(t, err, "the first pass of a target decodes it")
}
// A map older than the one still converging must not take its place: the
// stale guard in updateNetworkMap would ignore it and report convergence,
// leaving the newer map's remaining peer batches unapplied.
func TestEngine_LoadSyncTargetKeepsNewerPendingMap(t *testing.T) {
pending := &mgmtProto.NetworkMap{Serial: 10}
e := &Engine{
ctx: context.Background(),
syncMsgMux: &sync.Mutex{},
networkSerial: 10,
syncTargetMap: pending,
}
require.NoError(t, e.loadSyncTarget(&mgmtProto.SyncResponse{NetworkMap: &mgmtProto.NetworkMap{Serial: 9}}))
assert.Same(t, pending, e.syncTargetMap, "a stale map should leave the pending newer map in place")
newer := &mgmtProto.NetworkMap{Serial: 11}
require.NoError(t, e.loadSyncTarget(&mgmtProto.SyncResponse{NetworkMap: newer}))
assert.Same(t, newer, e.syncTargetMap, "a newer map should replace the pending one")
}