diff options
Diffstat (limited to 'src/syscall/syscall_linux_test.go')
| -rw-r--r-- | src/syscall/syscall_linux_test.go | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/src/syscall/syscall_linux_test.go b/src/syscall/syscall_linux_test.go index d7543ceb4b..43c0ba0ce3 100644 --- a/src/syscall/syscall_linux_test.go +++ b/src/syscall/syscall_linux_test.go @@ -5,6 +5,7 @@ package syscall_test import ( + "context" "fmt" "internal/testenv" "io" @@ -720,3 +721,197 @@ func TestPrlimitOtherProcess(t *testing.T) { t.Fatalf("origRlimitNofile got=%v, want=%v", rlimLater, rlimOrig) } } + +const magicRlimitValue = 42 + +// TestPrlimitFileLimit tests that we can start a Go program, use +// prlimit to change its NOFILE limit, and have that updated limit be +// seen by children. See issue #66797. +func TestPrlimitFileLimit(t *testing.T) { + switch os.Getenv("GO_WANT_HELPER_PROCESS") { + case "prlimit1": + testPrlimitFileLimitHelper1(t) + return + case "prlimit2": + testPrlimitFileLimitHelper2(t) + return + } + + origRlimitNofile := syscall.GetInternalOrigRlimitNofile() + defer origRlimitNofile.Store(origRlimitNofile.Load()) + + // Set our rlimit to magic+1/max. + // That will also become the rlimit of the child. + + var lim syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatal(err) + } + max := lim.Max + + lim = syscall.Rlimit{ + Cur: magicRlimitValue + 1, + Max: max, + } + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + + r1, w1, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r1.Close() + defer w1.Close() + + r2, w2, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r2.Close() + defer w2.Close() + + var output strings.Builder + + const arg = "-test.run=^TestPrlimitFileLimit$" + cmd := testenv.CommandContext(t, ctx, exe, arg, "-test.v") + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit1") + cmd.ExtraFiles = []*os.File{r1, w2} + cmd.Stdout = &output + cmd.Stderr = &output + + t.Logf("running %s %s", exe, arg) + + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + // Wait for the child to start. + b := make([]byte, 1) + if n, err := r2.Read(b); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatalf("read %d bytes, want 1", n) + } + + // Set the child's prlimit. + lim = syscall.Rlimit{ + Cur: magicRlimitValue, + Max: max, + } + if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, &lim, nil); err != nil { + t.Fatalf("Prlimit failed: %v", err) + } + + // Tell the child to continue. + if n, err := w1.Write(b); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatalf("wrote %d bytes, want 1", n) + } + + err = cmd.Wait() + if output.Len() > 0 { + t.Logf("%s", output.String()) + } + + if err != nil { + t.Errorf("child failed: %v", err) + } +} + +// testPrlimitFileLimitHelper1 is run by TestPrlimitFileLimit. +func testPrlimitFileLimitHelper1(t *testing.T) { + var lim syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatal(err) + } + t.Logf("helper1 rlimit is %v", lim) + t.Logf("helper1 cached rlimit is %v", syscall.OrigRlimitNofile()) + + // Tell the parent that we are ready. + b := []byte{0} + if n, err := syscall.Write(4, b); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatalf("wrote %d bytes, want 1", n) + } + + // Wait for the parent to tell us that prlimit was used. + if n, err := syscall.Read(3, b); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatalf("read %d bytes, want 1", n) + } + + if err := syscall.Close(3); err != nil { + t.Errorf("Close(3): %v", err) + } + if err := syscall.Close(4); err != nil { + t.Errorf("Close(4): %v", err) + } + + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatal(err) + } + t.Logf("after prlimit helper1 rlimit is %v", lim) + t.Logf("after prlimit helper1 cached rlimit is %v", syscall.OrigRlimitNofile()) + + // Start the grandchild, which should see the rlimit + // set by the prlimit called by the parent. + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + + const arg = "-test.run=^TestPrlimitFileLimit$" + cmd := testenv.CommandContext(t, ctx, exe, arg, "-test.v") + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit2") + t.Logf("running %s %s", exe, arg) + out, err := cmd.CombinedOutput() + if len(out) > 0 { + t.Logf("%s", out) + } + if err != nil { + t.Errorf("grandchild failed: %v", err) + } else { + fmt.Println("OK") + } +} + +// testPrlimitFileLimitHelper2 is run by testPrlimitFileLimit1. +func testPrlimitFileLimitHelper2(t *testing.T) { + var lim syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { + t.Fatal(err) + } + + t.Logf("helper2 rlimit is %v", lim) + cached := syscall.OrigRlimitNofile() + t.Logf("helper2 cached rlimit is %v", cached) + + // The value return by Getrlimit will have been adjusted. + // We should have cached the value set by prlimit called by the parent. + + if cached == nil { + t.Fatal("no cached rlimit") + } else if cached.Cur != magicRlimitValue { + t.Fatalf("cached rlimit is %d, want %d", cached.Cur, magicRlimitValue) + } + + fmt.Println("OK") +} |
