diff --git a/services/packages/packages.go b/services/packages/packages.go index 56d5cc04de2df..b003ccfc9da9a 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -11,6 +11,7 @@ import ( "io" "net/url" "strings" + "sync" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" @@ -31,6 +32,17 @@ var ( ErrQuotaTotalCount = errors.New("maximum allowed package count exceeded") ) +var mutexMap sync.Map + +func getOrCreateMutex(name string) *sync.Mutex { + mutex, ok := mutexMap.Load(name) + if !ok { + newMutex := &sync.Mutex{} + mutex, _ = mutexMap.LoadOrStore(name, newMutex) + } + return mutex.(*sync.Mutex) +} + // PackageInfo describes a package type PackageInfo struct { Owner *user_model.User @@ -76,6 +88,13 @@ func CreatePackageOrAddFileToExisting(ctx context.Context, pvci *PackageCreation } func createPackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, *packages_model.PackageFile, error) { + + mutex := getOrCreateMutex(pvci.PackageType.Name()) + mutex.Lock() + defer func() { + mutex.Unlock() + }() + dbCtx, committer, err := db.TxContext(ctx) if err != nil { return nil, nil, err diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 81112f305a62e..1049a1d523105 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" "strings" + "sync" "testing" "code.gitea.io/gitea/models/db" @@ -242,3 +243,35 @@ func TestPackageMaven(t *testing.T) { putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated) }) } + +func TestPackageMavenConcurrent(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + groupID := "com.gitea" + artifactID := "test-project" + packageVersion := "1.0.1" + + root := fmt.Sprintf("/api/packages/%s/maven/%s/%s", user.Name, strings.ReplaceAll(groupID, ".", "/"), artifactID) + + putFile := func(t *testing.T, path, content string, expectedStatus int) { + req := NewRequestWithBody(t, "PUT", root+path, strings.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, expectedStatus) + } + + t.Run("Concurrent Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated) + wg.Done() + }(i) + } + wg.Wait() + }) +}