Skip to content

Commit 221bc23

Browse files
committed
os/exec: deflake TestPipeLookPathLeak
The number of open file descriptors reported by lsof is unreliable because it depends on whether the parent process (the test) closed the file descriptors it passed into the child process (lsof) before lsof runs. Reading /proc/self/fd directly on Linux appears to be much more reliable and still detects any file descriptor leaks originating from attempting to run an executable that cannot be found (issue #5071). If /proc/self/fd is not available (e.g. on Darwin) then we fall back to lsof and tolerate small differences in open file descriptor counts. Fixes #19243. Change-Id: I052b0c129e609010f1083e43a9911cba154117bf Reviewed-on: https://go-review.googlesource.com/37343 Run-TryBot: Michael Munday <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent fdef951 commit 221bc23

File tree

1 file changed

+57
-30
lines changed

1 file changed

+57
-30
lines changed

src/os/exec/exec_test.go

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,51 @@ func TestStdinCloseRace(t *testing.T) {
289289

290290
// Issue 5071
291291
func TestPipeLookPathLeak(t *testing.T) {
292-
fd0, lsof0 := numOpenFDS(t)
293-
for i := 0; i < 4; i++ {
292+
// If we are reading from /proc/self/fd we (should) get an exact result.
293+
tolerance := 0
294+
295+
// Reading /proc/self/fd is more reliable than calling lsof, so try that
296+
// first.
297+
numOpenFDs := func() (int, []byte, error) {
298+
fds, err := ioutil.ReadDir("/proc/self/fd")
299+
if err != nil {
300+
return 0, nil, err
301+
}
302+
return len(fds), nil, nil
303+
}
304+
want, before, err := numOpenFDs()
305+
if err != nil {
306+
// We encountered a problem reading /proc/self/fd (we might be on
307+
// a platform that doesn't have it). Fall back onto lsof.
308+
t.Logf("using lsof because: %v", err)
309+
numOpenFDs = func() (int, []byte, error) {
310+
// Android's stock lsof does not obey the -p option,
311+
// so extra filtering is needed.
312+
// https://golang.org/issue/10206
313+
if runtime.GOOS == "android" {
314+
// numOpenFDsAndroid handles errors itself and
315+
// might skip or fail the test.
316+
n, lsof := numOpenFDsAndroid(t)
317+
return n, lsof, nil
318+
}
319+
lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
320+
return bytes.Count(lsof, []byte("\n")), lsof, err
321+
}
322+
323+
// lsof may see file descriptors associated with the fork itself,
324+
// so we allow some extra margin if we have to use it.
325+
// https://golang.org/issue/19243
326+
tolerance = 5
327+
328+
// Retry reading the number of open file descriptors.
329+
want, before, err = numOpenFDs()
330+
if err != nil {
331+
t.Log(err)
332+
t.Skipf("skipping test; error finding or running lsof")
333+
}
334+
}
335+
336+
for i := 0; i < 6; i++ {
294337
cmd := exec.Command("something-that-does-not-exist-binary")
295338
cmd.StdoutPipe()
296339
cmd.StderrPipe()
@@ -299,36 +342,20 @@ func TestPipeLookPathLeak(t *testing.T) {
299342
t.Fatal("unexpected success")
300343
}
301344
}
302-
for triesLeft := 3; triesLeft >= 0; triesLeft-- {
303-
open, lsof := numOpenFDS(t)
304-
fdGrowth := open - fd0
305-
if fdGrowth > 2 {
306-
if triesLeft > 0 {
307-
// Work around what appears to be a race with Linux's
308-
// proc filesystem (as used by lsof). It seems to only
309-
// be eventually consistent. Give it awhile to settle.
310-
// See golang.org/issue/7808
311-
time.Sleep(100 * time.Millisecond)
312-
continue
313-
}
314-
t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0)
315-
}
316-
break
317-
}
318-
}
319-
320-
func numOpenFDS(t *testing.T) (n int, lsof []byte) {
321-
if runtime.GOOS == "android" {
322-
// Android's stock lsof does not obey the -p option,
323-
// so extra filtering is needed. (golang.org/issue/10206)
324-
return numOpenFDsAndroid(t)
325-
}
326-
327-
lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
345+
got, after, err := numOpenFDs()
328346
if err != nil {
329-
t.Skip("skipping test; error finding or running lsof")
347+
// numOpenFDs has already succeeded once, it should work here.
348+
t.Errorf("unexpected failure: %v", err)
349+
}
350+
if got-want > tolerance {
351+
t.Errorf("number of open file descriptors changed: got %v, want %v", got, want)
352+
if before != nil {
353+
t.Errorf("before:\n%v\n", before)
354+
}
355+
if after != nil {
356+
t.Errorf("after:\n%v\n", after)
357+
}
330358
}
331-
return bytes.Count(lsof, []byte("\n")), lsof
332359
}
333360

334361
func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) {

0 commit comments

Comments
 (0)