zfs: a follower reaches the keeper's socket on every call, so a keeper up later is used (A-37, reviewer's note)

Builds: design 42 zfs-owns-zfs A-37
This commit is contained in:
2026-11-13 01:29:15 +01:00
parent 5f372ac63a
commit e79392d666
2 changed files with 27 additions and 8 deletions
+3 -6
View File
@@ -36,7 +36,7 @@ func (b busStates) Get(state, key string) (json.RawMessage, bool, error) {
}
// JoinBus gives a root process the module's events and states through the keeper's socket (A-37): the process holds no
// bus account of its own. Without the socket the machine keeps none, and what it changes is said as a warning (§18:
// bus account of its own. Without the socket variable the machine keeps none, and what it changes is said as a warning (§18:
// with the bus down the apply converges from the config file it last got). It answers how to leave the bus.
func (m *Machine) JoinBus() func() {
sock := strings.TrimSpace(os.Getenv(KeeperSocketEnv))
@@ -44,12 +44,9 @@ func (m *Machine) JoinBus() func() {
m.Warnings = append(m.Warnings, KeeperSocketEnv+" is not set, so the keeper is not reached and the bus is taken as down")
return func() {}
}
// Every call reaches the socket anew, so a keeper that comes up after this process is used from then on; one that
// is down at a call answers that call with an error, never the bus taken as down for the process's life.
c := keeperClient{sock}
// The keeper answering is what the bus being up means: one call says it before the process relies on it.
if _, err := c.Keys(StateSchedule); err != nil {
m.Warnings = append(m.Warnings, err.Error()+": the bus is taken as down")
return func() {}
}
m.Emit = c.emit
m.States = c
m.Findings = meshconditionsclient.FindingWriter{State: findingState{c}}
+24 -2
View File
@@ -106,14 +106,36 @@ func TestOnlyRootOpensTheKeepersSocketAndANeedlessKeeperIsTheBusDown(t *testing.
if _, left := st["schedule/x"]; left {
t.Errorf("a refused peer's call was read")
}
// No keeper: the machine keeps no bus and says so.
t.Setenv(KeeperSocketEnv, filepath.Join(dir, "none.sock"))
// No socket variable: the machine keeps no bus and says so.
t.Setenv(KeeperSocketEnv, "")
m := newFake(t).machine()
m.Emit = nil
m.JoinBus()
if m.Emit != nil || m.States != nil || len(m.Warnings) != 1 || !strings.Contains(m.Warnings[0], "bus is taken as down") {
t.Errorf("no keeper: %+v", m.Warnings)
}
// A keeper not yet up: the process joins, its calls fail, and the same process uses the keeper once it is up.
late := filepath.Join(dir, "late.sock")
t.Setenv(KeeperSocketEnv, late)
ml := newFake(t).machine()
ml.JoinBus()
if ml.States == nil {
t.Fatalf("a keeper not up yet must not take the bus as down: %+v", ml.Warnings)
}
if _, err := ml.States.Keys(StateSchedule); err == nil {
t.Errorf("no keeper up, yet a call answered")
}
ll, err := Listen(late)
if err != nil {
t.Fatal(err)
}
lctx, lcancel := context.WithCancel(context.Background())
defer lcancel()
go (&Keeper{Emit: func(string, any) error { return nil }, States: memStates{}, PeerOf: func(net.Conn) (int, error) { return 0, nil },
Call: func(string, any) (json.RawMessage, error) { return nil, nil }}).Serve(lctx, ll)
if _, err := ml.States.Keys(StateSchedule); err != nil {
t.Errorf("a keeper that came up later is not used: %v", err)
}
t.Setenv(KeeperSocketEnv, sock)
uid = 0
m2 := newFake(t).machine()