mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Fix goroutine leak on logs -f with no output
Also noticed potential hang when only stdout or stderr are used with follow=1 Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
cbe120db58
commit
0c84604f54
3 changed files with 73 additions and 10 deletions
|
@ -597,6 +597,11 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
|
||||||
since = time.Unix(s, 0)
|
since = time.Unix(s, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var closeNotifier <-chan bool
|
||||||
|
if notifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
closeNotifier = notifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
logsConfig := &daemon.ContainerLogsConfig{
|
||||||
Follow: boolValue(r, "follow"),
|
Follow: boolValue(r, "follow"),
|
||||||
Timestamps: boolValue(r, "timestamps"),
|
Timestamps: boolValue(r, "timestamps"),
|
||||||
|
@ -605,6 +610,7 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
|
||||||
UseStdout: stdout,
|
UseStdout: stdout,
|
||||||
UseStderr: stderr,
|
UseStderr: stderr,
|
||||||
OutStream: ioutils.NewWriteFlusher(w),
|
OutStream: ioutils.NewWriteFlusher(w),
|
||||||
|
Stop: closeNotifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil {
|
if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil {
|
||||||
|
|
|
@ -25,6 +25,7 @@ type ContainerLogsConfig struct {
|
||||||
Since time.Time
|
Since time.Time
|
||||||
UseStdout, UseStderr bool
|
UseStdout, UseStderr bool
|
||||||
OutStream io.Writer
|
OutStream io.Writer
|
||||||
|
Stop <-chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
|
func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
|
||||||
|
@ -119,7 +120,8 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Follow && container.IsRunning() {
|
if config.Follow && container.IsRunning() {
|
||||||
chErr := make(chan error)
|
chErrStderr := make(chan error)
|
||||||
|
chErrStdout := make(chan error)
|
||||||
var stdoutPipe, stderrPipe io.ReadCloser
|
var stdoutPipe, stderrPipe io.ReadCloser
|
||||||
|
|
||||||
// write an empty chunk of data (this is to ensure that the
|
// write an empty chunk of data (this is to ensure that the
|
||||||
|
@ -131,7 +133,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
stdoutPipe = container.StdoutLogPipe()
|
stdoutPipe = container.StdoutLogPipe()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Debug("logs: stdout stream begin")
|
logrus.Debug("logs: stdout stream begin")
|
||||||
chErr <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
|
chErrStdout <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
|
||||||
logrus.Debug("logs: stdout stream end")
|
logrus.Debug("logs: stdout stream end")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -139,19 +141,33 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
stderrPipe = container.StderrLogPipe()
|
stderrPipe = container.StderrLogPipe()
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Debug("logs: stderr stream begin")
|
logrus.Debug("logs: stderr stream begin")
|
||||||
chErr <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
|
chErrStderr <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
|
||||||
logrus.Debug("logs: stderr stream end")
|
logrus.Debug("logs: stderr stream end")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = <-chErr
|
select {
|
||||||
if stdoutPipe != nil {
|
case err = <-chErrStderr:
|
||||||
stdoutPipe.Close()
|
if stdoutPipe != nil {
|
||||||
|
stdoutPipe.Close()
|
||||||
|
<-chErrStdout
|
||||||
|
}
|
||||||
|
case err = <-chErrStdout:
|
||||||
|
if stderrPipe != nil {
|
||||||
|
stderrPipe.Close()
|
||||||
|
<-chErrStderr
|
||||||
|
}
|
||||||
|
case <-config.Stop:
|
||||||
|
if stdoutPipe != nil {
|
||||||
|
stdoutPipe.Close()
|
||||||
|
<-chErrStdout
|
||||||
|
}
|
||||||
|
if stderrPipe != nil {
|
||||||
|
stderrPipe.Close()
|
||||||
|
<-chErrStderr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if stderrPipe != nil {
|
|
||||||
stderrPipe.Close()
|
|
||||||
}
|
|
||||||
<-chErr // wait for 2nd goroutine to exit, otherwise bad things will happen
|
|
||||||
|
|
||||||
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
|
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
|
||||||
if e, ok := err.(*net.OpError); ok && e.Err != syscall.EPIPE {
|
if e, ok := err.(*net.OpError); ok && e.Err != syscall.EPIPE {
|
||||||
|
|
|
@ -425,3 +425,44 @@ func (s *DockerSuite) TestLogsFollowGoroutinesWithStdout(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestLogsFollowGoroutinesNoOutput(c *check.C) {
|
||||||
|
out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true; do sleep 2; done")
|
||||||
|
id := strings.TrimSpace(out)
|
||||||
|
c.Assert(waitRun(id), check.IsNil)
|
||||||
|
|
||||||
|
type info struct {
|
||||||
|
NGoroutines int
|
||||||
|
}
|
||||||
|
getNGoroutines := func() int {
|
||||||
|
var i info
|
||||||
|
status, b, err := sockRequest("GET", "/info", nil)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(status, check.Equals, 200)
|
||||||
|
c.Assert(json.Unmarshal(b, &i), check.IsNil)
|
||||||
|
return i.NGoroutines
|
||||||
|
}
|
||||||
|
|
||||||
|
nroutines := getNGoroutines()
|
||||||
|
|
||||||
|
cmd := exec.Command(dockerBinary, "logs", "-f", id)
|
||||||
|
c.Assert(cmd.Start(), check.IsNil)
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
c.Assert(cmd.Process.Kill(), check.IsNil)
|
||||||
|
|
||||||
|
// NGoroutines is not updated right away, so we need to wait before failing
|
||||||
|
t := time.After(30 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t:
|
||||||
|
if n := getNGoroutines(); n > nroutines {
|
||||||
|
c.Fatalf("leaked goroutines: expected less than or equal to %d, got: %d", nroutines, n)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if n := getNGoroutines(); n <= nroutines {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue