Skip to content

Commit 87e13b2

Browse files
committed
[debian] mirror: fix race conditions
* load data inside background tasks Perform collection.LoadComplete inside maybeRunTaskInBackground Have tasks use a fresh copy of taskCollectionFactory, taskCollection
1 parent 417ef48 commit 87e13b2

1 file changed

Lines changed: 47 additions & 14 deletions

File tree

api/mirror.go

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,9 @@ func apiMirrorsDrop(c *gin.Context) {
175175
name := c.Params.ByName("name")
176176
force := c.Request.URL.Query().Get("force") == "1"
177177

178+
// Phase 1: Pre-task validation (shallow load for 404 check only)
178179
collectionFactory := context.NewCollectionFactory()
179180
mirrorCollection := collectionFactory.RemoteRepoCollection()
180-
snapshotCollection := collectionFactory.SnapshotCollection()
181181

182182
repo, err := mirrorCollection.ByName(name)
183183
if err != nil {
@@ -187,21 +187,34 @@ func apiMirrorsDrop(c *gin.Context) {
187187

188188
resources := []string{string(repo.Key())}
189189
taskName := fmt.Sprintf("Delete mirror %s", name)
190+
190191
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
191-
err := repo.CheckLock()
192+
// Phase 2: Inside task lock - create fresh collections
193+
taskCollectionFactory := context.NewCollectionFactory()
194+
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
195+
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
196+
197+
// Fresh load after lock acquired
198+
repo, err := taskMirrorCollection.ByName(name)
199+
if err != nil {
200+
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
201+
}
202+
203+
err = repo.CheckLock()
192204
if err != nil {
193205
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
194206
}
195207

196208
if !force {
197-
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
209+
// Fresh checks with current collections
210+
snapshots := taskSnapshotCollection.ByRemoteRepoSource(repo)
198211

199212
if len(snapshots) > 0 {
200213
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
201214
}
202215
}
203216

204-
err = mirrorCollection.Drop(repo)
217+
err = taskMirrorCollection.Drop(repo)
205218
if err != nil {
206219
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
207220
}
@@ -232,6 +245,7 @@ func apiMirrorsShow(c *gin.Context) {
232245
err = collection.LoadComplete(repo)
233246
if err != nil {
234247
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
248+
return
235249
}
236250

237251
c.JSON(200, repo)
@@ -369,7 +383,8 @@ func apiMirrorsUpdate(c *gin.Context) {
369383
collectionFactory := context.NewCollectionFactory()
370384
collection := collectionFactory.RemoteRepoCollection()
371385

372-
remote, err = collection.ByName(c.Params.ByName("name"))
386+
name := c.Params.ByName("name")
387+
remote, err = collection.ByName(name)
373388
if err != nil {
374389
AbortWithJSONError(c, 404, err)
375390
return
@@ -384,6 +399,7 @@ func apiMirrorsUpdate(c *gin.Context) {
384399
return
385400
}
386401

402+
// Pre-task validation of new name if provided
387403
if b.Name != remote.Name {
388404
_, err = collection.ByName(b.Name)
389405
if err == nil {
@@ -400,9 +416,26 @@ func apiMirrorsUpdate(c *gin.Context) {
400416

401417
resources := []string{string(remote.Key())}
402418
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
419+
// Phase 2: Inside task lock - create fresh factory
420+
taskCollectionFactory := context.NewCollectionFactory()
421+
taskCollection := taskCollectionFactory.RemoteRepoCollection()
422+
423+
// Fresh load after lock acquired (use captured `name` variable, not gin context)
424+
remote, err := taskCollection.ByName(name)
425+
if err != nil {
426+
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
427+
}
428+
429+
// Fresh rename check inside lock (if renaming)
430+
if b.Name != remote.Name {
431+
_, err := taskCollection.ByName(b.Name)
432+
if err == nil {
433+
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)
434+
}
435+
}
403436

404437
downloader := context.NewDownloader(out)
405-
err := remote.Fetch(downloader, verifier, b.IgnoreSignatures)
438+
err = remote.Fetch(downloader, verifier, b.IgnoreSignatures)
406439
if err != nil {
407440
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
408441
}
@@ -414,7 +447,7 @@ func apiMirrorsUpdate(c *gin.Context) {
414447
}
415448
}
416449

417-
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
450+
err = remote.DownloadPackageIndexes(out, downloader, verifier, taskCollectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
418451
if err != nil {
419452
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
420453
}
@@ -433,8 +466,8 @@ func apiMirrorsUpdate(c *gin.Context) {
433466
}
434467
}
435468

436-
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
437-
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
469+
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), taskCollectionFactory.PackageCollection(),
470+
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
438471
if err != nil {
439472
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
440473
}
@@ -444,12 +477,12 @@ func apiMirrorsUpdate(c *gin.Context) {
444477
e := context.ReOpenDatabase()
445478
if e == nil {
446479
remote.MarkAsIdle()
447-
_ = collection.Update(remote)
480+
_ = taskCollection.Update(remote)
448481
}
449482
}()
450483

451484
remote.MarkAsUpdating()
452-
err = collection.Update(remote)
485+
err = taskCollection.Update(remote)
453486
if err != nil {
454487
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
455488
}
@@ -553,7 +586,7 @@ func apiMirrorsUpdate(c *gin.Context) {
553586
}
554587

555588
// and import it back to the pool
556-
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
589+
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, taskCollectionFactory.ChecksumCollection(nil))
557590
if err != nil {
558591
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
559592
pushError(err)
@@ -606,8 +639,8 @@ func apiMirrorsUpdate(c *gin.Context) {
606639
}
607640

608641
log.Info().Msgf("%s: Finalizing download...", b.Name)
609-
_ = remote.FinalizeDownload(collectionFactory, out)
610-
err = collectionFactory.RemoteRepoCollection().Update(remote)
642+
_ = remote.FinalizeDownload(taskCollectionFactory, out)
643+
err = taskCollection.Update(remote)
611644
if err != nil {
612645
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
613646
}

0 commit comments

Comments
 (0)