stdioClosedMusync.Mutex// for stdinClosed and stdoutClosed
stdinClosedbool
stdoutClosedbool
localAddrnet.Addr
remoteAddrnet.Addr
}
// killIfStdioClosed kills the cmd if both stdin and stdout are closed.
func(c*commandConn)killIfStdioClosed()error{
c.stdioClosedMu.Lock()
stdioClosed:=c.stdoutClosed&&c.stdinClosed
c.stdioClosedMu.Unlock()
if!stdioClosed{
returnnil
}
returnc.kill()
}
// killAndWait tries sending SIGTERM to the process before sending SIGKILL.
funckillAndWait(cmd*exec.Cmd)error{
varwerrerror
ifruntime.GOOS!="windows"{
werrCh:=make(chanerror)
gofunc(){werrCh<-cmd.Wait()}()
cmd.Process.Signal(syscall.SIGTERM)
select{
casewerr=<-werrCh:
case<-time.After(3*time.Second):
cmd.Process.Kill()
werr=<-werrCh
}
}else{
cmd.Process.Kill()
werr=cmd.Wait()
}
returnwerr
}
// kill returns nil if the command terminated, regardless to the exit status.
func(c*commandConn)kill()error{
varwerrerror
c.cmdMutex.Lock()
ifc.cmdExited{
werr=c.cmdWaitErr
}else{
werr=killAndWait(c.cmd)
c.cmdWaitErr=werr
c.cmdExited=true
}
c.cmdMutex.Unlock()
ifwerr==nil{
returnnil
}
wExitErr,ok:=werr.(*exec.ExitError)
ifok{
ifwExitErr.ProcessState.Exited(){
returnnil
}
}
returnerrors.Wrapf(werr,"commandconn: failed to wait")
}
func(c*commandConn)onEOF(eoferror)error{
// when we got EOF, the command is going to be terminated
varwerrerror
c.cmdMutex.Lock()
ifc.cmdExited{
werr=c.cmdWaitErr
}else{
werrCh:=make(chanerror)
gofunc(){werrCh<-c.cmd.Wait()}()
select{
casewerr=<-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()
returnerrors.Errorf("command %v did not exit after %v: stderr=%q",c.cmd.Args,eof,stderr)
}
}
c.cmdMutex.Unlock()
ifwerr==nil{
returneof
}
c.stderrMu.Lock()
stderr:=c.stderr.String()
c.stderrMu.Unlock()
returnerrors.Errorf("command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s",c.cmd.Args,werr,stderr)