github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/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  }