Skip to content

Commit 6fd4825

Browse files
authored
Merge pull request #5654 from Benehiko/fix-run-ctx-27.x
[27.x backport] fix: ctx on run image pull
2 parents ce03e25 + 530cf09 commit 6fd4825

File tree

4 files changed

+92
-9
lines changed

4 files changed

+92
-9
lines changed

cli/command/container/client_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type fakeClient struct {
2525
platform *specs.Platform,
2626
containerName string) (container.CreateResponse, error)
2727
containerStartFunc func(containerID string, options container.StartOptions) error
28-
imageCreateFunc func(parentReference string, options image.CreateOptions) (io.ReadCloser, error)
28+
imageCreateFunc func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
2929
infoFunc func() (system.Info, error)
3030
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
3131
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
@@ -94,9 +94,9 @@ func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, op
9494
return nil
9595
}
9696

97-
func (f *fakeClient) ImageCreate(_ context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
97+
func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
9898
if f.imageCreateFunc != nil {
99-
return f.imageCreateFunc(parentReference, options)
99+
return f.imageCreateFunc(ctx, parentReference, options)
100100
}
101101
return nil, nil
102102
}

cli/command/container/create_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
133133
return container.CreateResponse{ID: containerID}, nil
134134
}
135135
},
136-
imageCreateFunc: func(parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
136+
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
137137
defer func() { pullCounter++ }()
138138
return io.NopCloser(strings.NewReader("")), nil
139139
},

cli/command/container/run.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,6 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
112112

113113
//nolint:gocyclo
114114
func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
115-
ctx = context.WithoutCancel(ctx)
116-
117115
config := containerCfg.Config
118116
stdout, stderr := dockerCli.Out(), dockerCli.Err()
119117
apiClient := dockerCli.Client()
@@ -135,9 +133,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
135133
config.StdinOnce = false
136134
}
137135

138-
ctx, cancelFun := context.WithCancel(ctx)
139-
defer cancelFun()
140-
141136
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
142137
if err != nil {
143138
reportError(stderr, "run", err.Error(), true)
@@ -154,6 +149,9 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
154149
defer signal.StopCatch(sigc)
155150
}
156151

152+
ctx, cancelFun := context.WithCancel(context.WithoutCancel(ctx))
153+
defer cancelFun()
154+
157155
var (
158156
waitDisplayID chan struct{}
159157
errCh chan error

cli/command/container/run_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package container
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
7+
"fmt"
68
"io"
79
"net"
810
"syscall"
@@ -16,7 +18,9 @@ import (
1618
"github.com/docker/cli/internal/test/notary"
1719
"github.com/docker/docker/api/types"
1820
"github.com/docker/docker/api/types/container"
21+
"github.com/docker/docker/api/types/image"
1922
"github.com/docker/docker/api/types/network"
23+
"github.com/docker/docker/pkg/jsonmessage"
2024
specs "github.com/opencontainers/image-spec/specs-go/v1"
2125
"github.com/spf13/pflag"
2226
"gotest.tools/v3/assert"
@@ -189,6 +193,87 @@ func TestRunAttachTermination(t *testing.T) {
189193
}
190194
}
191195

196+
func TestRunPullTermination(t *testing.T) {
197+
ctx, cancel := context.WithCancel(context.Background())
198+
t.Cleanup(cancel)
199+
200+
attachCh := make(chan struct{})
201+
fakeCLI := test.NewFakeCli(&fakeClient{
202+
createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig,
203+
platform *specs.Platform, containerName string,
204+
) (container.CreateResponse, error) {
205+
select {
206+
case <-ctx.Done():
207+
return container.CreateResponse{}, ctx.Err()
208+
default:
209+
}
210+
return container.CreateResponse{}, fakeNotFound{}
211+
},
212+
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
213+
return types.HijackedResponse{}, errors.New("shouldn't try to attach to a container")
214+
},
215+
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
216+
server, client := net.Pipe()
217+
t.Cleanup(func() {
218+
_ = server.Close()
219+
})
220+
go func() {
221+
enc := json.NewEncoder(server)
222+
for i := 0; i < 100; i++ {
223+
select {
224+
case <-ctx.Done():
225+
assert.NilError(t, server.Close(), "failed to close imageCreateFunc server")
226+
return
227+
default:
228+
}
229+
assert.NilError(t, enc.Encode(jsonmessage.JSONMessage{
230+
Status: "Downloading",
231+
ID: fmt.Sprintf("id-%d", i),
232+
TimeNano: time.Now().UnixNano(),
233+
Time: time.Now().Unix(),
234+
Progress: &jsonmessage.JSONProgress{
235+
Current: int64(i),
236+
Total: 100,
237+
Start: 0,
238+
},
239+
}))
240+
time.Sleep(100 * time.Millisecond)
241+
}
242+
}()
243+
attachCh <- struct{}{}
244+
return client, nil
245+
},
246+
Version: "1.30",
247+
})
248+
249+
cmd := NewRunCommand(fakeCLI)
250+
cmd.SetOut(io.Discard)
251+
cmd.SetErr(io.Discard)
252+
cmd.SetArgs([]string{"foobar:latest"})
253+
254+
cmdErrC := make(chan error, 1)
255+
go func() {
256+
cmdErrC <- cmd.ExecuteContext(ctx)
257+
}()
258+
259+
select {
260+
case <-time.After(5 * time.Second):
261+
t.Fatal("imageCreateFunc was not called before the timeout")
262+
case <-attachCh:
263+
}
264+
265+
cancel()
266+
267+
select {
268+
case cmdErr := <-cmdErrC:
269+
assert.Equal(t, cmdErr, cli.StatusError{
270+
StatusCode: 125,
271+
})
272+
case <-time.After(10 * time.Second):
273+
t.Fatal("cmd did not return before the timeout")
274+
}
275+
}
276+
192277
func TestRunCommandWithContentTrustErrors(t *testing.T) {
193278
testCases := []struct {
194279
name string

0 commit comments

Comments
 (0)