add graceful shutdown for bin mode

This commit is contained in:
Alexander Baryshnikov 2020-10-09 19:37:41 +08:00
parent a183d116a1
commit 6d3d90b0e2
5 changed files with 85 additions and 30 deletions

18
_docs/modes.md Normal file
View File

@ -0,0 +1,18 @@
## BIN
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.
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
```yaml
command: "V=1 RAIL=2 foo bar -c -y -z"
```
Good
```yaml
command: "V=1 RAIL=2 exec foo bar -c -y -z"
```

View File

@ -3,6 +3,7 @@ package server
import (
"context"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
@ -33,11 +34,12 @@ func (m *markerResponse) WriteHeader(statusCode int) {
}
type binHandler struct {
command string
workDir string
shell string
environment []string
timeout time.Duration
command string
workDir string
shell string
environment []string
timeout time.Duration
gracefulTimeout time.Duration
}
func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
@ -75,7 +77,29 @@ func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reques
cmd.Stdout = marker
cmd.Env = env
api.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
if codeReset, ok := writer.(interface{ Status(status int) }); ok && err != nil {
codeReset.Status(http.StatusBadGateway)

File diff suppressed because one or more lines are too long

View File

@ -26,17 +26,18 @@ import (
)
type Unit struct {
Interval time.Duration `yaml:"interval,omitempty"` // interval between attempts
Attempts int `yaml:"attempts,omitempty"` // maximum number of attempts
Workers int `yaml:"workers,omitempty"` // concurrency level - number of parallel requests
Mode string `yaml:"mode,omitempty"` // execution mode: bin, cgi or proxy
WorkDir string `yaml:"workdir,omitempty"` // working directory for the worker. if empty - temporary one will generated automatically
Command string `yaml:"command"` // command in a shell to execute
Timeout time.Duration `yaml:"timeout,omitempty"` // maximum execution timeout (enabled only for bin mode and only if positive)
Shell string `yaml:"shell,omitempty"` // shell to execute command in bin mode (default - /bin/sh)
Environment map[string]string `yaml:"environment,omitempty"` // custom environment for executable (in addition to system)
MaxRequest int64 `yaml:"max_request,omitempty"` // optional maximum HTTP body size (enabled if positive)
Authorization struct {
Interval time.Duration `yaml:"interval,omitempty"` // interval between attempts
Attempts int `yaml:"attempts,omitempty"` // maximum number of attempts
Workers int `yaml:"workers,omitempty"` // concurrency level - number of parallel requests
Mode string `yaml:"mode,omitempty"` // execution mode: bin, cgi or proxy
WorkDir string `yaml:"workdir,omitempty"` // working directory for the worker. if empty - temporary one will generated automatically
Command string `yaml:"command"` // command in a shell to execute
Timeout time.Duration `yaml:"timeout,omitempty"` // maximum execution timeout (enabled only for bin mode and only if positive)
GracefulTimeout time.Duration `yaml:"graceful_timeout,omitempty"` // maximum execution timeout after which SIGINT will be sent (enabled only for bin mode and only if positive)
Shell string `yaml:"shell,omitempty"` // shell to execute command in bin mode (default - /bin/sh)
Environment map[string]string `yaml:"environment,omitempty"` // custom environment for executable (in addition to system)
MaxRequest int64 `yaml:"max_request,omitempty"` // optional maximum HTTP body size (enabled if positive)
Authorization struct {
JWT struct {
Enable bool `yaml:"enable"` // enable JWT verification
JWT `yaml:",inline"`
@ -242,11 +243,12 @@ func (cfg Unit) createRunner() (http.Handler, error) {
switch cfg.Mode {
case "bin":
return &binHandler{
command: cfg.Command,
workDir: cfg.WorkDir,
shell: cfg.Shell,
timeout: cfg.Timeout,
environment: append(os.Environ(), makeEnvList(cfg.Environment)...),
command: cfg.Command,
workDir: cfg.WorkDir,
shell: cfg.Shell,
timeout: cfg.Timeout,
gracefulTimeout: cfg.GracefulTimeout,
environment: append(os.Environ(), makeEnvList(cfg.Environment)...),
}, nil
case "cgi":
return &cgi.Handler{

View File

@ -50,14 +50,25 @@
<dd class="col-sm-9">{{.Unit.Attempts}}</dd>
<dt class="col-sm-3">Interval</dt>
<dd class="col-sm-9">{{.Unit.Interval}}</dd>
<dt class="col-sm-3">Timeout</dt>
<dd class="col-sm-9">
{{with .Unit.Timeout}}
{{.}}
{{else}}
{{end}}
</dd>
{{if eq .Unit.Mode "bin"}}
<dt class="col-sm-3" title="Timeout after which process will be sent">Timeout</dt>
<dd class="col-sm-9">
{{with .Unit.Timeout}}
{{.}}
{{else}}
{{end}}
</dd>
<dt class="col-sm-3" title="Timeout after which SIGINT will be sent">Graceful timeout
</dt>
<dd class="col-sm-9">
{{with .Unit.GracefulTimeout}}
{{.}}
{{else}}
{{end}}
</dd>
{{end}}
<dt class="col-sm-3">Max request size</dt>
<dd class="col-sm-9">
{{with .Unit.MaxRequest}}