Skip to content

Commit 459a246

Browse files
feat: support deletion for memory graph (#606)
Signed-off-by: Xiaoxuan Wang <[email protected]> Part 1/4 of #454 Based on draft PR #582 Current behavior regarding `graph.Memory.Remove(node)`: * `node` entry in `m.successors` and `m.nodes` is removed. * `node` is removed from its successors predecessors list. * `node` entry in `m.predecessors` is NOT removed, **unless all its predecessors no longer exist**. * `node` is NOT removed from its predecessors' `m.successors` list. The `m.successors` is always in accordance with the actual content. Signed-off-by: Xiaoxuan Wang <[email protected]>
1 parent e8e4f84 commit 459a246

File tree

4 files changed

+692
-47
lines changed

4 files changed

+692
-47
lines changed

internal/container/set/set.go

+5
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ func (s Set[T]) Contains(item T) bool {
3333
_, ok := s[item]
3434
return ok
3535
}
36+
37+
// Delete deletes an item from the set.
38+
func (s Set[T]) Delete(item T) {
39+
delete(s, item)
40+
}

internal/container/set/set_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,12 @@ func TestSet(t *testing.T) {
5252
if got, want := len(set), 2; got != want {
5353
t.Errorf("len(Set) = %v, want %v", got, want)
5454
}
55+
// test deleting a key
56+
set.Delete(key1)
57+
if got, want := set.Contains(key1), false; got != want {
58+
t.Errorf("Set.Contains(%s) = %v, want %v", key1, got, want)
59+
}
60+
if got, want := len(set), 1; got != want {
61+
t.Errorf("len(Set) = %v, want %v", got, want)
62+
}
5563
}

internal/graph/memory.go

+67-47
Original file line numberDiff line numberDiff line change
@@ -23,68 +23,54 @@ import (
2323
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2424
"oras.land/oras-go/v2/content"
2525
"oras.land/oras-go/v2/errdef"
26+
"oras.land/oras-go/v2/internal/container/set"
2627
"oras.land/oras-go/v2/internal/descriptor"
2728
"oras.land/oras-go/v2/internal/status"
2829
"oras.land/oras-go/v2/internal/syncutil"
2930
)
3031

3132
// Memory is a memory based PredecessorFinder.
3233
type Memory struct {
33-
predecessors sync.Map // map[descriptor.Descriptor]map[descriptor.Descriptor]ocispec.Descriptor
34-
indexed sync.Map // map[descriptor.Descriptor]any
34+
nodes map[descriptor.Descriptor]ocispec.Descriptor // nodes saves the map keys of ocispec.Descriptor
35+
predecessors map[descriptor.Descriptor]set.Set[descriptor.Descriptor]
36+
successors map[descriptor.Descriptor]set.Set[descriptor.Descriptor]
37+
lock sync.RWMutex
3538
}
3639

3740
// NewMemory creates a new memory PredecessorFinder.
3841
func NewMemory() *Memory {
39-
return &Memory{}
42+
return &Memory{
43+
nodes: make(map[descriptor.Descriptor]ocispec.Descriptor),
44+
predecessors: make(map[descriptor.Descriptor]set.Set[descriptor.Descriptor]),
45+
successors: make(map[descriptor.Descriptor]set.Set[descriptor.Descriptor]),
46+
}
4047
}
4148

4249
// Index indexes predecessors for each direct successor of the given node.
43-
// There is no data consistency issue as long as deletion is not implemented
44-
// for the underlying storage.
4550
func (m *Memory) Index(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) error {
46-
successors, err := content.Successors(ctx, fetcher, node)
47-
if err != nil {
48-
return err
49-
}
50-
51-
m.index(ctx, node, successors)
52-
return nil
51+
_, err := m.index(ctx, fetcher, node)
52+
return err
5353
}
5454

5555
// Index indexes predecessors for all the successors of the given node.
56-
// There is no data consistency issue as long as deletion is not implemented
57-
// for the underlying storage.
5856
func (m *Memory) IndexAll(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) error {
5957
// track content status
6058
tracker := status.NewTracker()
61-
6259
var fn syncutil.GoFunc[ocispec.Descriptor]
6360
fn = func(ctx context.Context, region *syncutil.LimitedRegion, desc ocispec.Descriptor) error {
6461
// skip the node if other go routine is working on it
6562
_, committed := tracker.TryCommit(desc)
6663
if !committed {
6764
return nil
6865
}
69-
70-
// skip the node if it has been indexed
71-
key := descriptor.FromOCI(desc)
72-
_, exists := m.indexed.Load(key)
73-
if exists {
74-
return nil
75-
}
76-
77-
successors, err := content.Successors(ctx, fetcher, desc)
66+
successors, err := m.index(ctx, fetcher, desc)
7867
if err != nil {
7968
if errors.Is(err, errdef.ErrNotFound) {
8069
// skip the node if it does not exist
8170
return nil
8271
}
8372
return err
8473
}
85-
m.index(ctx, desc, successors)
86-
m.indexed.Store(key, nil)
87-
8874
if len(successors) > 0 {
8975
// traverse and index successors
9076
return syncutil.Go(ctx, nil, fn, successors...)
@@ -96,39 +82,73 @@ func (m *Memory) IndexAll(ctx context.Context, fetcher content.Fetcher, node oci
9682

9783
// Predecessors returns the nodes directly pointing to the current node.
9884
// Predecessors returns nil without error if the node does not exists in the
99-
// store.
100-
// Like other operations, calling Predecessors() is go-routine safe. However,
101-
// it does not necessarily correspond to any consistent snapshot of the stored
102-
// contents.
85+
// store. Like other operations, calling Predecessors() is go-routine safe.
86+
// However, it does not necessarily correspond to any consistent snapshot of
87+
// the stored contents.
10388
func (m *Memory) Predecessors(_ context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
89+
m.lock.RLock()
90+
defer m.lock.RUnlock()
91+
10492
key := descriptor.FromOCI(node)
105-
value, exists := m.predecessors.Load(key)
93+
set, exists := m.predecessors[key]
10694
if !exists {
10795
return nil, nil
10896
}
109-
predecessors := value.(*sync.Map)
110-
11197
var res []ocispec.Descriptor
112-
predecessors.Range(func(key, value interface{}) bool {
113-
res = append(res, value.(ocispec.Descriptor))
114-
return true
115-
})
98+
for k := range set {
99+
res = append(res, m.nodes[k])
100+
}
116101
return res, nil
117102
}
118103

104+
// Remove removes the node from its predecessors and successors.
105+
func (m *Memory) Remove(ctx context.Context, node ocispec.Descriptor) error {
106+
m.lock.Lock()
107+
defer m.lock.Unlock()
108+
109+
nodeKey := descriptor.FromOCI(node)
110+
// remove the node from its successors' predecessor list
111+
for successorKey := range m.successors[nodeKey] {
112+
predecessorEntry := m.predecessors[successorKey]
113+
predecessorEntry.Delete(nodeKey)
114+
115+
// if none of the predecessors of the node still exists, we remove the
116+
// predecessors entry. Otherwise, we do not remove the entry.
117+
if len(predecessorEntry) == 0 {
118+
delete(m.predecessors, successorKey)
119+
}
120+
}
121+
delete(m.successors, nodeKey)
122+
delete(m.nodes, nodeKey)
123+
return nil
124+
}
125+
119126
// index indexes predecessors for each direct successor of the given node.
120-
// There is no data consistency issue as long as deletion is not implemented
121-
// for the underlying storage.
122-
func (m *Memory) index(ctx context.Context, node ocispec.Descriptor, successors []ocispec.Descriptor) {
123-
if len(successors) == 0 {
124-
return
127+
func (m *Memory) index(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
128+
successors, err := content.Successors(ctx, fetcher, node)
129+
if err != nil {
130+
return nil, err
125131
}
132+
m.lock.Lock()
133+
defer m.lock.Unlock()
134+
135+
// index the node
136+
nodeKey := descriptor.FromOCI(node)
137+
m.nodes[nodeKey] = node
126138

127-
predecessorKey := descriptor.FromOCI(node)
139+
// for each successor, put it into the node's successors list, and
140+
// put node into the succeesor's predecessors list
141+
successorSet := set.New[descriptor.Descriptor]()
142+
m.successors[nodeKey] = successorSet
128143
for _, successor := range successors {
129144
successorKey := descriptor.FromOCI(successor)
130-
value, _ := m.predecessors.LoadOrStore(successorKey, &sync.Map{})
131-
predecessors := value.(*sync.Map)
132-
predecessors.Store(predecessorKey, node)
145+
successorSet.Add(successorKey)
146+
predecessorSet, exists := m.predecessors[successorKey]
147+
if !exists {
148+
predecessorSet = set.New[descriptor.Descriptor]()
149+
m.predecessors[successorKey] = predecessorSet
150+
}
151+
predecessorSet.Add(nodeKey)
133152
}
153+
return successors, nil
134154
}

0 commit comments

Comments
 (0)