(ctx context.Context)
| 141 | } |
| 142 | |
| 143 | func (c *Cmd) initExecCommand(ctx context.Context) *exec.Cmd { |
| 144 | if c.execCmd != nil { |
| 145 | return c.execCmd |
| 146 | } |
| 147 | |
| 148 | c.execCmd = exec.CommandContext(ctx, c.Path) |
| 149 | c.execCmd.Path = c.Path |
| 150 | c.execCmd.Args = c.Args.StringSlice() |
| 151 | c.execCmd.Env = c.Env |
| 152 | c.execCmd.Stdin = c.Stdin |
| 153 | c.execCmd.Stdout = c.Stdout |
| 154 | c.execCmd.Stderr = c.Stderr |
| 155 | |
| 156 | c.execCmd.Cancel = func() error { |
| 157 | // Try to let Nix exit gracefully by sending an interrupt |
| 158 | // instead of the default behavior of killing it. |
| 159 | c.logger().DebugContext(ctx, "sending interrupt to nix process", slog.Group("cmd", |
| 160 | "args", c.Args, |
| 161 | "path", c.execCmd.Path, |
| 162 | "pid", c.execCmd.Process.Pid, |
| 163 | )) |
| 164 | err := c.execCmd.Process.Signal(os.Interrupt) |
| 165 | if errors.Is(err, os.ErrProcessDone) { |
| 166 | // Nix already exited; execCmd.Wait will use the exit |
| 167 | // code. |
| 168 | return err |
| 169 | } |
| 170 | if err != nil { |
| 171 | // We failed to send SIGINT, so kill the process |
| 172 | // instead. |
| 173 | // |
| 174 | // - If Nix already exited, Kill will return |
| 175 | // os.ErrProcessDone and execCmd.Wait will use |
| 176 | // the exit code. |
| 177 | // - Otherwise, execCmd.Wait will always return an |
| 178 | // error. |
| 179 | c.logger().DebugContext(ctx, "error interrupting nix process, attempting to kill", |
| 180 | "err", err, slog.Group("cmd", |
| 181 | "args", c.Args, |
| 182 | "path", c.execCmd.Path, |
| 183 | "pid", c.execCmd.Process.Pid, |
| 184 | )) |
| 185 | return c.execCmd.Process.Kill() |
| 186 | } |
| 187 | |
| 188 | // We sent the SIGINT successfully. It's still possible for Nix |
| 189 | // to exit successfully, so return os.ErrProcessDone so that |
| 190 | // execCmd.Wait uses the exit code instead of ctx.Err. |
| 191 | return os.ErrProcessDone |
| 192 | } |
| 193 | // Kill Nix if it doesn't exit within 15 seconds of Devbox sending an |
| 194 | // interrupt. |
| 195 | c.execCmd.WaitDelay = 15 * time.Second |
| 196 | return c.execCmd |
| 197 | } |
| 198 | |
| 199 | func (c *Cmd) error(ctx context.Context, err error) error { |
| 200 | if err == nil { |
no test coverage detected