From 9280231c1d4a4d39ce4098af0ef599ea58089ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 12:49:16 +0200 Subject: [PATCH 1/9] publish: debug locking --- api/publish.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/publish.go b/api/publish.go index 334a27a26..3c796b45a 100644 --- a/api/publish.go +++ b/api/publish.go @@ -515,6 +515,8 @@ func apiPublishUpdateSwitch(c *gin.Context) { published.Version = *b.Version } + + fmt.Printf("apiPublishUpdateSwitch: %s\n", string(published.Key())) resources := []string{string(published.Key())} taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { From 55b2943f447c62b88c800c07ad822559c9c4caa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 13:33:56 +0200 Subject: [PATCH 2/9] more debug --- task/list.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/task/list.go b/task/list.go index 5b9e9395e..e46e983fc 100644 --- a/task/list.go +++ b/task/list.go @@ -65,6 +65,7 @@ func (list *List) consumer() { task.State = SUCCEEDED } + fmt.Printf("RACE DEBUG: Task %s done, freeing %s\n", task.Name, task.resources) list.usedResources.Free(task.resources) task.wgTask.Done() @@ -77,6 +78,8 @@ func (list *List) consumer() { blockingTasks := list.usedResources.UsedBy(t.resources) if len(blockingTasks) == 0 { list.usedResources.MarkInUse(t.resources, t) + + fmt.Printf("RACE DEBUG: Starting queued task %s, using %s\n", t.Name, t.resources) // unlock list since queueing may block list.Unlock() unlocked = true @@ -209,10 +212,12 @@ func (list *List) RunTaskInBackground(name string, resources []string, process P tasks := list.usedResources.UsedBy(resources) if len(tasks) == 0 { list.usedResources.MarkInUse(task.resources, task) + fmt.Printf("RACE DEBUG: Starting task %s, using %s\n", name, resources) // queueing task might block if channel not ready, unlock list before queueing list.Unlock() list.queue <- task } else { + fmt.Printf("RACE DEBUG: Queued task %s, locked %s\n", name, resources) list.Unlock() } From f8efb3e9b7ce95024bc8ca97d3bdfb14841066b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 16:12:54 +0200 Subject: [PATCH 3/9] publish update: lock all snapshots and repos as well --- api/publish.go | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/api/publish.go b/api/publish.go index 3c796b45a..649486f18 100644 --- a/api/publish.go +++ b/api/publish.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "strings" + "errors" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/deb" @@ -465,18 +466,54 @@ func apiPublishUpdateSwitch(c *gin.Context) { return } + resources := []string{string(published.Key())} + if published.SourceKind == deb.SourceLocalRepo { if len(b.Snapshots) > 0 { AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo")) return } + + // FIXME: lock repo ? + // localCollection := collectionFactory.LocalRepoCollection() + // for _, source := range b.Sources { + // components = append(components, source.Component) + // names = append(names, source.Name) + + // localRepo, err = localCollection.ByName(source.Name) + // if err != nil { + // AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) + // return + // } + + // resources = append(resources, string(localRepo.Key())) + // } } else if published.SourceKind == deb.SourceSnapshot { for _, snapshotInfo := range b.Snapshots { - _, err2 := snapshotCollection.ByName(snapshotInfo.Name) + snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name) if err2 != nil { AbortWithJSONError(c, http.StatusNotFound, err2) return } + resources = append(resources, string(snapshot.ResourceKey())) + // for repo := snapshot.LocalRepos { + // } + + fmt.Printf("RACE DEBUG: source ids: %s\n", snapshot.SourceIDs) + for _, sourceID := range snapshot.SourceIDs { + if snapshot.SourceKind == deb.SourceSnapshot { + // FIXME: implement + err := errors.New("not implemented") + AbortWithJSONError(c, http.StatusNotFound, err) + } else if snapshot.SourceKind == deb.SourceLocalRepo { + var repo *deb.LocalRepo + repo, err = context.NewCollectionFactory().LocalRepoCollection().ByUUID(sourceID) + if err != nil { + AbortWithJSONError(c, http.StatusNotFound, err2) + } + resources = append(resources, string(repo.Key())) + } + } } } else { AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type")) @@ -515,9 +552,6 @@ func apiPublishUpdateSwitch(c *gin.Context) { published.Version = *b.Version } - - fmt.Printf("apiPublishUpdateSwitch: %s\n", string(published.Key())) - resources := []string{string(published.Key())} taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution) maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) { err = collection.LoadComplete(published, collectionFactory) From 8179f73bf0db1cbb40c69ddd72e50b3d892281b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 17:19:15 +0200 Subject: [PATCH 4/9] publish: cleanup --- api/publish.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/api/publish.go b/api/publish.go index 649486f18..2b4c957ed 100644 --- a/api/publish.go +++ b/api/publish.go @@ -256,13 +256,13 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { if b.SourceKind == deb.SourceSnapshot { var snapshot *deb.Snapshot - snapshotCollection := collectionFactory.SnapshotCollection() + tmpCollection := collectionFactory.SnapshotCollection() for _, source := range b.Sources { components = append(components, source.Component) names = append(names, source.Name) - snapshot, err = snapshotCollection.ByName(source.Name) + snapshot, err = tmpCollection.ByName(source.Name) if err != nil { AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) return @@ -274,13 +274,13 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { } else if b.SourceKind == deb.SourceLocalRepo { var localRepo *deb.LocalRepo - localCollection := collectionFactory.LocalRepoCollection() + tmpCollection := collectionFactory.LocalRepoCollection() for _, source := range b.Sources { components = append(components, source.Component) names = append(names, source.Name) - localRepo, err = localCollection.ByName(source.Name) + localRepo, err = tmpCollection.ByName(source.Name) if err != nil { AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) return @@ -496,10 +496,7 @@ func apiPublishUpdateSwitch(c *gin.Context) { return } resources = append(resources, string(snapshot.ResourceKey())) - // for repo := snapshot.LocalRepos { - // } - fmt.Printf("RACE DEBUG: source ids: %s\n", snapshot.SourceIDs) for _, sourceID := range snapshot.SourceIDs { if snapshot.SourceKind == deb.SourceSnapshot { // FIXME: implement From 41f5d2263755d8d5ef0965d6cc991079740dd1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 17:19:36 +0200 Subject: [PATCH 5/9] publish: remove useless ressource assignment --- api/publish.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/publish.go b/api/publish.go index 2b4c957ed..41311c26c 100644 --- a/api/publish.go +++ b/api/publish.go @@ -333,8 +333,6 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err) } - resources = append(resources, string(published.Key())) - if b.Origin != "" { published.Origin = b.Origin } From 6fbcbc108c60ba553093045672e36cc59d364643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 18:40:22 +0200 Subject: [PATCH 6/9] more debug --- api/publish.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/publish.go b/api/publish.go index 41311c26c..f13e0b629 100644 --- a/api/publish.go +++ b/api/publish.go @@ -471,6 +471,7 @@ func apiPublishUpdateSwitch(c *gin.Context) { AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo")) return } + fmt.Printf("RACE DEBUG: deb.SourceLocalRepo\n") // FIXME: lock repo ? // localCollection := collectionFactory.LocalRepoCollection() @@ -487,6 +488,7 @@ func apiPublishUpdateSwitch(c *gin.Context) { // resources = append(resources, string(localRepo.Key())) // } } else if published.SourceKind == deb.SourceSnapshot { + fmt.Printf("RACE DEBUG: deb.SourceSnapshot: %s\n", b.Snapshots) for _, snapshotInfo := range b.Snapshots { snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name) if err2 != nil { @@ -500,11 +502,13 @@ func apiPublishUpdateSwitch(c *gin.Context) { // FIXME: implement err := errors.New("not implemented") AbortWithJSONError(c, http.StatusNotFound, err) + return } else if snapshot.SourceKind == deb.SourceLocalRepo { var repo *deb.LocalRepo repo, err = context.NewCollectionFactory().LocalRepoCollection().ByUUID(sourceID) if err != nil { - AbortWithJSONError(c, http.StatusNotFound, err2) + AbortWithJSONError(c, http.StatusNotFound, err) + return } resources = append(resources, string(repo.Key())) } From 4defa49b7f7c7e73ac914c4a35bf1f8754580d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 20:48:05 +0200 Subject: [PATCH 7/9] publish: lock resources from all SourceKinds --- api/publish.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/publish.go b/api/publish.go index f13e0b629..7de257db0 100644 --- a/api/publish.go +++ b/api/publish.go @@ -500,7 +500,7 @@ func apiPublishUpdateSwitch(c *gin.Context) { for _, sourceID := range snapshot.SourceIDs { if snapshot.SourceKind == deb.SourceSnapshot { // FIXME: implement - err := errors.New("not implemented") + err := errors.New("not implemented deb.SourceSnapshot") AbortWithJSONError(c, http.StatusNotFound, err) return } else if snapshot.SourceKind == deb.SourceLocalRepo { @@ -511,6 +511,11 @@ func apiPublishUpdateSwitch(c *gin.Context) { return } resources = append(resources, string(repo.Key())) + } else if snapshot.SourceKind == deb.SourceRemoteRepo { + // FIXME: implement + err := errors.New("not implemented: deb.SourceRemoteRepo") + AbortWithJSONError(c, http.StatusNotFound, err) + return } } } From 5ff552d919d0e08b95b2dfc7c1d935edb2efafd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 4 May 2026 23:56:01 +0200 Subject: [PATCH 8/9] publish: fix locking of snapshots snapshots come in 3 kinds: local, remote and snapshot find resources to be locked for each kind, recursively for the snapshot kind --- api/publish.go | 106 ++++++++++-------- system/t12_api/publish.py | 226 ++++++++++++++++++++++++++++++++++++++ task/list.go | 8 +- 3 files changed, 288 insertions(+), 52 deletions(-) diff --git a/api/publish.go b/api/publish.go index 7de257db0..8c3672d3b 100644 --- a/api/publish.go +++ b/api/publish.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "strings" - "errors" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/deb" @@ -386,6 +385,46 @@ func apiPublishRepoOrSnapshot(c *gin.Context) { }) } +// Return resources to be locked for a Snapshot name +func getSnapshotResources(snapshotCollection *deb.SnapshotCollection, snapshotName string) (resources []string, err error) { + snapshot, err := snapshotCollection.ByName(snapshotName) + if err != nil { + return + } + resources = append(resources, string(snapshot.ResourceKey())) + + for _, sourceID := range snapshot.SourceIDs { + if snapshot.SourceKind == deb.SourceSnapshot { + snapshot2, err2 := snapshotCollection.ByUUID(sourceID) + if err2 != nil { + err = err2 + return + } + res, err3 := getSnapshotResources(snapshotCollection, snapshot2.Name) + if err3 != nil { + err = err3 + return + } + resources = append(resources, res...) + } else if snapshot.SourceKind == deb.SourceLocalRepo { + var repo *deb.LocalRepo + repo, err = context.NewCollectionFactory().LocalRepoCollection().ByUUID(sourceID) + if err != nil { + return + } + resources = append(resources, string(repo.Key())) + } else if snapshot.SourceKind == deb.SourceRemoteRepo { + var mirror *deb.RemoteRepo + mirror, err = context.NewCollectionFactory().RemoteRepoCollection().ByUUID(sourceID) + if err != nil { + return + } + resources = append(resources, string(mirror.Key())) + } + } + return +} + type publishedRepoUpdateSwitchParams struct { // when publishing, overwrite files in pool/ directory without notice ForceOverwrite bool ` json:"ForceOverwrite" example:"false"` @@ -405,12 +444,12 @@ type publishedRepoUpdateSwitchParams struct { SignedBy *string ` json:"SignedBy" example:""` // Enable multiple packages with the same filename in different distributions MultiDist *bool ` json:"MultiDist" example:"false"` - // Value of Label: field in published repository stanza - Label *string ` json:"Label" example:"Debian"` - // Value of Origin: field in published repository stanza - Origin *string ` json:"Origin" example:"Debian"` - // Version of the release: Optional - Version *string ` json:"Version" example:"13.3"` + // Value of Label: field in published repository stanza + Label *string ` json:"Label" example:"Debian"` + // Value of Origin: field in published repository stanza + Origin *string ` json:"Origin" example:"Debian"` + // Version of the release: Optional + Version *string ` json:"Version" example:"13.3"` } // @Summary Update Published Repository @@ -471,53 +510,24 @@ func apiPublishUpdateSwitch(c *gin.Context) { AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo")) return } - fmt.Printf("RACE DEBUG: deb.SourceLocalRepo\n") - - // FIXME: lock repo ? - // localCollection := collectionFactory.LocalRepoCollection() - // for _, source := range b.Sources { - // components = append(components, source.Component) - // names = append(names, source.Name) - - // localRepo, err = localCollection.ByName(source.Name) - // if err != nil { - // AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to publish: %s", err)) - // return - // } - - // resources = append(resources, string(localRepo.Key())) - // } + + localCollection := collectionFactory.LocalRepoCollection() + for _, sourceID := range published.Sources { + localRepo, err2 := localCollection.ByUUID(sourceID) + if err2 != nil { + AbortWithJSONError(c, http.StatusNotFound, err2) + return + } + resources = append(resources, string(localRepo.Key())) + } } else if published.SourceKind == deb.SourceSnapshot { - fmt.Printf("RACE DEBUG: deb.SourceSnapshot: %s\n", b.Snapshots) for _, snapshotInfo := range b.Snapshots { - snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name) + res, err2 := getSnapshotResources(snapshotCollection, snapshotInfo.Name) if err2 != nil { AbortWithJSONError(c, http.StatusNotFound, err2) return } - resources = append(resources, string(snapshot.ResourceKey())) - - for _, sourceID := range snapshot.SourceIDs { - if snapshot.SourceKind == deb.SourceSnapshot { - // FIXME: implement - err := errors.New("not implemented deb.SourceSnapshot") - AbortWithJSONError(c, http.StatusNotFound, err) - return - } else if snapshot.SourceKind == deb.SourceLocalRepo { - var repo *deb.LocalRepo - repo, err = context.NewCollectionFactory().LocalRepoCollection().ByUUID(sourceID) - if err != nil { - AbortWithJSONError(c, http.StatusNotFound, err) - return - } - resources = append(resources, string(repo.Key())) - } else if snapshot.SourceKind == deb.SourceRemoteRepo { - // FIXME: implement - err := errors.New("not implemented: deb.SourceRemoteRepo") - AbortWithJSONError(c, http.StatusNotFound, err) - return - } - } + resources = append(resources, res...) } } else { AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type")) diff --git a/system/t12_api/publish.py b/system/t12_api/publish.py index 964a71a9f..a9d5cf0de 100644 --- a/system/t12_api/publish.py +++ b/system/t12_api/publish.py @@ -992,6 +992,232 @@ def check(self): self.check_not_exists("public/" + prefix + "dists/") +class PublishSwitchAPITestMirror(APITest): + """ + PUT /publish/:prefix/:distribution (snapshots), DELETE /publish/:prefix/:distribution + """ + fixtureGpg = True + + def check(self): + mirror_name = self.random_name() + mirror_desc = {'Name': mirror_name, + 'ArchiveURL': 'http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/', + 'Distribution': 'wheezy', + 'Keyrings': ["aptlytest.gpg"], + 'Architectures': ["amd64"], + 'Components': ['main']} + mirror_desc['IgnoreSignatures'] = True + + # Create Mirror + resp = self.post("/api/mirrors", json=mirror_desc) + self.check_equal(resp.status_code, 201) + + # Get Mirror + resp = self.get("/api/mirrors/" + mirror_name + "/packages") + self.check_equal(resp.status_code, 404) + + # Update Mirror + resp = self.put_task("/api/mirrors/" + mirror_name, json=mirror_desc) + self.check_task(resp) + + # Snapshot Mirror + snapshot1_name = self.random_name() + task = self.post_task("/api/mirrors/" + mirror_name + '/snapshots', json={'Name': snapshot1_name}) + self.check_task(task) + + # Publish Snapshot + prefix = self.random_name() + task = self.post_task( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "snapshot", + "Sources": [{"Name": snapshot1_name}], + "Signing": DefaultSigningOptions, + }) + self.check_task(task) + + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Origin': 'packagecloud.io/varnishcache/varnish30', + 'Version': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SignedBy': '', + 'SkipContents': False, + 'MultiDist': False, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], + 'Storage': '', + 'Suite': ''} + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + # Snapshot Mirror 2 + snapshot2_name = self.random_name() + task = self.post_task("/api/mirrors/" + mirror_name + '/snapshots', json={'Name': snapshot2_name}) + self.check_task(task) + + task = self.put_task( + "/api/publish/" + prefix + "/wheezy", + json={ + "Snapshots": [{"Component": "main", "Name": snapshot2_name}], + "Signing": DefaultSigningOptions, + "SkipContents": True, + "Label": "fun", + "Origin": "earth", + "Version": "13.3", + }) + self.check_task(task) + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': 'fun', + 'Origin': 'earth', + 'Version': '13.3', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SignedBy': '', + 'SkipContents': True, + 'MultiDist': False, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot2_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + task = self.delete_task("/api/publish/" + prefix + "/wheezy") + self.check_task(task) + self.check_not_exists("public/" + prefix + "dists/") + + +class PublishSwitchAPITestSnapshot(APITest): + """ + publish snapshot of snapshot + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post( + "/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal( + self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + task = self.post_task("/api/repos/" + repo_name + "/file/" + d) + self.check_task(task) + + snapshot1_name = self.random_name() + task = self.post_task("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}) + self.check_task(task) + + prefix = self.random_name() + task = self.post_task( + "/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "snapshot", + "Sources": [{"Name": snapshot1_name}], + "Signing": DefaultSigningOptions, + }) + self.check_task(task) + + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': '', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Origin': '', + 'Version': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SignedBy': '', + 'SkipContents': False, + 'MultiDist': False, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], + 'Storage': '', + 'Suite': ''} + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + self.check_not_exists( + "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_exists("public/" + prefix + + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + snapshot2_name = self.random_name() + task = self.post_task("/api/snapshots", json={"Name": snapshot2_name, 'SourceSnapshots': [snapshot1_name]}) + self.check_task(task) + + task = self.put_task( + "/api/publish/" + prefix + "/wheezy", + json={ + "Snapshots": [{"Component": "main", "Name": snapshot2_name}], + "Signing": DefaultSigningOptions, + "SkipContents": True, + "Label": "fun", + "Origin": "earth", + "Version": "13.3", + }) + self.check_task(task) + repo_expected = { + 'AcquireByHash': False, + 'Architectures': ['i386', 'source'], + 'Codename': '', + 'Distribution': 'wheezy', + 'Label': 'fun', + 'Origin': 'earth', + 'Version': '13.3', + 'NotAutomatic': '', + 'ButAutomaticUpgrades': '', + 'Path': prefix + '/' + 'wheezy', + 'Prefix': prefix, + 'SignedBy': '', + 'SkipContents': True, + 'MultiDist': False, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot2_name}], + 'Storage': '', + 'Suite': ''} + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + # FIXME: what should exist here ? publish snapshot of snapshot + self.check_not_exists( + "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_not_exists("public/" + prefix + + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + task = self.delete_task("/api/publish/" + prefix + "/wheezy") + self.check_task(task) + self.check_not_exists("public/" + prefix + "dists/") + + class PublishSwitchAPITestRepoSignedBy(APITest): """ PUT /publish/:prefix/:distribution (snapshots), DELETE /publish/:prefix/:distribution diff --git a/task/list.go b/task/list.go index e46e983fc..3b127b8eb 100644 --- a/task/list.go +++ b/task/list.go @@ -65,7 +65,7 @@ func (list *List) consumer() { task.State = SUCCEEDED } - fmt.Printf("RACE DEBUG: Task %s done, freeing %s\n", task.Name, task.resources) + fmt.Printf("RACE DEBUG: Task Done '%s', freeing %s\n", task.Name, task.resources) list.usedResources.Free(task.resources) task.wgTask.Done() @@ -79,7 +79,7 @@ func (list *List) consumer() { if len(blockingTasks) == 0 { list.usedResources.MarkInUse(t.resources, t) - fmt.Printf("RACE DEBUG: Starting queued task %s, using %s\n", t.Name, t.resources) + fmt.Printf("RACE DEBUG: Task Resuming '%s', locking %s\n", t.Name, t.resources) // unlock list since queueing may block list.Unlock() unlocked = true @@ -212,12 +212,12 @@ func (list *List) RunTaskInBackground(name string, resources []string, process P tasks := list.usedResources.UsedBy(resources) if len(tasks) == 0 { list.usedResources.MarkInUse(task.resources, task) - fmt.Printf("RACE DEBUG: Starting task %s, using %s\n", name, resources) + fmt.Printf("RACE DEBUG: Task Starting '%s', locking %s\n", name, resources) // queueing task might block if channel not ready, unlock list before queueing list.Unlock() list.queue <- task } else { - fmt.Printf("RACE DEBUG: Queued task %s, locked %s\n", name, resources) + fmt.Printf("RACE DEBUG: Task Queued '%s', waiting on %s\n", name, resources) list.Unlock() } From c77d78849301950f6cda4eab6dae48da09c9562a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Tue, 5 May 2026 11:04:36 +0200 Subject: [PATCH 9/9] publish: lock all distributions with MultiDist --- deb/publish.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deb/publish.go b/deb/publish.go index cc4c2a0fc..e5a72f1b1 100644 --- a/deb/publish.go +++ b/deb/publish.go @@ -609,7 +609,12 @@ func (p *PublishedRepo) StoragePrefix() string { // Key returns unique key identifying PublishedRepo func (p *PublishedRepo) Key() []byte { - return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution) + if p.MultiDist { + // do not lock Distribution in MultiDist + return []byte("UM" + p.StoragePrefix()) + } else { + return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution) + } } // RefKey is a unique id for package reference list