proper handle nested subprocesses on Linux
This commit is contained in:
parent
b6fd8e1eb2
commit
38935d4657
@ -3,6 +3,11 @@
|
||||
Binary modes just executes any script in shell (`/bin/sh` by default). You can override shell per-unit
|
||||
by `shell: /path/to/shell` configuration param.
|
||||
|
||||
Linux's hosts should not worry about "leaking" processes (when process
|
||||
creates number of background subprocesses) - it wil be cleaned automatically.
|
||||
|
||||
**For non-Linux users**
|
||||
|
||||
To handle a graceful timeout, child should be able to forward signal: basically, use `exec` before last command.
|
||||
|
||||
Danger (but will work), signals may not be handled by foo
|
||||
|
@ -1,9 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
package api
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func SetBinFlags(cmd *exec.Cmd) {
|
||||
|
||||
}
|
20
server/internal/flags.go
Normal file
20
server/internal/flags.go
Normal file
@ -0,0 +1,20 @@
|
||||
// +build !linux
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func SetBinFlags(cmd *exec.Cmd) {
|
||||
|
||||
}
|
||||
|
||||
func IntSignal(cmd *exec.Cmd) error {
|
||||
return cmd.Process.Signal(os.Interrupt)
|
||||
}
|
||||
|
||||
func KillSignal(cmd *exec.Cmd) error {
|
||||
return cmd.Process.Signal(os.Kill)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package api
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
@ -12,3 +12,11 @@ func SetBinFlags(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGTERM
|
||||
cmd.SysProcAttr.Setpgid = true
|
||||
}
|
||||
|
||||
func IntSignal(cmd *exec.Cmd) error {
|
||||
return syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
|
||||
}
|
||||
|
||||
func KillSignal(cmd *exec.Cmd) error {
|
||||
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"nano-run/server/api"
|
||||
"nano-run/server/internal"
|
||||
)
|
||||
|
||||
type markerResponse struct {
|
||||
@ -44,15 +44,8 @@ type binHandler struct {
|
||||
|
||||
func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
marker := &markerResponse{res: writer}
|
||||
|
||||
ctx := request.Context()
|
||||
if bh.timeout > 0 {
|
||||
c, cancel := context.WithTimeout(ctx, bh.timeout)
|
||||
defer cancel()
|
||||
ctx = c
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, bh.shell, "-c", bh.command) //nolint:gosec
|
||||
cmd := exec.Command(bh.shell, "-c", bh.command) //nolint:gosec
|
||||
|
||||
if bh.workDir == "" {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
@ -76,30 +69,9 @@ func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reques
|
||||
cmd.Stdin = request.Body
|
||||
cmd.Stdout = marker
|
||||
cmd.Env = env
|
||||
api.SetBinFlags(cmd)
|
||||
internal.SetBinFlags(cmd)
|
||||
|
||||
var done bool
|
||||
|
||||
if bh.gracefulTimeout > 0 {
|
||||
graceCtx, graceCancel := context.WithTimeout(ctx, bh.gracefulTimeout)
|
||||
defer graceCancel()
|
||||
go func() {
|
||||
<-graceCtx.Done()
|
||||
proc := cmd.Process
|
||||
if proc == nil || done {
|
||||
return
|
||||
}
|
||||
err := proc.Signal(os.Interrupt)
|
||||
if err != nil {
|
||||
log.Println("failed send signal to process:", err)
|
||||
} else {
|
||||
log.Println("sent graceful shutdown to proces")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
done = true
|
||||
err := bh.run(ctx, cmd)
|
||||
|
||||
if codeReset, ok := writer.(interface{ Status(status int) }); ok && err != nil {
|
||||
codeReset.Status(http.StatusBadGateway)
|
||||
@ -114,6 +86,62 @@ func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reques
|
||||
}
|
||||
}
|
||||
|
||||
func (bh *binHandler) run(global context.Context, cmd *exec.Cmd) error {
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
gracefulTimeout <-chan time.Time
|
||||
ctx context.Context
|
||||
)
|
||||
|
||||
var gracefulTimer *time.Ticker
|
||||
if bh.gracefulTimeout > 0 {
|
||||
gracefulTimer = time.NewTicker(bh.gracefulTimeout)
|
||||
defer gracefulTimer.Stop()
|
||||
gracefulTimeout = gracefulTimer.C
|
||||
}
|
||||
|
||||
if bh.timeout > 0 {
|
||||
t, cancel := context.WithTimeout(global, bh.timeout)
|
||||
defer cancel()
|
||||
ctx = t
|
||||
} else {
|
||||
ctx = global
|
||||
}
|
||||
|
||||
var process = make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(process)
|
||||
process <- cmd.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-gracefulTimeout:
|
||||
if err := internal.IntSignal(cmd); err != nil {
|
||||
log.Println("failed send signal to process:", err)
|
||||
} else {
|
||||
log.Println("sent graceful shutdown to process")
|
||||
}
|
||||
gracefulTimer.Stop()
|
||||
gracefulTimeout = nil
|
||||
case <-ctx.Done():
|
||||
if err := internal.KillSignal(cmd); err != nil {
|
||||
log.Println("failed send kill to process:", err)
|
||||
} else {
|
||||
log.Println("sent kill to process")
|
||||
}
|
||||
return <-process
|
||||
case err := <-process:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bh *binHandler) cloneEnv() []string {
|
||||
var cp = make([]string, len(bh.environment))
|
||||
copy(cp, bh.environment)
|
||||
|
Loading…
Reference in New Issue
Block a user