Skip to content

Commit e99dbce

Browse files
committed
review: fact-gathering commands get a deadline; a hung probe cannot stall a campaign
1 parent ac7a47e commit e99dbce

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

runner/internal/bundle/metadata.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package bundle
22

33
import (
44
"bufio"
5+
"context"
56
"encoding/json"
67
"fmt"
78
"io"
@@ -238,10 +239,21 @@ func memTotalKB() int64 {
238239
return 0
239240
}
240241

242+
// factTimeout bounds every fact-gathering command. A probe that hangs — lsblk on
243+
// a wedged device, a binary whose `version` never returns — must not cost an
244+
// unattended campaign its start, so a fact that takes too long is treated the
245+
// same as one that cannot be gathered. A var so tests can shrink it.
246+
var factTimeout = 5 * time.Second
247+
241248
// commandOutput runs a fact-gathering command and returns its trimmed stdout,
242-
// or "" if it cannot be run. Every caller here is best-effort by contract.
249+
// or "" if it cannot be run or does not finish inside factTimeout. Every caller
250+
// here is best-effort by contract.
243251
func commandOutput(name string, args ...string) string {
244-
out, err := exec.Command(name, args...).Output()
252+
ctx, cancel := context.WithTimeout(context.Background(), factTimeout)
253+
defer cancel()
254+
cmd := exec.CommandContext(ctx, name, args...)
255+
cmd.WaitDelay = time.Second
256+
out, err := cmd.Output()
245257
if err != nil {
246258
return ""
247259
}

runner/internal/bundle/metadata_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,19 @@ func TestCollectHardwareOffEC2(t *testing.T) {
255255
}
256256
}
257257

258+
func TestCommandOutputTimesOut(t *testing.T) {
259+
old := factTimeout
260+
factTimeout = 100 * time.Millisecond
261+
t.Cleanup(func() { factTimeout = old })
262+
start := time.Now()
263+
if got := commandOutput("sleep", "30"); got != "" {
264+
t.Errorf("commandOutput = %q, want \"\" on timeout", got)
265+
}
266+
if elapsed := time.Since(start); elapsed > 5*time.Second {
267+
t.Errorf("commandOutput took %s, want well under the 30s sleep", elapsed)
268+
}
269+
}
270+
258271
func TestWriteMetadataUnwritableDir(t *testing.T) {
259272
err := WriteMetadata(filepath.Join(t.TempDir(), "nope"), goldenInput(t))
260273
if err == nil {

runner/internal/bundle/provenance.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bundle
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"os/exec"
@@ -158,9 +159,14 @@ func fsyncProbe(benchRoot string) string {
158159

159160
// binaryVersion is the benchmarked binary's own version output, first 3 lines,
160161
// stdout and stderr together — the binary reports its build stamp there. A
161-
// binary that cannot run says so in place of its version.
162+
// binary that cannot run, or does not answer inside factTimeout, says so in
163+
// place of its version.
162164
func binaryVersion(binPath string) []string {
163-
out, err := exec.Command(binPath, "version").CombinedOutput()
165+
ctx, cancel := context.WithTimeout(context.Background(), factTimeout)
166+
defer cancel()
167+
cmd := exec.CommandContext(ctx, binPath, "version")
168+
cmd.WaitDelay = time.Second
169+
out, err := cmd.CombinedOutput()
164170
lines := splitLines(head(strings.TrimRight(string(out), "\n"), 3))
165171
if err != nil && len(lines) == 0 {
166172
return []string{fmt.Sprintf("version: %v", err)}

0 commit comments

Comments
 (0)