From 7b2601a02ef97ac6bf40ac4adffadd925e75c945 Mon Sep 17 00:00:00 2001 From: flamingcow Date: Tue, 4 Aug 2026 22:26:07 -0700 Subject: [PATCH] Fail loudly on a touch read, stop the workers before their sockets close, and file down the small edges --- fb.go | 20 ++++++++------------ input.go | 11 ++++++++--- main.go | 16 ++++++++++++++-- probe.go | 7 +++++-- render.go | 4 +++- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/fb.go b/fb.go index d3f4a08..94e626a 100644 --- a/fb.go +++ b/fb.go @@ -170,23 +170,24 @@ func findDisplay(fd int) (connID, crtcID uint32, err error) { } // The card number is not stable across machines, so the card driving a -// connected display is the one we want. -func openCard() (int, error) { +// connected display is the one we want, and the display it found comes back +// with it. +func openCard() (fd int, connID, crtcID uint32, err error) { paths, err := filepath.Glob("/dev/dri/card*") if err != nil { - return -1, err + return -1, 0, 0, err } for _, p := range paths { fd, err := unix.Open(p, unix.O_RDWR|unix.O_CLOEXEC, 0) if err != nil { continue } - if _, _, err := findDisplay(fd); err == nil { - return fd, nil + if connID, crtcID, err := findDisplay(fd); err == nil { + return fd, connID, crtcID, nil } unix.Close(fd) } - return -1, fmt.Errorf("no drm device with a connected display") + return -1, 0, 0, fmt.Errorf("no drm device with a connected display") } func (fb *framebuffer) addScanout(i int) error { @@ -223,7 +224,7 @@ func (fb *framebuffer) addScanout(i int) error { } func openFramebuffer() (*framebuffer, error) { - fd, err := openCard() + fd, connID, crtcID, err := openCard() if err != nil { return nil, err } @@ -236,11 +237,6 @@ func openFramebuffer() (*framebuffer, error) { return nil, fmt.Errorf("take drm master: %w", err) } - connID, crtcID, err := findDisplay(fd) - if err != nil { - fb.close() - return nil, err - } mode, err := preferredMode(fd, connID) if err != nil { fb.close() diff --git a/input.go b/input.go index eb421fe..b2992e1 100644 --- a/input.go +++ b/input.go @@ -119,8 +119,11 @@ func watchTouch(w, h int) (*touchState, error) { var down bool for { n, err := f.Read(buf) + // Nothing else feeds this state, so returning here would freeze the + // last touch in place and leave the reset button dead while the panel + // goes on looking alive. if err != nil { - return + panic(fmt.Sprintf("reading touchscreen events: %v", err)) } for o := 0; o+sizeofInputEvent <= n; o += sizeofInputEvent { typ := *(*uint16)(unsafe.Pointer(&buf[o+16])) @@ -142,9 +145,11 @@ func watchTouch(w, h int) (*touchState, error) { if code != synReport { break } + // Scaled onto [0, w-1], so full deflection is the last pixel + // rather than one past it. state.set( - int(int64(rawX-minX)*int64(w)/int64(maxX-minX)), - int(int64(rawY-minY)*int64(h)/int64(maxY-minY)), + int(int64(rawX-minX)*int64(w-1)/int64(maxX-minX)), + int(int64(rawY-minY)*int64(h-1)/int64(maxY-minY)), down) } } diff --git a/main.go b/main.go index 64a0edc..2ff3134 100644 --- a/main.go +++ b/main.go @@ -640,6 +640,20 @@ func run(aName, bName string, nsPerM float64) error { defer wg.Done() samp.run(&done, startTx) }() + // Every return from here on stops the workers before the deferred closes + // pull their sockets out from under them: otherwise the sampler panics on a + // closed fd and can mask the error that actually ended the run. An error + // before the gate opens closes it here, or the wait would hang on + // goroutines still parked at startTx. + defer func() { + done.Store(true) + select { + case <-startTx: + default: + close(startTx) + } + wg.Wait() + }() rxReady.Wait() sig := make(chan os.Signal, 1) @@ -674,8 +688,6 @@ func run(aName, bName string, nsPerM float64) error { for { select { case <-sig: - done.Store(true) - wg.Wait() return nil case <-space: start = resetAll(dirs, stats) diff --git a/probe.go b/probe.go index b84837e..2a8446e 100644 --- a/probe.go +++ b/probe.go @@ -170,10 +170,13 @@ func (p *probeSender) awaitTx(scratch, oob []byte) (int64, bool) { fds := []unix.PollFd{{Fd: int32(p.fd), Events: unix.POLLERR}} deadline := time.Now().Add(probeTimeout) for { - ms := int(time.Until(deadline).Milliseconds()) - if ms <= 0 { + left := time.Until(deadline) + if left <= 0 { return 0, false } + // Rounded up, since truncating would give up with time still on the + // clock and shave the last fraction of a millisecond off every wait. + ms := int((left + time.Millisecond - 1) / time.Millisecond) n, err := unix.Poll(fds, ms) if err == unix.EINTR { continue diff --git a/render.go b/render.go index 0ea3acb..f0d5262 100644 --- a/render.go +++ b/render.go @@ -75,8 +75,10 @@ func commas(v uint64) string { // on the label. Below a thousand no letter is left dangling, since a trailing // space would push the figure off centre. func scaleSI(v float64) string { + // %.2f rounds 999.995 and up to a fourth digit, so the magnitude rolls over + // where the rounding does rather than at the bare thousand. for _, mag := range []string{"", "k", "M", "G", "T"} { - if v < 1000 { + if v < 999.995 { if mag == "" { return fmt.Sprintf("%.2f", v) }