mirror of https://github.com/docker/cli.git
connhelper: try sending SIGTERM before SIGKILL
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit acbb0eb6da
)
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
parent
5d3ab5bc0c
commit
4925fd9c34
|
@ -10,8 +10,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/connhelper/ssh"
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
|
@ -82,6 +84,8 @@ func newCommandConn(ctx context.Context, cmd string, args ...string) (net.Conn,
|
||||||
// commandConn implements net.Conn
|
// commandConn implements net.Conn
|
||||||
type commandConn struct {
|
type commandConn struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
cmdExited bool
|
||||||
|
cmdWaitErr error
|
||||||
cmdMutex sync.Mutex
|
cmdMutex sync.Mutex
|
||||||
stdin io.WriteCloser
|
stdin io.WriteCloser
|
||||||
stdout io.ReadCloser
|
stdout io.ReadCloser
|
||||||
|
@ -102,26 +106,74 @@ func (c *commandConn) killIfStdioClosed() error {
|
||||||
if !stdioClosed {
|
if !stdioClosed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var err error
|
return c.kill()
|
||||||
c.cmdMutex.Lock()
|
}
|
||||||
// NOTE: maybe already killed here
|
|
||||||
if err = c.cmd.Process.Kill(); err == nil {
|
// killAndWait tries sending SIGTERM to the process before sending SIGKILL.
|
||||||
err = c.cmd.Wait()
|
func killAndWait(cmd *exec.Cmd) error {
|
||||||
}
|
var werr error
|
||||||
if err != nil {
|
if runtime.GOOS != "windows" {
|
||||||
// err is typically "os: process already finished".
|
werrCh := make(chan error)
|
||||||
// we check ProcessState here instead of `strings.Contains(err, "os: process already finished")`
|
go func() { werrCh <- cmd.Wait() }()
|
||||||
if c.cmd.ProcessState.Exited() {
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
err = nil
|
select {
|
||||||
|
case werr = <-werrCh:
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
cmd.Process.Kill()
|
||||||
|
werr = <-werrCh
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
werr = cmd.Wait()
|
||||||
|
}
|
||||||
|
return werr
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill returns nil if the command terminated, regardless to the exit status.
|
||||||
|
func (c *commandConn) kill() error {
|
||||||
|
var werr error
|
||||||
|
c.cmdMutex.Lock()
|
||||||
|
if c.cmdExited {
|
||||||
|
werr = c.cmdWaitErr
|
||||||
|
} else {
|
||||||
|
werr = killAndWait(c.cmd)
|
||||||
|
c.cmdWaitErr = werr
|
||||||
|
c.cmdExited = true
|
||||||
}
|
}
|
||||||
c.cmdMutex.Unlock()
|
c.cmdMutex.Unlock()
|
||||||
return err
|
if werr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
wExitErr, ok := werr.(*exec.ExitError)
|
||||||
|
if ok {
|
||||||
|
if wExitErr.ProcessState.Exited() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Wrapf(werr, "connhelper: failed to wait")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandConn) onEOF(eof error) error {
|
func (c *commandConn) onEOF(eof error) error {
|
||||||
|
// when we got EOF, the command is going to be terminated
|
||||||
|
var werr error
|
||||||
c.cmdMutex.Lock()
|
c.cmdMutex.Lock()
|
||||||
werr := c.cmd.Wait()
|
if c.cmdExited {
|
||||||
|
werr = c.cmdWaitErr
|
||||||
|
} else {
|
||||||
|
werrCh := make(chan error)
|
||||||
|
go func() { werrCh <- c.cmd.Wait() }()
|
||||||
|
select {
|
||||||
|
case werr = <-werrCh:
|
||||||
|
c.cmdWaitErr = werr
|
||||||
|
c.cmdExited = true
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
c.cmdMutex.Unlock()
|
||||||
|
c.stderrMu.Lock()
|
||||||
|
stderr := c.stderr.String()
|
||||||
|
c.stderrMu.Unlock()
|
||||||
|
return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, eof, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
c.cmdMutex.Unlock()
|
c.cmdMutex.Unlock()
|
||||||
if werr == nil {
|
if werr == nil {
|
||||||
return eof
|
return eof
|
||||||
|
@ -136,7 +188,6 @@ func ignorableCloseError(err error) bool {
|
||||||
errS := err.Error()
|
errS := err.Error()
|
||||||
ss := []string{
|
ss := []string{
|
||||||
os.ErrClosed.Error(),
|
os.ErrClosed.Error(),
|
||||||
"process already finished",
|
|
||||||
}
|
}
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
if strings.Contains(errS, s) {
|
if strings.Contains(errS, s) {
|
||||||
|
@ -154,7 +205,7 @@ func (c *commandConn) CloseRead() error {
|
||||||
c.stdioClosedMu.Lock()
|
c.stdioClosedMu.Lock()
|
||||||
c.stdoutClosed = true
|
c.stdoutClosed = true
|
||||||
c.stdioClosedMu.Unlock()
|
c.stdioClosedMu.Unlock()
|
||||||
if err := c.killIfStdioClosed(); err != nil && !ignorableCloseError(err) {
|
if err := c.killIfStdioClosed(); err != nil {
|
||||||
logrus.Warnf("commandConn.CloseRead: %v", err)
|
logrus.Warnf("commandConn.CloseRead: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -176,7 +227,7 @@ func (c *commandConn) CloseWrite() error {
|
||||||
c.stdioClosedMu.Lock()
|
c.stdioClosedMu.Lock()
|
||||||
c.stdinClosed = true
|
c.stdinClosed = true
|
||||||
c.stdioClosedMu.Unlock()
|
c.stdioClosedMu.Unlock()
|
||||||
if err := c.killIfStdioClosed(); err != nil && !ignorableCloseError(err) {
|
if err := c.killIfStdioClosed(); err != nil {
|
||||||
logrus.Warnf("commandConn.CloseWrite: %v", err)
|
logrus.Warnf("commandConn.CloseWrite: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in New Issue