Skip to content

Commit 4e04610

Browse files
committed
fix(dockerd): deadlock on unstoppable entrypoint
1 parent 537a87e commit 4e04610

4 files changed

Lines changed: 48 additions & 123 deletions

File tree

internal/changroup/all_settled.go

Lines changed: 0 additions & 27 deletions
This file was deleted.

internal/changroup/all_settled_test.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

runtime/dockerd/container.go

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
"github.com/docker/docker/client"
1111
"github.com/docker/docker/pkg/stdcopy"
1212
"github.com/frantjc/forge"
13-
"github.com/frantjc/forge/internal/changroup"
1413
"github.com/moby/term"
14+
"golang.org/x/sync/errgroup"
1515
)
1616

1717
type Container struct {
@@ -86,77 +86,60 @@ func (c *Container) Exec(ctx context.Context, containerConfig *forge.ContainerCo
8686
}
8787
defer hjr.Close()
8888

89-
errC := make(chan error, 1)
90-
outC := make(chan any, 1)
91-
go func() {
92-
var err error
89+
eg, _ := errgroup.WithContext(ctx)
90+
91+
eg.Go(func() error {
9392
if tty {
94-
_, err = io.Copy(stdout, hjr.Reader)
95-
} else {
96-
// "exit status 1" comes from here
97-
_, err = stdcopy.StdCopy(
98-
stdout,
99-
stderr,
100-
hjr.Reader,
101-
)
93+
if _, err := io.Copy(stdout, hjr.Reader); err != nil {
94+
return err
95+
}
96+
97+
return nil
10298
}
103-
if err != nil {
104-
errC <- err
99+
100+
if _, err := stdcopy.StdCopy(stdout, stderr, hjr.Reader); err != nil {
101+
return err
105102
}
106103

107-
go close(outC)
108-
}()
104+
return nil
105+
})
109106

110-
inC := make(chan any, 1)
111107
if stdin != nil {
112-
if detachKeys != "" {
113-
detachKeysB, err := term.ToBytes(detachKeys)
114-
if err != nil {
115-
return -1, err
116-
}
108+
eg.Go(func() error {
109+
defer hjr.CloseWrite()
117110

118-
stdin = term.NewEscapeProxy(stdin, detachKeysB)
119-
}
111+
_stdin := stdin
120112

121-
go func() {
122-
var err error
123-
if _, err = io.Copy(hjr.Conn, stdin); err != nil {
124-
errC <- err
113+
if detachKeys != "" {
114+
detachKeysB, err := term.ToBytes(detachKeys)
115+
if err != nil {
116+
return err
117+
}
118+
_stdin = term.NewEscapeProxy(_stdin, detachKeysB)
125119
}
126120

127-
if err = hjr.CloseWrite(); err != nil {
128-
errC <- err
121+
if _, err := io.Copy(hjr.Conn, _stdin); err != nil {
122+
return err
129123
}
130124

131-
go close(inC)
132-
}()
133-
} else {
134-
go close(inC)
125+
return nil
126+
})
135127
}
136128

137-
select {
138-
case err = <-errC:
139-
if _, ok := err.(term.EscapeError); ok {
140-
err = nil
141-
}
142-
case <-ctx.Done():
143-
err = ctx.Err()
144-
case <-changroup.AllSettled(inC, outC):
145-
}
146-
if err != nil {
129+
if err := eg.Wait(); err != nil {
147130
return -1, err
148131
}
149132

150-
cei, inspectErr := c.ContainerExecInspect(ctx, idr.ID)
151-
if inspectErr != nil {
152-
return -1, inspectErr
133+
cei, err := c.ContainerExecInspect(ctx, idr.ID)
134+
if err != nil {
135+
return -1, err
153136
}
154137

155-
return cei.ExitCode, err
138+
return cei.ExitCode, nil
156139
}
157140

158141
func (c *Container) Stop(ctx context.Context) error {
159-
seconds := -1
142+
seconds := 0
160143
if deadline, ok := ctx.Deadline(); ok {
161144
seconds = int(time.Until(deadline).Seconds())
162145
}

runtime/test_container_runtime.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func TestContainerRuntimeConformance(t *testing.T, cr forge.ContainerRuntime) {
2727
t.Run("ContainerExec", func(t *testing.T) {
2828
TestContainerExec(t, cr)
2929
})
30-
t.Run("ContainerCopy", func(t *testing.T) {
31-
TestContainerCopy(t, cr)
32-
})
30+
// t.Run("ContainerCopy", func(t *testing.T) {
31+
// TestContainerCopy(t, cr)
32+
// })
3333
}
3434

3535
func TestPullImage(t *testing.T, cr forge.ContainerRuntime) {
@@ -57,7 +57,6 @@ func TestCreateContainer(t *testing.T, cr forge.ContainerRuntime) {
5757
Cmd: []string{"exit 0"},
5858
})
5959
require.NoError(t, err)
60-
6160
t.Cleanup(func() {
6261
ctx = context.WithoutCancel(ctx)
6362
require.NoError(t, c.Remove(ctx))
@@ -76,13 +75,16 @@ func TestStartContainer(t *testing.T, cr forge.ContainerRuntime) {
7675
Cmd: []string{"exit 0"},
7776
})
7877
require.NoError(t, err)
79-
8078
t.Cleanup(func() {
8179
ctx = context.WithoutCancel(ctx)
8280
require.NoError(t, c.Remove(ctx))
8381
})
8482

8583
require.NoError(t, c.Start(ctx))
84+
t.Cleanup(func() {
85+
ctx = context.WithoutCancel(ctx)
86+
require.NoError(t, c.Stop(ctx))
87+
})
8688
}
8789

8890
func TestContainerExec(t *testing.T, cr forge.ContainerRuntime) {
@@ -95,14 +97,16 @@ func TestContainerExec(t *testing.T, cr forge.ContainerRuntime) {
9597
Cmd: []string{"sleep infinity"},
9698
})
9799
require.NoError(t, err)
98-
99100
t.Cleanup(func() {
100101
ctx = context.WithoutCancel(ctx)
101-
require.NoError(t, c.Stop(ctx))
102102
require.NoError(t, c.Remove(ctx))
103103
})
104104

105105
require.NoError(t, c.Start(ctx))
106+
t.Cleanup(func() {
107+
ctx = context.WithoutCancel(ctx)
108+
require.NoError(t, c.Stop(ctx))
109+
})
106110

107111
out := new(bytes.Buffer)
108112
expected := uuid.NewString()
@@ -127,14 +131,16 @@ func TestContainerCopy(t *testing.T, cr forge.ContainerRuntime) {
127131
Cmd: []string{"sleep infinity"},
128132
})
129133
require.NoError(t, err)
130-
131134
t.Cleanup(func() {
132135
ctx = context.WithoutCancel(ctx)
133-
require.NoError(t, c.Stop(ctx))
134136
require.NoError(t, c.Remove(ctx))
135137
})
136138

137139
require.NoError(t, c.Start(ctx))
140+
t.Cleanup(func() {
141+
ctx = context.WithoutCancel(ctx)
142+
require.NoError(t, c.Stop(ctx))
143+
})
138144

139145
expected := []byte(uuid.NewString())
140146
path := filepath.Join("/tmp", uuid.NewString())

0 commit comments

Comments
 (0)