Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 84d6240

Browse files
author
Jim Minter
committedFeb 1, 2017
Resolve race conditions in attach API call
Signed-off-by: Jim Minter <[email protected]>
1 parent 48dd90d commit 84d6240

File tree

4 files changed

+98
-91
lines changed

4 files changed

+98
-91
lines changed
 

‎container/container.go

-13
Original file line numberDiff line numberDiff line change
@@ -365,19 +365,6 @@ func (container *Container) GetExecIDs() []string {
365365
return container.ExecCommands.List()
366366
}
367367

368-
// Attach connects to the container's stdio to the client streams
369-
func (container *Container) Attach(cfg *stream.AttachConfig) chan error {
370-
ctx := container.InitAttachContext()
371-
372-
cfg.TTY = container.Config.Tty
373-
if !container.Config.OpenStdin {
374-
cfg.Stdin = nil
375-
}
376-
cfg.CloseStdin = cfg.Stdin != nil && container.Config.StdinOnce
377-
378-
return container.StreamConfig.Attach(ctx, cfg)
379-
}
380-
381368
// ShouldRestart decides whether the daemon should restart the container or not.
382369
// This is based on the container's restart policy.
383370
func (container *Container) ShouldRestart() bool {

‎container/stream/attach.go

+43-25
Original file line numberDiff line numberDiff line change
@@ -33,33 +33,51 @@ type AttachConfig struct {
3333
// For example, this would close the attached container's stdin.
3434
CloseStdin bool
3535

36+
// UseStd* indicate whether the client has requested to be connected to the
37+
// given stream or not. These flags are used instead of checking Std* != nil
38+
// at points before the client streams Std* are wired up.
39+
UseStdin, UseStdout, UseStderr bool
40+
41+
// CStd* are the streams directly connected to the container
42+
CStdin io.WriteCloser
43+
CStdout, CStderr io.ReadCloser
44+
3645
// Provide client streams to wire up to
3746
Stdin io.ReadCloser
3847
Stdout, Stderr io.Writer
3948
}
4049

41-
// Attach attaches the stream config to the streams specified in
42-
// the AttachOptions
43-
func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
50+
// AttachStreams attaches the container's streams to the AttachConfig
51+
func (c *Config) AttachStreams(cfg *AttachConfig) {
52+
if cfg.UseStdin {
53+
cfg.CStdin = c.StdinPipe()
54+
}
55+
56+
if cfg.UseStdout {
57+
cfg.CStdout = c.StdoutPipe()
58+
}
59+
60+
if cfg.UseStderr {
61+
cfg.CStderr = c.StderrPipe()
62+
}
63+
}
64+
65+
// CopyStreams starts goroutines to copy data in and out to/from the container
66+
func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) chan error {
4467
var (
45-
cStdout, cStderr io.ReadCloser
46-
cStdin io.WriteCloser
47-
wg sync.WaitGroup
48-
errors = make(chan error, 3)
68+
wg sync.WaitGroup
69+
errors = make(chan error, 3)
4970
)
5071

5172
if cfg.Stdin != nil {
52-
cStdin = c.StdinPipe()
5373
wg.Add(1)
5474
}
5575

5676
if cfg.Stdout != nil {
57-
cStdout = c.StdoutPipe()
5877
wg.Add(1)
5978
}
6079

6180
if cfg.Stderr != nil {
62-
cStderr = c.StderrPipe()
6381
wg.Add(1)
6482
}
6583

@@ -72,9 +90,9 @@ func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
7290

7391
var err error
7492
if cfg.TTY {
75-
_, err = copyEscapable(cStdin, cfg.Stdin, cfg.DetachKeys)
93+
_, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
7694
} else {
77-
_, err = io.Copy(cStdin, cfg.Stdin)
95+
_, err = io.Copy(cfg.CStdin, cfg.Stdin)
7896
}
7997
if err == io.ErrClosedPipe {
8098
err = nil
@@ -84,14 +102,14 @@ func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
84102
errors <- err
85103
}
86104
if cfg.CloseStdin && !cfg.TTY {
87-
cStdin.Close()
105+
cfg.CStdin.Close()
88106
} else {
89107
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
90-
if cStdout != nil {
91-
cStdout.Close()
108+
if cfg.CStdout != nil {
109+
cfg.CStdout.Close()
92110
}
93-
if cStderr != nil {
94-
cStderr.Close()
111+
if cfg.CStderr != nil {
112+
cfg.CStderr.Close()
95113
}
96114
}
97115
logrus.Debug("attach: stdin: end")
@@ -121,8 +139,8 @@ func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
121139
wg.Done()
122140
}
123141

124-
go attachStream("stdout", cfg.Stdout, cStdout)
125-
go attachStream("stderr", cfg.Stderr, cStderr)
142+
go attachStream("stdout", cfg.Stdout, cfg.CStdout)
143+
go attachStream("stderr", cfg.Stderr, cfg.CStderr)
126144

127145
return promise.Go(func() error {
128146
done := make(chan struct{})
@@ -134,14 +152,14 @@ func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
134152
case <-done:
135153
case <-ctx.Done():
136154
// close all pipes
137-
if cStdin != nil {
138-
cStdin.Close()
155+
if cfg.CStdin != nil {
156+
cfg.CStdin.Close()
139157
}
140-
if cStdout != nil {
141-
cStdout.Close()
158+
if cfg.CStdout != nil {
159+
cfg.CStdout.Close()
142160
}
143-
if cStderr != nil {
144-
cStderr.Close()
161+
if cfg.CStderr != nil {
162+
cfg.CStderr.Close()
145163
}
146164
<-done
147165
}

‎daemon/attach.go

+49-51
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ import (
1515
"github.com/docker/docker/pkg/term"
1616
)
1717

18-
type containerAttachConfig struct {
19-
detachKeys []byte
20-
stdin io.ReadCloser
21-
stdout, stderr io.Writer
22-
showHistory bool
23-
stream bool
24-
}
25-
2618
// ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
2719
func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
2820
keys := []byte{}
@@ -43,6 +35,16 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
4335
return errors.NewRequestConflictError(err)
4436
}
4537

38+
cfg := stream.AttachConfig{
39+
UseStdin: c.UseStdin && container.Config.OpenStdin,
40+
UseStdout: c.UseStdout,
41+
UseStderr: c.UseStderr,
42+
TTY: container.Config.Tty,
43+
CloseStdin: container.Config.StdinOnce,
44+
DetachKeys: keys,
45+
}
46+
container.StreamConfig.AttachStreams(&cfg)
47+
4648
inStream, outStream, errStream, err := c.GetStreams()
4749
if err != nil {
4850
return err
@@ -54,48 +56,51 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
5456
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
5557
}
5658

57-
var cfg containerAttachConfig
58-
59-
if c.UseStdin {
60-
cfg.stdin = inStream
59+
if cfg.UseStdin {
60+
cfg.Stdin = inStream
6161
}
62-
if c.UseStdout {
63-
cfg.stdout = outStream
62+
if cfg.UseStdout {
63+
cfg.Stdout = outStream
6464
}
65-
if c.UseStderr {
66-
cfg.stderr = errStream
65+
if cfg.UseStderr {
66+
cfg.Stderr = errStream
6767
}
6868

69-
cfg.showHistory = c.Logs
70-
cfg.stream = c.Stream
71-
cfg.detachKeys = keys
72-
73-
if err := daemon.containerAttach(container, &cfg); err != nil {
69+
if err := daemon.containerAttach(container, &cfg, c.Logs, c.Stream); err != nil {
7470
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
7571
}
7672
return nil
7773
}
7874

7975
// ContainerAttachRaw attaches the provided streams to the container's stdio
80-
func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
76+
func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, doStream bool) error {
8177
container, err := daemon.GetContainer(prefixOrName)
8278
if err != nil {
8379
return err
8480
}
85-
cfg := &containerAttachConfig{
86-
stdin: stdin,
87-
stdout: stdout,
88-
stderr: stderr,
89-
stream: stream,
81+
cfg := stream.AttachConfig{
82+
UseStdin: stdin != nil && container.Config.OpenStdin,
83+
UseStdout: stdout != nil,
84+
UseStderr: stderr != nil,
85+
TTY: container.Config.Tty,
86+
CloseStdin: container.Config.StdinOnce,
87+
}
88+
container.StreamConfig.AttachStreams(&cfg)
89+
if cfg.UseStdin {
90+
cfg.Stdin = stdin
9091
}
91-
return daemon.containerAttach(container, cfg)
92+
if cfg.UseStdout {
93+
cfg.Stdout = stdout
94+
}
95+
if cfg.UseStderr {
96+
cfg.Stderr = stderr
97+
}
98+
99+
return daemon.containerAttach(container, &cfg, false, doStream)
92100
}
93101

94-
func (daemon *Daemon) containerAttach(c *container.Container, cfg *containerAttachConfig) error {
95-
stdin := cfg.stdin
96-
stdout := cfg.stdout
97-
stderr := cfg.stderr
98-
if cfg.showHistory {
102+
func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.AttachConfig, logs, doStream bool) error {
103+
if logs {
99104
logDriver, err := daemon.getLogger(c)
100105
if err != nil {
101106
return err
@@ -113,11 +118,11 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *containerAtta
113118
if !ok {
114119
break LogLoop
115120
}
116-
if msg.Source == "stdout" && stdout != nil {
117-
stdout.Write(msg.Line)
121+
if msg.Source == "stdout" && cfg.Stdout != nil {
122+
cfg.Stdout.Write(msg.Line)
118123
}
119-
if msg.Source == "stderr" && stderr != nil {
120-
stderr.Write(msg.Line)
124+
if msg.Source == "stderr" && cfg.Stderr != nil {
125+
cfg.Stderr.Write(msg.Line)
121126
}
122127
case err := <-logs.Err:
123128
logrus.Errorf("Error streaming logs: %v", err)
@@ -128,19 +133,18 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *containerAtta
128133

129134
daemon.LogContainerEvent(c, "attach")
130135

131-
if !cfg.stream {
136+
if !doStream {
132137
return nil
133138
}
134139

135-
var stdinPipe io.ReadCloser
136-
if stdin != nil {
140+
if cfg.Stdin != nil {
137141
r, w := io.Pipe()
138-
go func() {
142+
go func(stdin io.ReadCloser) {
139143
defer w.Close()
140144
defer logrus.Debug("Closing buffered stdin pipe")
141145
io.Copy(w, stdin)
142-
}()
143-
stdinPipe = r
146+
}(cfg.Stdin)
147+
cfg.Stdin = r
144148
}
145149

146150
waitChan := make(chan struct{})
@@ -154,14 +158,8 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *containerAtta
154158
}()
155159
}
156160

157-
aCfg := &stream.AttachConfig{
158-
Stdin: stdinPipe,
159-
Stdout: stdout,
160-
Stderr: stderr,
161-
DetachKeys: cfg.detachKeys,
162-
}
163-
164-
err := <-c.Attach(aCfg)
161+
ctx := c.InitAttachContext()
162+
err := <-c.StreamConfig.CopyStreams(ctx, cfg)
165163
if err != nil {
166164
if _, ok := err.(stream.DetachError); ok {
167165
daemon.LogContainerEvent(c, "detach")

‎daemon/exec.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,19 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
210210
return err
211211
}
212212

213-
attachConfig := &stream.AttachConfig{
213+
attachConfig := stream.AttachConfig{
214214
TTY: ec.Tty,
215+
UseStdin: cStdin != nil,
216+
UseStdout: cStdout != nil,
217+
UseStderr: cStderr != nil,
215218
Stdin: cStdin,
216219
Stdout: cStdout,
217220
Stderr: cStderr,
218221
DetachKeys: ec.DetachKeys,
219222
CloseStdin: true,
220223
}
221-
attachErr := ec.StreamConfig.Attach(ctx, attachConfig)
224+
ec.StreamConfig.AttachStreams(&attachConfig)
225+
attachErr := ec.StreamConfig.CopyStreams(ctx, &attachConfig)
222226

223227
systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio)
224228
if err != nil {

0 commit comments

Comments
 (0)
Please sign in to comment.