Skip to content

Commit ad0ee26

Browse files
KN4CK3Rlunny
andcommitted
Workaround for container registry push/pull errors (go-gitea#21862)
This PR addresses go-gitea#19586 I added a mutex to the upload version creation which will prevent the push errors when two requests try to create these database entries. I'm not sure if this should be the final solution for this problem. I added a workaround to allow a reupload of missing blobs. Normally a reupload is skipped because the database knows the blob is already present. The workaround checks if the blob exists on the file system. This should not be needed anymore with the above fix so I marked this code to be removed with Gitea v1.20. Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 parent e93a4a0 commit ad0ee26

File tree

5 files changed

+100
-4
lines changed

5 files changed

+100
-4
lines changed

integrations/api_packages_container_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ package integrations
66

77
import (
88
"bytes"
9+
"crypto/sha256"
910
"encoding/base64"
1011
"fmt"
1112
"net/http"
1213
"strings"
14+
"sync"
1315
"testing"
1416

1517
"code.gitea.io/gitea/models/db"
@@ -549,6 +551,32 @@ func TestPackageContainer(t *testing.T) {
549551
})
550552
}
551553

554+
// https://github.com/go-gitea/gitea/issues/19586
555+
t.Run("ParallelUpload", func(t *testing.T) {
556+
defer PrintCurrentTest(t)()
557+
558+
url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name)
559+
560+
var wg sync.WaitGroup
561+
for i := 0; i < 10; i++ {
562+
wg.Add(1)
563+
564+
content := []byte{byte(i)}
565+
digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content))
566+
567+
go func() {
568+
defer wg.Done()
569+
570+
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, digest), bytes.NewReader(content))
571+
addTokenAuthHeader(req, userToken)
572+
resp := MakeRequest(t, req, http.StatusCreated)
573+
574+
assert.Equal(t, digest, resp.Header().Get("Docker-Content-Digest"))
575+
}()
576+
}
577+
wg.Wait()
578+
})
579+
552580
t.Run("OwnerNameChange", func(t *testing.T) {
553581
defer PrintCurrentTest(t)()
554582

modules/packages/content_store.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
3030
return s.store.Open(KeyToRelativePath(key))
3131
}
3232

33+
// FIXME: Workaround to be removed in v1.20
34+
// https://github.com/go-gitea/gitea/issues/19586
35+
func (s *ContentStore) Has(key BlobHash256Key) error {
36+
_, err := s.store.Stat(KeyToRelativePath(key))
37+
return err
38+
}
39+
3340
// Save stores a package blob
3441
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
3542
_, err := s.store.Save(KeyToRelativePath(key), r, size)

routers/api/packages/container/blob.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ package container
77
import (
88
"context"
99
"encoding/hex"
10+
"errors"
1011
"fmt"
12+
"os"
1113
"strings"
14+
"sync"
1215

1316
"code.gitea.io/gitea/models/db"
1417
packages_model "code.gitea.io/gitea/models/packages"
@@ -19,6 +22,8 @@ import (
1922
packages_service "code.gitea.io/gitea/services/packages"
2023
)
2124

25+
var uploadVersionMutex sync.Mutex
26+
2227
// saveAsPackageBlob creates a package blob from an upload
2328
// The uploaded blob gets stored in a special upload version to link them to the package/image
2429
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
@@ -28,6 +33,11 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
2833

2934
contentStore := packages_module.NewContentStore()
3035

36+
var uploadVersion *packages_model.PackageVersion
37+
38+
// FIXME: Replace usage of mutex with database transaction
39+
// https://github.com/go-gitea/gitea/pull/21862
40+
uploadVersionMutex.Lock()
3141
err := db.WithTx(func(ctx context.Context) error {
3242
created := true
3343
p := &packages_model.Package{
@@ -68,11 +78,30 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
6878
}
6979
}
7080

81+
uploadVersion = pv
82+
83+
return nil
84+
})
85+
uploadVersionMutex.Unlock()
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
err = db.WithTx(func(ctx context.Context) error {
7191
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
7292
if err != nil {
7393
log.Error("Error inserting package blob: %v", err)
7494
return err
7595
}
96+
// FIXME: Workaround to be removed in v1.20
97+
// https://github.com/go-gitea/gitea/issues/19586
98+
if exists {
99+
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
100+
if err != nil && errors.Is(err, os.ErrNotExist) {
101+
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
102+
exists = false
103+
}
104+
}
76105
if !exists {
77106
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
78107
log.Error("Error saving package blob in content store: %v", err)
@@ -83,7 +112,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
83112
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
84113

85114
pf := &packages_model.PackageFile{
86-
VersionID: pv.ID,
115+
VersionID: uploadVersion.ID,
87116
BlobID: pb.ID,
88117
Name: filename,
89118
LowerName: filename,

routers/api/packages/container/container.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"net/http"
1212
"net/url"
13+
"os"
1314
"regexp"
1415
"strconv"
1516
"strings"
@@ -193,7 +194,7 @@ func InitiateUploadBlob(ctx *context.Context) {
193194
mount := ctx.FormTrim("mount")
194195
from := ctx.FormTrim("from")
195196
if mount != "" {
196-
blob, _ := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
197+
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
197198
Image: from,
198199
Digest: mount,
199200
})
@@ -361,7 +362,7 @@ func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescri
361362
return nil, container_model.ErrContainerBlobNotExist
362363
}
363364

364-
return container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
365+
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
365366
OwnerID: ctx.Package.Owner.ID,
366367
Image: ctx.Params("image"),
367368
Digest: digest,
@@ -503,7 +504,7 @@ func getManifestFromContext(ctx *context.Context) (*packages_model.PackageFileDe
503504
return nil, container_model.ErrContainerBlobNotExist
504505
}
505506

506-
return container_model.GetContainerBlob(ctx, opts)
507+
return workaroundGetContainerBlob(ctx, opts)
507508
}
508509

509510
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#checking-if-content-exists-in-the-registry
@@ -643,3 +644,23 @@ func GetTagList(ctx *context.Context) {
643644
Tags: tags,
644645
})
645646
}
647+
648+
// FIXME: Workaround to be removed in v1.20
649+
// https://github.com/go-gitea/gitea/issues/19586
650+
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
651+
blob, err := container_model.GetContainerBlob(ctx, opts)
652+
if err != nil {
653+
return nil, err
654+
}
655+
656+
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(blob.Blob.HashSHA256))
657+
if err != nil {
658+
if errors.Is(err, os.ErrNotExist) {
659+
log.Debug("Package registry inconsistent: blob %s does not exist on file system", blob.Blob.HashSHA256)
660+
return nil, container_model.ErrContainerBlobNotExist
661+
}
662+
return nil, err
663+
}
664+
665+
return blob, nil
666+
}

routers/api/packages/container/manifest.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ package container
66

77
import (
88
"context"
9+
"errors"
910
"fmt"
1011
"io"
12+
"os"
1113
"strings"
1214

1315
"code.gitea.io/gitea/models/db"
@@ -403,6 +405,15 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
403405
log.Error("Error inserting package blob: %v", err)
404406
return nil, false, "", err
405407
}
408+
// FIXME: Workaround to be removed in v1.20
409+
// https://github.com/go-gitea/gitea/issues/19586
410+
if exists {
411+
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
412+
if err != nil && errors.Is(err, os.ErrNotExist) {
413+
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
414+
exists = false
415+
}
416+
}
406417
if !exists {
407418
contentStore := packages_module.NewContentStore()
408419
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {

0 commit comments

Comments
 (0)