space: the diff's clock runs inside sudo, so zfs is killed too

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-23 01:19:19 -07:00
co-authored by Claude Fable 5.1
parent 314a021a3e
commit ad88a48c01
2 changed files with 16 additions and 11 deletions
+10 -7
View File
@@ -571,16 +571,19 @@ func newestSnapshot(ctx context.Context, dataset string) string {
// zfsDiff lists what changed in a dataset since a snapshot: every changed, added, removed
// or renamed path, with the directory whose listing that touches.
func zfsDiff(ctx context.Context, snapshot, mount string) ([]change, error) {
// a diff slower than this is not worth waiting for: the walk by mtime is bounded
ctx, cancel := context.WithTimeout(ctx, 4*time.Minute)
// A diff slower than this is not worth waiting for: the walk by mtime is bounded. The
// clock runs inside sudo — `timeout` is zfs's parent and kills it as root — because sudo
// on Debian gives the command a session of its own, out of reach of our process group;
// the context is only a backstop.
const patience = 3 * time.Minute
ctx, cancel := context.WithTimeout(ctx, patience+30*time.Second)
defer cancel()
args := priv.Wrap("zfs", "diff", "-H", "-F", snapshot)
args := priv.Wrap("timeout", "-s", "KILL", strconv.Itoa(int(patience.Seconds())), "zfs", "diff", "-H", "-F", snapshot)
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
// on timeout the whole process group goes, not just sudo: zfs itself would otherwise run
// on holding the pipe, and Wait would sit there with it
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error { return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) }
cmd.WaitDelay = 5 * time.Second
t0 := time.Now()
var stderr strings.Builder
cmd.Stderr = &stderr
out, err := cmd.StdoutPipe()
@@ -609,8 +612,8 @@ func zfsDiff(ctx context.Context, snapshot, mount string) ([]change, error) {
}
if err := cmd.Wait(); err != nil {
msg := strings.TrimSpace(stderr.String())
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
msg = "gave up after 4 min"
if errors.Is(ctx.Err(), context.DeadlineExceeded) || time.Since(t0) >= patience {
msg = "gave up after " + patience.String()
}
if msg == "" {
msg = err.Error()