//go:build linux package hostexec import ( "context" "encoding/json" "fmt" "os" "path/filepath" "testing" "golang.org/x/sys/unix" ) func (d *brokerDisk) HoldLive(name string) (func(), error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } lock := unix.Flock_t{Type: unix.F_WRLCK} if err := unix.FcntlFlock(f.Fd(), unix.F_OFD_SETLK, &lock); err != nil { f.Close() return nil, err } return func() { f.Close() }, nil } func (d *brokerDisk) LiveHeld(name string) (bool, error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_RDWR, 0) if err != nil { return false, err } defer f.Close() lock := unix.Flock_t{Type: unix.F_RDLCK} err = unix.FcntlFlock(f.Fd(), unix.F_OFD_GETLK, &lock) return lock.Type == unix.F_WRLCK && lock.Pid == -1, err } type pausedProbe struct { *brokerDisk entered, resume chan struct{} mutate bool } func (d *pausedProbe) LiveHeld(name string) (bool, error) { if d.mutate { unlock, err := d.Lock(context.Background(), name) if err != nil { return false, err } close(d.entered) <-d.resume unlock() return false, nil } held, err := d.brokerDisk.LiveHeld(name) close(d.entered) <-d.resume return held, err } type falseBrokerProbe struct{ *brokerDisk } func (d falseBrokerProbe) LiveHeld(name string) (bool, error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_RDWR, 0) if err != nil { return false, err } defer f.Close() err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) if err == unix.EWOULDBLOCK { return true, nil } return false, err } func TestPlan14ConcurrentReadersNeverGrantLiveness(t *testing.T) { for _, mutate := range []bool{false, true} { t.Run(fmt.Sprint(mutate), func(t *testing.T) { b, d, _, clock, q, oldPeer := brokerFixture(t) peer, err := currentControllerProcess() if err != nil { t.Fatal(err) } b.cfg.BootID = peer.KernelBootID original := b.cfg.Authority b.cfg.Authority = func(ctx context.Context, q ExecutionRequest, _ ControllerProcess) (ExecutionAuthority, error) { return original(ctx, q, oldPeer) } if _, err = b.Execute(t.Context(), peer, q); err != nil { t.Fatal(err) } census, _ := json.Marshal(map[string]any{"Schema": 1, "Box": "box", "Enrollment": "en", "Migration": 1, "Revision": 1, "LaunchRevision": 1, "Complete": true}) if err = d.Write("census.json", census); err != nil { t.Fatal(err) } if _, err = ReadRoot(t.Context(), d, clock, q, false); err != nil { t.Fatal(err) } raw, err := d.Read(q.Host + ".json") if err != nil { t.Fatal(err) } r, err := b.read(q.Host) if err != nil { t.Fatal(err) } d.failAt = d.writes + 1 if err = b.deny(t.Context(), r, ErrLost); err == nil { t.Fatal("denial succeeded") } if err = os.WriteFile(filepath.Join(d.dir, q.Host+".json"), raw, 0600); err != nil { t.Fatal(err) } a := &pausedProbe{brokerDisk: d, entered: make(chan struct{}), resume: make(chan struct{}), mutate: mutate} done := make(chan error, 1) go func() { _, err := ReadRoot(t.Context(), a, clock, q, false); done <- err }() <-a.entered var readErr error if mutate { _, readErr = ReadRoot(t.Context(), falseBrokerProbe{d}, clock, q, false) } else { _, readErr = ReadRoot(t.Context(), d, clock, q, false) } close(a.resume) if err := <-done; err == nil { t.Fatal("reader A admitted") } if (readErr == nil) != mutate { t.Fatalf("mutation=%v B error=%v", mutate, readErr) } }) } }