package accounts // The walk for setuid programs yields to a busy disk (novox/hq issue 433). The walk is put in the idle I/O class // (idle_linux.go), but only a disk scheduler that schedules by class honours that (BFQ does). The control-node's // disks run mq-deadline, which does not, and there the walk read at full priority: on 2026-10-11 it kept two // spinning disks saturated for sixteen minutes, the bus whose store shares those disks stalled for half a minute // twice, and the controller lost its lease each time. So the walk also paces itself by the machine's I/O pressure // (PSI), which reads the same whatever the scheduler: while some task has waited on I/O for more than PaceAbove // of the last ten seconds, the walk pauses before it reads the next directory. // // The walk's own reads count in that pressure, so on a machine where nothing else waits it slows itself to keep // the disk below the bound; that is the yielding the idle class promised. A pause in a row is at most PaceAtMost, // then one more directory is read: on a machine that is never quiet the walk is slow, but it still ends, so a // machine is never left unjudged for good (the fault issue 361 repaired). import ( "bufio" "bytes" "context" "fmt" "log" "os" "strconv" "strings" "time" ) const ( // PaceAbove is the I/O pressure, as the percentage of the last ten seconds in which some task waited on I/O // (PSI's "some avg10"), above which the walk pauses. PaceAbove = 10.0 // PaceLook is how often the walk looks at the pressure while it walks. PaceLook = 250 * time.Millisecond // PacePause is one pause. PacePause = 2 * time.Second // PaceAtMost is the longest the walk pauses in a row before it reads one more directory. PaceAtMost = 30 * time.Second // ioPressureFile is where the kernel says the machine's I/O pressure. ioPressureFile = "/proc/pressure/io" ) // pacer is what the walk asks before it reads a directory. A nil pacer never pauses. type pacer struct { pressure func() (float64, error) // some avg10, in percent sleep func(context.Context, time.Duration) error now func() time.Time logf func(format string, args ...any) lastLook time.Time saidBlind bool } // newPacer paces by the machine's own I/O pressure. func newPacer() *pacer { return &pacer{ pressure: func() (float64, error) { return readIOPressure(ioPressureFile) }, sleep: sleepCtx, now: time.Now, logf: log.Printf, } } // yield pauses while the machine's I/O pressure is above PaceAbove, PacePause at a time and PaceAtMost in a row, // looking at most every PaceLook. It ends early only with ctx, whose error ends the walk as incomplete. Pressure // that cannot be read pauses nothing, and is said once: the walk then yields only where the idle class does. func (pc *pacer) yield(ctx context.Context) error { if pc == nil || pc.pressure == nil { return nil } if !pc.lastLook.IsZero() && pc.now().Sub(pc.lastLook) < PaceLook { return nil } defer func() { pc.lastLook = pc.now() }() for waited := time.Duration(0); waited < PaceAtMost; waited += PacePause { p, err := pc.pressure() if err != nil { if !pc.saidBlind && pc.logf != nil { pc.logf("the walk for setuid programs cannot read the machine's I/O pressure, so it does not pace "+ "itself and yields only where the disk's scheduler honours the idle class: %v", err) } pc.saidBlind = true return nil } if p <= PaceAbove { return nil } if err := pc.sleep(ctx, PacePause); err != nil { return err } } return nil } // readIOPressure is the "some avg10" of a PSI file: the percentage of the last ten seconds in which some task // waited on I/O. func readIOPressure(path string) (float64, error) { b, err := os.ReadFile(path) if err != nil { return 0, err } return parseIOPressure(b) } func parseIOPressure(b []byte) (float64, error) { sc := bufio.NewScanner(bytes.NewReader(b)) for sc.Scan() { fields := strings.Fields(sc.Text()) if len(fields) == 0 || fields[0] != "some" { continue } for _, f := range fields[1:] { if v, ok := strings.CutPrefix(f, "avg10="); ok { p, err := strconv.ParseFloat(v, 64) if err != nil { return 0, fmt.Errorf("the I/O pressure's avg10 is not a number: %q", v) } return p, nil } } } return 0, fmt.Errorf("the I/O pressure names no \"some\" line with avg10") } // sleepCtx waits d, or until ctx ends. func sleepCtx(ctx context.Context, d time.Duration) error { t := time.NewTimer(d) defer t.Stop() select { case <-ctx.Done(): return ctx.Err() case <-t.C: return nil } }