1 // Copyright 2019 The SwiftShader Authors. All Rights Reserved.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // Package shell provides functions for running sub-processes.
32 // MaxProcMemory is the maximum virtual memory per child process
33 var MaxProcMemory uint64 = 2 * 1024 * 1024 * 1024 // 2GB
36 // As we are going to be running a number of tests concurrently, we need to
37 // limit the amount of virtual memory each test uses, otherwise memory
38 // hungry tests can bring the whole system down into a swapping apocalypse.
40 // Linux has the setrlimit() function to limit a process (and child's)
41 // virtual memory usage - but we cannot call this from the regres process
42 // as this process may need more memory than the limit allows.
44 // Unfortunately golang has no native support for setting rlimits for child
45 // processes (https://github.com/golang/go/issues/6603), so we instead wrap
46 // the exec to the test executable with another child regres process using a
47 // special --exec mode:
49 // [regres] -> [regres --exec <test-exe N args...>] -> [test-exe]
51 // (calls rlimit() with memory limit of N bytes)
53 if len(os.Args) > 3 && os.Args[1] == "--exec" {
55 limit, err := strconv.ParseUint(os.Args[3], 10, 64)
57 log.Fatalf("Expected memory limit as 3rd argument. %v\n", err)
60 if err := syscall.Setrlimit(syscall.RLIMIT_AS, &syscall.Rlimit{Cur: limit, Max: limit}); err != nil {
61 log.Fatalln(cause.Wrap(err, "Setrlimit").Error())
64 cmd := exec.Command(exe, os.Args[4:]...)
66 cmd.Stdout = os.Stdout
67 cmd.Stderr = os.Stderr
68 if err := cmd.Start(); err != nil {
69 os.Stderr.WriteString(err.Error())
72 // Forward signals to the child process
73 c := make(chan os.Signal, 1)
74 signal.Notify(c, os.Interrupt)
77 cmd.Process.Signal(sig)
82 os.Exit(cmd.ProcessState.ExitCode())
86 // Shell runs the executable exe with the given arguments, in the working
88 // If the process does not finish within timeout a errTimeout will be returned.
89 func Shell(timeout time.Duration, exe, wd string, args ...string) error {
90 if out, err := Exec(timeout, exe, wd, nil, args...); err != nil {
91 return cause.Wrap(err, "%s", out)
96 // Exec runs the executable exe with the given arguments, in the working
97 // directory wd, with the custom environment flags.
98 // If the process does not finish within timeout a errTimeout will be returned.
99 func Exec(timeout time.Duration, exe, wd string, env []string, args ...string) ([]byte, error) {
100 // Shell via regres: --exec N <exe> <args...>
101 // See main() for details.
102 args = append([]string{"--exec", exe, fmt.Sprintf("%v", MaxProcMemory)}, args...)
104 c := exec.Command(os.Args[0], args...)
110 if err := c.Start(); err != nil {
114 res := make(chan error)
115 go func() { res <- c.Wait() }()
118 case <-time.NewTimer(timeout).C:
119 log.Printf("Timeout for process %v\n", c.Process.Pid)
120 c.Process.Signal(syscall.SIGINT)
121 time.Sleep(time.Second * 5)
122 if !c.ProcessState.Exited() {
123 log.Printf("Process %v still has not exited, killing\n", c.Process.Pid)
124 syscall.Kill(-c.Process.Pid, syscall.SIGKILL)
126 return b.Bytes(), ErrTimeout{exe, timeout}
128 return b.Bytes(), err
132 // ErrTimeout is the error returned when a process does not finish with its
134 type ErrTimeout struct {
136 timeout time.Duration
139 func (e ErrTimeout) Error() string {
140 return fmt.Sprintf("'%v' did not return after %v", e.process, e.timeout)