Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: GC on oci.Store.Delete #653

Merged
merged 26 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 82 additions & 20 deletions content/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"oras.land/oras-go/v2/internal/descriptor"
"oras.land/oras-go/v2/internal/graph"
"oras.land/oras-go/v2/internal/resolver"
"oras.land/oras-go/v2/registry"
)

// Store implements `oras.Target`, and represents a content store
Expand All @@ -52,12 +53,26 @@
// to manually call SaveIndex() when needed.
// - Default value: true.
AutoSaveIndex bool
root string
indexPath string
index *ocispec.Index
storage *Storage
tagResolver *resolver.Memory
graph *graph.Memory

// AutoGC controls if the OCI store will automatically clean newly produced
// dangling (unreferenced) blobs during Delete() operation. For example the
// blobs whose manifests have been deleted. Manifests in the index will not
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved
// be deleted.
// - Default value: true.
AutoGC bool

// AutoDeleteReferrers controls if the OCI store will automatically delete its
// referrers when a manifest is deleted. When set to true, the referrers will
// be deleted even if they exist in the index.
// - Default value: true.
AutoDeleteReferrers bool

root string
indexPath string
index *ocispec.Index
storage *Storage
tagResolver *resolver.Memory
graph *graph.Memory

// sync ensures that most operations can be done concurrently, while Delete
// has the exclusive access to Store if a delete operation is underway. Operations
Expand All @@ -84,12 +99,14 @@
}

store := &Store{
AutoSaveIndex: true,
root: rootAbs,
indexPath: filepath.Join(rootAbs, ocispec.ImageIndexFile),
storage: storage,
tagResolver: resolver.NewMemory(),
graph: graph.NewMemory(),
AutoSaveIndex: true,
AutoGC: true,
AutoDeleteReferrers: true,
root: rootAbs,
indexPath: filepath.Join(rootAbs, ocispec.ImageIndexFile),
storage: storage,
tagResolver: resolver.NewMemory(),
graph: graph.NewMemory(),
}

if err := ensureDir(filepath.Join(rootAbs, ocispec.ImageBlobsDir)); err != nil {
Expand Down Expand Up @@ -143,11 +160,55 @@

// Delete deletes the content matching the descriptor from the store. Delete may
// fail on certain systems (i.e. NTFS), if there is a process (i.e. an unclosed
// Reader) using target.
// Reader) using target. If s.AutoGC is set to true, Delete will recursively
// remove the dangling nodes caused by the current delete. If s.AutoRemoveReferrers
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved
// is set to true, Delete will recursively remove the referrers of the manifests
// being deleted.
func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
s.sync.Lock()
defer s.sync.Unlock()
deleteQueue := []ocispec.Descriptor{target}

for len(deleteQueue) > 0 {
head := deleteQueue[0]
deleteQueue = deleteQueue[1:]

// get referrers if applicable
if s.AutoDeleteReferrers && descriptor.IsManifest(head) {
referrers, err := registry.Referrers(ctx, s, head, "")
if err != nil {
return err
}

Check warning on line 179 in content/oci/oci.go

View check run for this annotation

Codecov / codecov/patch

content/oci/oci.go#L178-L179

Added lines #L178 - L179 were not covered by tests
deleteQueue = append(deleteQueue, referrers...)
}

// delete the head of queue
if err := func() error {
s.sync.Lock()
defer s.sync.Unlock()
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved

danglings, err := s.delete(ctx, head)
if err != nil {
return err
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 191 in content/oci/oci.go

View check run for this annotation

Codecov / codecov/patch

content/oci/oci.go#L190-L191

Added lines #L190 - L191 were not covered by tests
if s.AutoGC {
for _, d := range danglings {
// do not delete existing manifests in tagResolver
_, err = s.tagResolver.Resolve(ctx, string(d.Digest))
if errors.Is(err, errdef.ErrNotFound) {
deleteQueue = append(deleteQueue, d)
}
}
}
return nil
}(); err != nil {
return err
wangxiaoxuan273 marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 204 in content/oci/oci.go

View check run for this annotation

Codecov / codecov/patch

content/oci/oci.go#L202-L204

Added lines #L202 - L204 were not covered by tests
}

return nil
}

// delete deletes one node and returns the dangling nodes caused by the delete.
func (s *Store) delete(ctx context.Context, target ocispec.Descriptor) ([]ocispec.Descriptor, error) {
resolvers := s.tagResolver.Map()
untagged := false
for reference, desc := range resolvers {
Expand All @@ -156,16 +217,17 @@
untagged = true
}
}
if err := s.graph.Remove(ctx, target); err != nil {
return err
}
danglings := s.graph.Remove(target)
if untagged && s.AutoSaveIndex {
err := s.saveIndex()
if err != nil {
return err
return nil, err

Check warning on line 224 in content/oci/oci.go

View check run for this annotation

Codecov / codecov/patch

content/oci/oci.go#L224

Added line #L224 was not covered by tests
}
}
return s.storage.Delete(ctx, target)
if err := s.storage.Delete(ctx, target); err != nil {
return nil, err
}

Check warning on line 229 in content/oci/oci.go

View check run for this annotation

Codecov / codecov/patch

content/oci/oci.go#L228-L229

Added lines #L228 - L229 were not covered by tests
return danglings, nil
}

// Tag tags a descriptor with a reference string.
Expand Down
Loading
Loading