diff --git a/modules/zfs/cmd/zfs-tools/bus.go b/modules/zfs/cmd/zfs-tools/bus.go index 5aabd9049..7d0654061 100644 --- a/modules/zfs/cmd/zfs-tools/bus.go +++ b/modules/zfs/cmd/zfs-tools/bus.go @@ -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}} diff --git a/modules/zfs/cmd/zfs-tools/keeper_test.go b/modules/zfs/cmd/zfs-tools/keeper_test.go index 9c5cbb2cd..fda12c202 100644 --- a/modules/zfs/cmd/zfs-tools/keeper_test.go +++ b/modules/zfs/cmd/zfs-tools/keeper_test.go @@ -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()