Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 341b254

Browse files
authored
Merge pull request #12127 from k8s-infra-cherrypick-robot/cherry-pick-12126-to-release/2.1
[release/2.1] fix(dockerFetcher): resolve deadlock issue in dockerFetcher open
2 parents 1a989e4 + add2dcf commit 341b254

File tree

2 files changed

+62
-18
lines changed

2 files changed

+62
-18
lines changed

core/remotes/docker/fetcher.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,18 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
474474
return nil, err
475475
}
476476

477-
body := resp.Body
477+
body := &fnOnClose{
478+
BeforeClose: func() {
479+
r.Release(1)
480+
},
481+
ReadCloser: resp.Body,
482+
}
483+
defer func() {
484+
if retErr != nil {
485+
body.Close()
486+
}
487+
}()
488+
478489
encoding := strings.FieldsFunc(resp.Header.Get("Content-Encoding"), func(r rune) bool {
479490
return r == ' ' || r == '\t' || r == ','
480491
})
@@ -505,29 +516,33 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
505516
for i := range numChunks {
506517
readers[i], writers[i] = newPipeWriter(bufPool)
507518
}
519+
// keep reference of the initial body value to ensure it is closed
520+
ibody := body
508521
go func() {
509522
for i := range numChunks {
510523
select {
511524
case queue <- i:
512525
case <-done:
526+
if i == 0 {
527+
ibody.Close()
528+
}
513529
return // avoid leaking a goroutine if we exit early.
514530
}
515531
}
516532
close(queue)
517533
}()
518-
r.Release(1)
519534
for range parallelism {
520535
go func() {
521536
for i := range queue { // first in first out
522537
copy := func() error {
523-
if err := r.Acquire(ctx, 1); err != nil {
524-
return err
525-
}
526-
defer r.Release(1)
527538
var body io.ReadCloser
528539
if i == 0 {
529-
body = resp.Body
540+
body = ibody
530541
} else {
542+
if err := r.Acquire(ctx, 1); err != nil {
543+
return err
544+
}
545+
defer r.Release(1)
531546
reqClone := req.clone()
532547
reqClone.setOffset(offset + i*chunkSize)
533548
nresp, err := reqClone.doWithRetries(ctx, lastHost, withErrorCheck)
@@ -564,32 +579,27 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
564579
},
565580
ReadCloser: io.NopCloser(io.MultiReader(readers...)),
566581
}
567-
} else {
568-
body = &fnOnClose{
569-
BeforeClose: func() {
570-
r.Release(1)
571-
},
572-
ReadCloser: body,
573-
}
574582
}
583+
575584
for i := len(encoding) - 1; i >= 0; i-- {
576585
algorithm := strings.ToLower(encoding[i])
577586
switch algorithm {
578587
case "zstd":
579-
r, err := zstd.NewReader(body,
588+
r, err := zstd.NewReader(body.ReadCloser,
580589
zstd.WithDecoderLowmem(false),
581590
)
582591
if err != nil {
583592
return nil, err
584593
}
585-
body = r.IOReadCloser()
594+
body.ReadCloser = r.IOReadCloser()
586595
case "gzip":
587-
body, err = gzip.NewReader(body)
596+
r, err := gzip.NewReader(body.ReadCloser)
588597
if err != nil {
589598
return nil, err
590599
}
600+
body.ReadCloser = r
591601
case "deflate":
592-
body = flate.NewReader(body)
602+
body.ReadCloser = flate.NewReader(body.ReadCloser)
593603
case "identity", "":
594604
// no content-encoding applied, use raw body
595605
default:

core/remotes/docker/fetcher_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,40 @@ func TestDockerFetcherOpen(t *testing.T) {
559559
}
560560
}
561561

562+
func TestDockerFetcherOpenLimiterDeadlock(t *testing.T) {
563+
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
564+
rw.Header().Set("Content-Encoding", "gzip")
565+
rw.Write([]byte("bad gzip data"))
566+
rw.WriteHeader(http.StatusOK)
567+
}))
568+
defer s.Close()
569+
570+
u, err := url.Parse(s.URL)
571+
if err != nil {
572+
t.Fatal(err)
573+
}
574+
575+
f := dockerFetcher{&dockerBase{
576+
repository: "ns",
577+
limiter: semaphore.NewWeighted(int64(1)),
578+
}}
579+
580+
host := RegistryHost{
581+
Client: s.Client(),
582+
Host: u.Host,
583+
Scheme: u.Scheme,
584+
Path: u.Path,
585+
}
586+
587+
req := f.request(host, http.MethodGet)
588+
_, err = f.open(context.Background(), req, "", 0, true)
589+
assert.Error(t, err)
590+
591+
// verify that the limiter Release has been successfully called when the last open error occurred
592+
_, err = f.open(context.Background(), req, "", 0, true)
593+
assert.Error(t, err)
594+
}
595+
562596
// httpRange specifies the byte range to be sent to the client.
563597
type httpRange struct {
564598
start, length int64

0 commit comments

Comments
 (0)