From f06c01d680ba696e5ef2b29d5b36415441d25669 Mon Sep 17 00:00:00 2001 From: "Jonathan A. Sternberg" Date: Thu, 30 Jan 2025 11:19:11 -0600 Subject: [PATCH] bboltcachestorage: delete links outside of cursor The `emptyBranchWithParents` method could accidentally leave link entries that shouldn't exist. When finding these links, deleting during the iteration can sometimes cause the cursor to jump entries that should be deleted. This changes the code path to delete the links outside of the iteration to avoid this. This is caused by a long-standing bug in bolt that can't be fixed easily. See https://github.com/etcd-io/bbolt/pull/611 for details. Signed-off-by: Jonathan A. Sternberg --- solver/bboltcachestorage/storage.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/solver/bboltcachestorage/storage.go b/solver/bboltcachestorage/storage.go index c374bb6cd53a..cffc3a86ad39 100644 --- a/solver/bboltcachestorage/storage.go +++ b/solver/bboltcachestorage/storage.go @@ -267,19 +267,28 @@ func (s *Store) emptyBranchWithParents(tx *bolt.Tx, id []byte) error { if backlinks := tx.Bucket([]byte(backlinksBucket)).Bucket(id); backlinks != nil { if err := backlinks.ForEach(func(k, v []byte) error { if subLinks := tx.Bucket([]byte(linksBucket)).Bucket(k); subLinks != nil { + // Perform deletion outside of the iteration. + // https://github.com/etcd-io/bbolt/pull/611 + var toDelete []string if err := subLinks.ForEach(func(k, v []byte) error { parts := bytes.Split(k, []byte("@")) if len(parts) != 2 { return errors.Errorf("invalid key %s", k) } if bytes.Equal(id, parts[1]) { - return subLinks.Delete(k) + toDelete = append(toDelete, string(k)) } return nil }); err != nil { return err } + for _, k := range toDelete { + if err := subLinks.Delete([]byte(k)); err != nil { + return err + } + } + if isEmptyBucket(subLinks) { if subResult := tx.Bucket([]byte(resultBucket)).Bucket(k); isEmptyBucket(subResult) { if err := tx.Bucket([]byte(linksBucket)).DeleteBucket(k); err != nil {