Do not process intermediate one if new ones are fresher just use the freshest

This commit is contained in:
riccardom
2026-06-28 17:20:00 +02:00
parent 297dcb3e24
commit 0bf964dad7
2 changed files with 75 additions and 38 deletions
+47 -9
View File
@@ -42,14 +42,14 @@ func TestMapStateManager_ConvergesThenStops(t *testing.T) {
require.EqualValues(t, 3, atomic.LoadInt32(&passes), "apply must not run after convergence")
}
// every map management sends is reported via onConverged exactly once, in order
// — mirroring the legacy per-message handleSync timing.
func TestMapStateManager_SignalsEveryMap(t *testing.T) {
var converged atomic.Int32
// each map that is actually processed (converged before the next arrives) fires
// onConverged exactly once — mirroring the legacy per-message handleSync timing.
func TestMapStateManager_SignalsEachProcessedMap(t *testing.T) {
converged := make(chan struct{}, 8)
apply := func(*mgmProto.SyncResponse) (bool, error) {
return false, nil // each map converges in one pass
return false, nil // converge in one pass
}
m := newMapStateManager(apply, func(time.Duration) { converged.Add(1) })
m := newMapStateManager(apply, func(time.Duration) { converged <- struct{}{} })
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -58,12 +58,50 @@ func TestMapStateManager_SignalsEveryMap(t *testing.T) {
const maps = 3
for i := 0; i < maps; i++ {
require.NoError(t, m.SetTarget(&mgmProto.SyncResponse{}))
select { // wait for this map to converge before sending the next (no coalescing)
case <-converged:
case <-time.After(2 * time.Second):
t.Fatalf("map %d not signaled", i)
}
}
require.Eventually(t, func() bool { return converged.Load() == maps }, 2*time.Second, 10*time.Millisecond)
// no extra signals once the stream goes quiet
time.Sleep(100 * time.Millisecond)
require.EqualValues(t, maps, converged.Load())
select {
case <-converged:
t.Fatal("unexpected extra onConverged")
case <-time.After(100 * time.Millisecond):
}
}
// a map superseded before it converges is skipped: only the latest (processed) map
// fires onConverged, not the skipped one.
func TestMapStateManager_SkippedMapNotSignaled(t *testing.T) {
release := make(chan struct{})
var applies, converged atomic.Int32
apply := func(*mgmProto.SyncResponse) (bool, error) {
applies.Add(1)
<-release // hold the first apply in-flight so we can queue a newer target
return false, nil
}
m := newMapStateManager(apply, func(time.Duration) { converged.Add(1) })
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go m.run(ctx)
// map1 is picked up; its apply blocks on release
require.NoError(t, m.SetTarget(&mgmProto.SyncResponse{}))
require.Eventually(t, func() bool { return applies.Load() >= 1 }, 2*time.Second, 5*time.Millisecond)
// map2 supersedes map1 before it settled -> map1 is skipped
require.NoError(t, m.SetTarget(&mgmProto.SyncResponse{}))
close(release) // let both applies proceed
// only the processed (latest) map signals; the skipped one does not
require.Eventually(t, func() bool { return converged.Load() == 1 }, 2*time.Second, 10*time.Millisecond)
time.Sleep(150 * time.Millisecond)
require.EqualValues(t, 1, converged.Load(), "skipped map must not fire onConverged")
require.EqualValues(t, 2, applies.Load(), "both targets entered apply (map1 once, map2 once)")
}
// an apply error drops the target: no retry of the same target, no onConverged,