1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
23 // Test that CopyPipeToChildLog works even on lines longer than
24 // bufio.MaxScanTokenSize.
25 func TestCopyPipeToChildLogLongLines(t *testing.T) {
26 logger, logBuf := bufLogger()
28 pipeIn, pipeOut := io.Pipe()
29 copied := make(chan bool)
31 copyPipeToChildLog(pipeIn, logger)
35 sentBytes := make([]byte, bufio.MaxScanTokenSize+MaxLogLine+(1<<22))
37 pipeOut.Write([]byte("before\n"))
39 for i := range sentBytes {
40 // Some bytes that aren't newlines:
41 sentBytes[i] = byte((rand.Int() & 0xff) | 0x80)
43 sentBytes[len(sentBytes)-1] = '\n'
44 pipeOut.Write(sentBytes)
46 pipeOut.Write([]byte("after"))
50 if before, err := logBuf.ReadBytes('\n'); err != nil || string(before) != "before\n" {
51 t.Fatalf("\"before\n\" not received (got \"%s\", %s)", before, err)
54 var receivedBytes []byte
57 line, err := logBuf.ReadBytes('\n')
61 if len(line) >= 5 && string(line[0:5]) == "[...]" {
62 if receivedBytes == nil {
63 t.Fatal("Beginning of line reported as continuation")
67 if len(line) >= 6 && string(line[len(line)-6:]) == "[...]\n" {
68 line = line[:len(line)-6]
72 receivedBytes = append(receivedBytes, line...)
74 if bytes.Compare(receivedBytes, sentBytes) != 0 {
75 t.Fatalf("sent %d bytes, got %d different bytes", len(sentBytes), len(receivedBytes))
78 if after, err := logBuf.ReadBytes('\n'); err != nil || string(after) != "after\n" {
79 t.Fatalf("\"after\n\" not received (got \"%s\", %s)", after, err)
83 case <-time.After(time.Second):
90 func bufLogger() (*log.Logger, *bufio.Reader) {
92 logger := log.New(w, "", 0)
93 return logger, bufio.NewReader(r)
96 func TestSignalOnDeadPPID(t *testing.T) {
97 if !testDeadParent(t, 0) {
98 t.Fatal("child should still be alive after parent dies")
100 if testDeadParent(t, 15) {
101 t.Fatal("child should have been killed when parent died")
105 // testDeadParent returns true if crunchstat's child proc is still
106 // alive after its parent dies.
107 func testDeadParent(t *testing.T, signum int) bool {
109 var bin, childlockfile, parentlockfile *os.File
110 for _, f := range []**os.File{&bin, &childlockfile, &parentlockfile} {
111 *f, err = ioutil.TempFile("", "crunchstat_")
116 defer os.Remove((*f).Name())
120 err = exec.Command("go", "build", "-o", bin.Name()).Run()
125 err = syscall.Flock(int(parentlockfile.Fd()), syscall.LOCK_EX)
130 cmd := exec.Command("bash", "-c", `
132 "$BINFILE" -cgroup-root=/none -ppid-check-interval=10ms -signal-on-dead-ppid="$SIGNUM" bash -c '
135 flock --unlock "$CHILDLOCKFD"
139 flock --exclusive "$CHILDLOCKFD"
140 echo -n "$$" > "$CHILDLOCKFILE"
141 flock --unlock "$PARENTLOCKFD"
142 sleep 20 </dev/null >/dev/null 2>/dev/null &
147 # wait for inner bash to start, to ensure $BINFILE has seen this bash proc as its initial PPID
148 flock --exclusive "$PARENTLOCKFILE" true
150 cmd.Env = append(os.Environ(),
151 "SIGNUM="+fmt.Sprintf("%d", signum),
153 "PARENTLOCKFILE="+parentlockfile.Name(),
155 "CHILDLOCKFILE="+childlockfile.Name(),
156 "BINFILE="+bin.Name())
157 cmd.ExtraFiles = []*os.File{parentlockfile, childlockfile}
158 stderr, err := cmd.StderrPipe()
162 stdout, err := cmd.StdoutPipe()
169 var wg sync.WaitGroup
172 for _, rdr := range []io.ReadCloser{stderr, stdout} {
173 go func(rdr io.ReadCloser) {
175 buf := make([]byte, 1024)
177 n, err := rdr.Read(buf)
179 t.Logf("%s", buf[:n])
188 // Wait until inner bash process releases parentlockfile
189 // (which means it has locked childlockfile and written its
191 err = exec.Command("flock", "--exclusive", parentlockfile.Name(), "true").Run()
196 childDone := make(chan bool)
198 // Notify the main thread when the inner bash process
199 // releases its lock on childlockfile (which means
200 // either its sleep process ended or it received a
203 err = exec.Command("flock", "--exclusive", childlockfile.Name(), "true").Run()
207 t.Logf("child done after %s", time.Since(t0))
212 case <-time.After(500 * time.Millisecond):
213 // Inner bash process is still alive after the timeout
214 // period. Kill it now, so our stdout and stderr pipes
215 // can finish and we don't leave a mess of child procs
217 buf, err := ioutil.ReadFile(childlockfile.Name())
222 _, err = fmt.Sscanf(string(buf), "%d", &childPID)
226 child, err := os.FindProcess(childPID)
230 child.Signal(syscall.Signal(15))
234 // Inner bash process ended soon after its grandparent