blob: 28fae261bb9f5719d7a53ccf94f309ebb486b4df [file] [log] [blame]
Ben Claytona2a00e22019-02-16 01:05:23 +00001// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
2//
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
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
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.
14
15// Package shell provides functions for running sub-processes.
16package shell
17
18import (
19 "bytes"
20 "fmt"
21 "log"
22 "os"
23 "os/exec"
24 "strconv"
25 "syscall"
26 "time"
27
28 "../cause"
29)
30
31// MaxProcMemory is the maximum virtual memory per child process
32var MaxProcMemory uint64 = 2 * 1024 * 1024 * 1024 // 2GB
33
34func init() {
35 // As we are going to be running a number of tests concurrently, we need to
36 // limit the amount of virtual memory each test uses, otherwise memory
37 // hungry tests can bring the whole system down into a swapping apocalypse.
38 //
39 // Linux has the setrlimit() function to limit a process (and child's)
40 // virtual memory usage - but we cannot call this from the regres process
41 // as this process may need more memory than the limit allows.
42 //
43 // Unfortunately golang has no native support for setting rlimits for child
44 // processes (https://github.com/golang/go/issues/6603), so we instead wrap
45 // the exec to the test executable with another child regres process using a
46 // special --exec mode:
47 //
48 // [regres] -> [regres --exec <test-exe N args...>] -> [test-exe]
49 // ^^^^
50 // (calls rlimit() with memory limit of N bytes)
51
52 if len(os.Args) > 3 && os.Args[1] == "--exec" {
53 exe := os.Args[2]
54 limit, err := strconv.ParseUint(os.Args[3], 10, 64)
55 if err != nil {
56 log.Fatalf("Expected memory limit as 3rd argument. %v\n", err)
57 }
58 if limit > 0 {
59 if err := syscall.Setrlimit(syscall.RLIMIT_AS, &syscall.Rlimit{Cur: limit, Max: limit}); err != nil {
60 log.Fatalln(cause.Wrap(err, "Setrlimit").Error())
61 }
62 }
63 c := exec.Command(exe, os.Args[4:]...)
64 c.Stdin = os.Stdin
65 c.Stdout = os.Stdout
66 c.Stderr = os.Stderr
67 c.Run()
68 os.Exit(c.ProcessState.ExitCode())
69 }
70}
71
72// Shell runs the executable exe with the given arguments, in the working
73// directory wd.
74// If the process does not finish within timeout a errTimeout will be returned.
75func Shell(timeout time.Duration, exe, wd string, args ...string) error {
76 if out, err := Exec(timeout, exe, wd, nil, args...); err != nil {
77 return cause.Wrap(err, "%s", out)
78 }
79 return nil
80}
81
82// Exec runs the executable exe with the given arguments, in the working
83// directory wd, with the custom environment flags.
84// If the process does not finish within timeout a errTimeout will be returned.
85func Exec(timeout time.Duration, exe, wd string, env []string, args ...string) ([]byte, error) {
86 // Shell via regres: --exec N <exe> <args...>
87 // See main() for details.
88 args = append([]string{"--exec", exe, fmt.Sprintf("%v", MaxProcMemory)}, args...)
89 b := bytes.Buffer{}
90 c := exec.Command(os.Args[0], args...)
91 c.Dir = wd
92 c.Env = env
93 c.Stdout = &b
94 c.Stderr = &b
95
96 if err := c.Start(); err != nil {
97 return nil, err
98 }
99
100 res := make(chan error)
101 go func() { res <- c.Wait() }()
102
103 select {
104 case <-time.NewTimer(timeout).C:
105 c.Process.Signal(syscall.SIGINT)
106 time.Sleep(time.Second * 5)
107 if !c.ProcessState.Exited() {
108 c.Process.Kill()
109 }
110 return b.Bytes(), ErrTimeout{exe, timeout}
111 case err := <-res:
112 return b.Bytes(), err
113 }
114}
115
116// ErrTimeout is the error returned when a process does not finish with its
117// permitted time.
118type ErrTimeout struct {
119 process string
120 timeout time.Duration
121}
122
123func (e ErrTimeout) Error() string {
124 return fmt.Sprintf("'%v' did not return after %v", e.process, e.timeout)
125}