pacman starts a round's run only once the node's file holds the round's mirror list, and a refresh never writes after a round opened (ADR 0585 §2, §4; review round 1)
mesh/delivery-group group feat/node-packages rejected: composed together: the checker's verdict at the members' heads (98ce55efa892): unit: i3/cmd/i3-tools.Tes…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: i3/cmd/i3-tools.TestAtItsStartTheWatcherReloadsWhatTheRunningI3HasNotLoaded: loaded map[/tmp/TestAtItsStartTheWatcherReloadsW…
mesh/delivery-group group feat/node-packages rejected: composed together: the checker's verdict at the members' heads (98ce55efa892): unit: i3/cmd/i3-tools.Tes…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: i3/cmd/i3-tools.TestAtItsStartTheWatcherReloadsWhatTheRunningI3HasNotLoaded: loaded map[/tmp/TestAtItsStartTheWatcherReloadsW…
The opener ranked the list and took its turn in the same step, so its upgrade started before the keeper's change hook wrote /etc/pacman.d/mirrorlist, and a failed write on any node was only logged. A round's run now writes the list first and checks the file holds the revision the round opened with; otherwise the run is refused with why and tried again an hour later, as every refused run is. A refresh asks again for a running round right before its write, since ranking takes minutes. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -26,12 +26,15 @@ type countingRoot struct {
|
||||
mu sync.Mutex
|
||||
started []string
|
||||
mirrors []string
|
||||
// mirrorsAtStart is the mirror list the node's file held when each transaction started (ADR 0585 §2).
|
||||
mirrorsAtStart [][]string
|
||||
}
|
||||
|
||||
func (r *countingRoot) Transaction(kind, run string, ignore []string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.started = append(r.started, kind+" "+run)
|
||||
r.mirrorsAtStart = append(r.mirrorsAtStart, append([]string(nil), r.mirrors...))
|
||||
return nil
|
||||
}
|
||||
func (r *countingRoot) Rollback(string, []RollbackItem) error { return nil }
|
||||
@@ -83,16 +86,20 @@ func startRoundNode(t *testing.T, ctx context.Context, url, node string, order [
|
||||
m := machine(run, 1000)
|
||||
began := time.Now()
|
||||
m.Now = func() time.Time { return t0.Add(time.Since(began)) }
|
||||
n := &roundNode{root: &countingRoot{}, signals: make(chan Signal)}
|
||||
m.ReadFile = func(path string) ([]byte, error) {
|
||||
switch path {
|
||||
case OSRelease:
|
||||
return []byte("7.2.8-arch1-2\n"), nil
|
||||
case ReflectorConf:
|
||||
return []byte("--sort rate\n"), nil
|
||||
case Mirrorlist: // the file holds what the root helper last wrote
|
||||
if l := n.root.mirrorlist(); l != nil {
|
||||
return RenderMirrorlist(l), nil
|
||||
}
|
||||
}
|
||||
return nil, errNoFile
|
||||
}
|
||||
n := &roundNode{root: &countingRoot{}, signals: make(chan Signal)}
|
||||
k := &Keeper{Config: KeeperConfig{UpgradeOrder: order, RecordDays: 180, RoundStartMinutes: 120}, Machine: m, Clock: realClock{},
|
||||
Watcher: fakeWatcher{n.signals}, Logf: func(f string, a ...any) { t.Logf(node+": "+f, a...) }, MakeHooks: keeperHooks,
|
||||
Root: n.root, Check: func(Run, *Record) Check { return Check{Passed: passes} },
|
||||
@@ -192,6 +199,13 @@ func TestTheRoundGoesNodeByNodeOnARealBus(t *testing.T) {
|
||||
eventually(t, "each node wrote the mesh's mirror list", 10*time.Second, func() bool {
|
||||
return strings.Join(n.root.mirrorlist(), " ") == strings.Join(roundMirrors, " ")
|
||||
})
|
||||
// And each started its run only once its file held the round's list, the opener too.
|
||||
n.root.mu.Lock()
|
||||
at := n.root.mirrorsAtStart
|
||||
n.root.mu.Unlock()
|
||||
if len(at) != 1 || strings.Join(at[0], " ") != strings.Join(roundMirrors, " ") {
|
||||
t.Errorf("a node's run started with the mirror list file holding %v, want the round's %v", at, roundMirrors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user