oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/internal/graph/memory.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package graph 17 18 import ( 19 "context" 20 "errors" 21 "sync" 22 23 "github.com/opencontainers/go-digest" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "oras.land/oras-go/v2/content" 26 "oras.land/oras-go/v2/errdef" 27 "oras.land/oras-go/v2/internal/container/set" 28 "oras.land/oras-go/v2/internal/descriptor" 29 "oras.land/oras-go/v2/internal/status" 30 "oras.land/oras-go/v2/internal/syncutil" 31 ) 32 33 // Memory is a memory based PredecessorFinder. 34 type Memory struct { 35 // nodes has the following properties and behaviors: 36 // 1. a node exists in Memory.nodes if and only if it exists in the memory 37 // 2. Memory.nodes saves the ocispec.Descriptor map keys, which are used by 38 // the other fields. 39 nodes map[descriptor.Descriptor]ocispec.Descriptor 40 41 // predecessors has the following properties and behaviors: 42 // 1. a node exists in Memory.predecessors if it has at least one predecessor 43 // in the memory, regardless of whether or not the node itself exists in 44 // the memory. 45 // 2. a node does not exist in Memory.predecessors, if it doesn't have any predecessors 46 // in the memory. 47 predecessors map[descriptor.Descriptor]set.Set[descriptor.Descriptor] 48 49 // successors has the following properties and behaviors: 50 // 1. a node exists in Memory.successors if and only if it exists in the memory. 51 // 2. a node's entry in Memory.successors is always consistent with the actual 52 // content of the node, regardless of whether or not each successor exists 53 // in the memory. 54 successors map[descriptor.Descriptor]set.Set[descriptor.Descriptor] 55 56 lock sync.RWMutex 57 } 58 59 // NewMemory creates a new memory PredecessorFinder. 60 func NewMemory() *Memory { 61 return &Memory{ 62 nodes: make(map[descriptor.Descriptor]ocispec.Descriptor), 63 predecessors: make(map[descriptor.Descriptor]set.Set[descriptor.Descriptor]), 64 successors: make(map[descriptor.Descriptor]set.Set[descriptor.Descriptor]), 65 } 66 } 67 68 // Index indexes predecessors for each direct successor of the given node. 69 func (m *Memory) Index(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) error { 70 _, err := m.index(ctx, fetcher, node) 71 return err 72 } 73 74 // Index indexes predecessors for all the successors of the given node. 75 func (m *Memory) IndexAll(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) error { 76 // track content status 77 tracker := status.NewTracker() 78 var fn syncutil.GoFunc[ocispec.Descriptor] 79 fn = func(ctx context.Context, region *syncutil.LimitedRegion, desc ocispec.Descriptor) error { 80 // skip the node if other go routine is working on it 81 _, committed := tracker.TryCommit(desc) 82 if !committed { 83 return nil 84 } 85 successors, err := m.index(ctx, fetcher, desc) 86 if err != nil { 87 if errors.Is(err, errdef.ErrNotFound) { 88 // skip the node if it does not exist 89 return nil 90 } 91 return err 92 } 93 if len(successors) > 0 { 94 // traverse and index successors 95 return syncutil.Go(ctx, nil, fn, successors...) 96 } 97 return nil 98 } 99 return syncutil.Go(ctx, nil, fn, node) 100 } 101 102 // Predecessors returns the nodes directly pointing to the current node. 103 // Predecessors returns nil without error if the node does not exists in the 104 // store. Like other operations, calling Predecessors() is go-routine safe. 105 // However, it does not necessarily correspond to any consistent snapshot of 106 // the stored contents. 107 func (m *Memory) Predecessors(_ context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { 108 m.lock.RLock() 109 defer m.lock.RUnlock() 110 111 key := descriptor.FromOCI(node) 112 set, exists := m.predecessors[key] 113 if !exists { 114 return nil, nil 115 } 116 var res []ocispec.Descriptor 117 for k := range set { 118 res = append(res, m.nodes[k]) 119 } 120 return res, nil 121 } 122 123 // Remove removes the node from its predecessors and successors, and returns the 124 // dangling root nodes caused by the deletion. 125 func (m *Memory) Remove(node ocispec.Descriptor) []ocispec.Descriptor { 126 m.lock.Lock() 127 defer m.lock.Unlock() 128 129 nodeKey := descriptor.FromOCI(node) 130 var danglings []ocispec.Descriptor 131 // remove the node from its successors' predecessor list 132 for successorKey := range m.successors[nodeKey] { 133 predecessorEntry := m.predecessors[successorKey] 134 predecessorEntry.Delete(nodeKey) 135 136 // if none of the predecessors of the node still exists, we remove the 137 // predecessors entry and return it as a dangling node. Otherwise, we do 138 // not remove the entry. 139 if len(predecessorEntry) == 0 { 140 delete(m.predecessors, successorKey) 141 if _, exists := m.nodes[successorKey]; exists { 142 danglings = append(danglings, m.nodes[successorKey]) 143 } 144 } 145 } 146 delete(m.successors, nodeKey) 147 delete(m.nodes, nodeKey) 148 return danglings 149 } 150 151 // DigestSet returns the set of node digest in memory. 152 func (m *Memory) DigestSet() set.Set[digest.Digest] { 153 m.lock.RLock() 154 defer m.lock.RUnlock() 155 156 s := set.New[digest.Digest]() 157 for desc := range m.nodes { 158 s.Add(desc.Digest) 159 } 160 return s 161 } 162 163 // index indexes predecessors for each direct successor of the given node. 164 func (m *Memory) index(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { 165 successors, err := content.Successors(ctx, fetcher, node) 166 if err != nil { 167 return nil, err 168 } 169 m.lock.Lock() 170 defer m.lock.Unlock() 171 172 // index the node 173 nodeKey := descriptor.FromOCI(node) 174 m.nodes[nodeKey] = node 175 176 // for each successor, put it into the node's successors list, and 177 // put node into the succeesor's predecessors list 178 successorSet := set.New[descriptor.Descriptor]() 179 m.successors[nodeKey] = successorSet 180 for _, successor := range successors { 181 successorKey := descriptor.FromOCI(successor) 182 successorSet.Add(successorKey) 183 predecessorSet, exists := m.predecessors[successorKey] 184 if !exists { 185 predecessorSet = set.New[descriptor.Descriptor]() 186 m.predecessors[successorKey] = predecessorSet 187 } 188 predecessorSet.Add(nodeKey) 189 } 190 return successors, nil 191 } 192 193 // Exists checks if the node exists in the graph 194 func (m *Memory) Exists(node ocispec.Descriptor) bool { 195 m.lock.RLock() 196 defer m.lock.RUnlock() 197 198 nodeKey := descriptor.FromOCI(node) 199 _, exists := m.nodes[nodeKey] 200 return exists 201 }