github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/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/opcr-io/oras-go/v2/content"
    24  	"github.com/opcr-io/oras-go/v2/errdef"
    25  	"github.com/opcr-io/oras-go/v2/internal/descriptor"
    26  	"github.com/opcr-io/oras-go/v2/internal/status"
    27  	"github.com/opcr-io/oras-go/v2/internal/syncutil"
    28  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    29  )
    30  
    31  // Memory is a memory based PredecessorFinder.
    32  type Memory struct {
    33  	predecessors sync.Map // map[descriptor.Descriptor]map[descriptor.Descriptor]ocispec.Descriptor
    34  	indexed      sync.Map // map[descriptor.Descriptor]any
    35  }
    36  
    37  // NewMemory creates a new memory PredecessorFinder.
    38  func NewMemory() *Memory {
    39  	return &Memory{}
    40  }
    41  
    42  // 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.
    45  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
    53  }
    54  
    55  // 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.
    58  func (m *Memory) IndexAll(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) error {
    59  	// track content status
    60  	tracker := status.NewTracker()
    61  
    62  	var fn syncutil.GoFunc[ocispec.Descriptor]
    63  	fn = func(ctx context.Context, region *syncutil.LimitedRegion, desc ocispec.Descriptor) error {
    64  		// skip the node if other go routine is working on it
    65  		_, committed := tracker.TryCommit(desc)
    66  		if !committed {
    67  			return nil
    68  		}
    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)
    78  		if err != nil {
    79  			if errors.Is(err, errdef.ErrNotFound) {
    80  				// skip the node if it does not exist
    81  				return nil
    82  			}
    83  			return err
    84  		}
    85  		m.index(ctx, desc, successors)
    86  		m.indexed.Store(key, nil)
    87  
    88  		if len(successors) > 0 {
    89  			// traverse and index successors
    90  			return syncutil.Go(ctx, nil, fn, successors...)
    91  		}
    92  		return nil
    93  	}
    94  	return syncutil.Go(ctx, nil, fn, node)
    95  }
    96  
    97  // Predecessors returns the nodes directly pointing to the current node.
    98  // 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.
   103  func (m *Memory) Predecessors(_ context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   104  	key := descriptor.FromOCI(node)
   105  	value, exists := m.predecessors.Load(key)
   106  	if !exists {
   107  		return nil, nil
   108  	}
   109  	predecessors := value.(*sync.Map)
   110  
   111  	var res []ocispec.Descriptor
   112  	predecessors.Range(func(key, value interface{}) bool {
   113  		res = append(res, value.(ocispec.Descriptor))
   114  		return true
   115  	})
   116  	return res, nil
   117  }
   118  
   119  // 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
   125  	}
   126  
   127  	predecessorKey := descriptor.FromOCI(node)
   128  	for _, successor := range successors {
   129  		successorKey := descriptor.FromOCI(successor)
   130  		value, _ := m.predecessors.LoadOrStore(successorKey, &sync.Map{})
   131  		predecessors := value.(*sync.Map)
   132  		predecessors.Store(predecessorKey, node)
   133  	}
   134  }