github.com/ethersphere/bee/v2@v2.2.0/pkg/file/redundancy/getter/getter.go (about)

     1  // Copyright 2023 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package getter
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	"github.com/ethersphere/bee/v2/pkg/log"
    14  	"github.com/ethersphere/bee/v2/pkg/storage"
    15  	"github.com/ethersphere/bee/v2/pkg/swarm"
    16  	"github.com/klauspost/reedsolomon"
    17  )
    18  
    19  var (
    20  	errStrategyNotAllowed = errors.New("strategy not allowed")
    21  	errStrategyFailed     = errors.New("strategy failed")
    22  )
    23  
    24  // decoder is a private implementation of storage.Getter
    25  // if retrieves children of an intermediate chunk potentially using erasure decoding
    26  // it caches sibling chunks if erasure decoding started already
    27  type decoder struct {
    28  	fetcher      storage.Getter  // network retrieval interface to fetch chunks
    29  	putter       storage.Putter  // interface to local storage to save reconstructed chunks
    30  	addrs        []swarm.Address // all addresses of the intermediate chunk
    31  	inflight     []atomic.Bool   // locks to protect wait channels and RS buffer
    32  	cache        map[string]int  // map from chunk address shard position index
    33  	waits        []chan error    // wait channels for each chunk
    34  	rsbuf        [][]byte        // RS buffer of data + parity shards for erasure decoding
    35  	goodRecovery chan struct{}   // signal channel for successful retrieval of shardCnt chunks
    36  	badRecovery  chan struct{}   // signals that either the recovery has failed or not allowed to run
    37  	initRecovery chan struct{}   // signals that the recovery has been initialized
    38  	lastLen      int             // length of the last data chunk in the RS buffer
    39  	shardCnt     int             // number of data shards
    40  	parityCnt    int             // number of parity shards
    41  	mu           sync.Mutex      // mutex to protect buffer
    42  	fetchedCnt   atomic.Int32    // count successful retrievals
    43  	failedCnt    atomic.Int32    // count successful retrievals
    44  	remove       func(error)     // callback to remove decoder from decoders cache
    45  	config       Config          // configuration
    46  	logger       log.Logger
    47  }
    48  
    49  // New returns a decoder object used to retrieve children of an intermediate chunk
    50  func New(addrs []swarm.Address, shardCnt int, g storage.Getter, p storage.Putter, remove func(error), conf Config) storage.Getter {
    51  	size := len(addrs)
    52  
    53  	d := &decoder{
    54  		fetcher:      g,
    55  		putter:       p,
    56  		addrs:        addrs,
    57  		inflight:     make([]atomic.Bool, size),
    58  		cache:        make(map[string]int, size),
    59  		waits:        make([]chan error, size),
    60  		rsbuf:        make([][]byte, size),
    61  		goodRecovery: make(chan struct{}),
    62  		badRecovery:  make(chan struct{}),
    63  		initRecovery: make(chan struct{}),
    64  		remove:       remove,
    65  		shardCnt:     shardCnt,
    66  		parityCnt:    size - shardCnt,
    67  		config:       conf,
    68  		logger:       conf.Logger.WithName("redundancy").Build(),
    69  	}
    70  
    71  	// after init, cache and wait channels are immutable, need no locking
    72  	for i := 0; i < shardCnt; i++ {
    73  		d.cache[addrs[i].ByteString()] = i
    74  	}
    75  
    76  	// after init, cache and wait channels are immutable, need no locking
    77  	for i := 0; i < size; i++ {
    78  		d.waits[i] = make(chan error)
    79  	}
    80  
    81  	go d.prefetch()
    82  
    83  	return d
    84  }
    85  
    86  // Get will call parities and other sibling chunks if the chunk address cannot be retrieved
    87  // assumes it is called for data shards only
    88  func (g *decoder) Get(ctx context.Context, addr swarm.Address) (swarm.Chunk, error) {
    89  	i, ok := g.cache[addr.ByteString()]
    90  	if !ok {
    91  		return nil, storage.ErrNotFound
    92  	}
    93  	err := g.fetch(ctx, i, true)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return swarm.NewChunk(addr, g.getData(i)), nil
    98  }
    99  
   100  // fetch retrieves a chunk from the netstore if it is the first time the chunk is fetched.
   101  // If the fetch fails and waiting for the recovery is allowed, the function will wait
   102  // for either a good or bad recovery signal.
   103  func (g *decoder) fetch(ctx context.Context, i int, waitForRecovery bool) (err error) {
   104  
   105  	waitRecovery := func(err error) error {
   106  		if !waitForRecovery {
   107  			return err
   108  		}
   109  
   110  		select {
   111  		case <-g.badRecovery:
   112  			return storage.ErrNotFound
   113  		case <-g.goodRecovery:
   114  			g.logger.Debug("recovered chunk", "address", g.addrs[i])
   115  			return nil
   116  		case <-ctx.Done():
   117  			return ctx.Err()
   118  		}
   119  	}
   120  
   121  	// recovery has started, wait for result instead of fetching from the network
   122  	select {
   123  	case <-g.initRecovery:
   124  		return waitRecovery(nil)
   125  	default:
   126  	}
   127  
   128  	// first time
   129  	if g.fly(i) {
   130  
   131  		fctx, cancel := context.WithTimeout(ctx, g.config.FetchTimeout)
   132  		defer cancel()
   133  
   134  		// when the recovery is triggered, we can terminate any inflight requests.
   135  		// we do the extra bool check to not fire an unnecessary goroutine
   136  		if waitForRecovery {
   137  			go func() {
   138  				defer cancel()
   139  				select {
   140  				case <-g.initRecovery:
   141  				case <-fctx.Done():
   142  				}
   143  			}()
   144  		}
   145  
   146  		// retrieval
   147  		ch, err := g.fetcher.Get(fctx, g.addrs[i])
   148  		if err != nil {
   149  			g.failedCnt.Add(1)
   150  			close(g.waits[i])
   151  			return waitRecovery(err)
   152  		}
   153  
   154  		g.setData(i, ch.Data())
   155  		close(g.waits[i])
   156  		g.fetchedCnt.Add(1)
   157  		return nil
   158  	}
   159  
   160  	select {
   161  	case <-g.waits[i]:
   162  	case <-ctx.Done():
   163  		return ctx.Err()
   164  	}
   165  
   166  	if g.getData(i) != nil {
   167  		return nil
   168  	}
   169  
   170  	return waitRecovery(storage.ErrNotFound)
   171  }
   172  
   173  func (g *decoder) prefetch() {
   174  
   175  	var err error
   176  	defer func() {
   177  		if err != nil {
   178  			close(g.badRecovery)
   179  		} else {
   180  			close(g.goodRecovery)
   181  		}
   182  		g.remove(err)
   183  	}()
   184  
   185  	s := g.config.Strategy
   186  	for ; s < strategyCnt; s++ {
   187  
   188  		err = g.runStrategy(s)
   189  		if err != nil && s == DATA || s == RACE {
   190  			g.logger.Debug("failed strategy", "strategy", s)
   191  		}
   192  
   193  		if err == nil || g.config.Strict {
   194  			break
   195  		}
   196  	}
   197  
   198  	if err != nil {
   199  		return
   200  	}
   201  
   202  	close(g.initRecovery)
   203  
   204  	err = g.recover()
   205  	if err == nil && s > DATA {
   206  		g.logger.Debug("successful recovery", "strategy", s)
   207  	}
   208  
   209  }
   210  
   211  func (g *decoder) runStrategy(s Strategy) error {
   212  
   213  	// across the different strategies, the common goal is to fetch at least as many chunks
   214  	// as the number of data shards.
   215  	// DATA strategy has a max error tolerance of zero.
   216  	// RACE strategy has a max error tolerance of number of parity chunks.
   217  	var allowedErrs int
   218  	var m []int
   219  
   220  	switch s {
   221  	case NONE:
   222  		return errStrategyNotAllowed
   223  	case DATA:
   224  		// only retrieve data shards
   225  		m = g.unattemptedDataShards()
   226  		allowedErrs = 0
   227  	case PROX:
   228  		// proximity driven selective fetching
   229  		// NOT IMPLEMENTED
   230  		return errStrategyNotAllowed
   231  	case RACE:
   232  		allowedErrs = g.parityCnt
   233  		// retrieve all chunks at once enabling race among chunks
   234  		m = g.unattemptedDataShards()
   235  		for i := g.shardCnt; i < len(g.addrs); i++ {
   236  			m = append(m, i)
   237  		}
   238  	}
   239  
   240  	if len(m) == 0 {
   241  		return nil
   242  	}
   243  
   244  	c := make(chan error, len(m))
   245  
   246  	ctx, cancel := context.WithCancel(context.Background())
   247  	defer cancel()
   248  
   249  	for _, i := range m {
   250  		go func(i int) {
   251  			c <- g.fetch(ctx, i, false)
   252  		}(i)
   253  	}
   254  
   255  	for range c {
   256  		if g.fetchedCnt.Load() >= int32(g.shardCnt) {
   257  			return nil
   258  		}
   259  		if g.failedCnt.Load() > int32(allowedErrs) {
   260  			return errStrategyFailed
   261  		}
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  // recover wraps the stages of data shard recovery:
   268  // 1. gather missing data shards
   269  // 2. decode using Reed-Solomon decoder
   270  // 3. save reconstructed chunks
   271  func (g *decoder) recover() error {
   272  	// gather missing shards
   273  	m := g.missingDataShards()
   274  	if len(m) == 0 {
   275  		return nil // recovery is not needed as there are no missing data chunks
   276  	}
   277  
   278  	// decode using Reed-Solomon decoder
   279  	if err := g.decode(); err != nil {
   280  		return err
   281  	}
   282  
   283  	// save chunks
   284  	return g.save(m)
   285  }
   286  
   287  // decode uses Reed-Solomon erasure coding decoder to recover data shards
   288  // it must be called after shqrdcnt shards are retrieved
   289  func (g *decoder) decode() error {
   290  	g.mu.Lock()
   291  	defer g.mu.Unlock()
   292  
   293  	enc, err := reedsolomon.New(g.shardCnt, g.parityCnt)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	// decode data
   299  	return enc.ReconstructData(g.rsbuf)
   300  }
   301  
   302  func (g *decoder) unattemptedDataShards() (m []int) {
   303  	for i := 0; i < g.shardCnt; i++ {
   304  		select {
   305  		case <-g.waits[i]: // attempted
   306  			continue
   307  		default:
   308  			m = append(m, i) // remember the missing chunk
   309  		}
   310  	}
   311  	return m
   312  }
   313  
   314  // it must be called under mutex protection
   315  func (g *decoder) missingDataShards() (m []int) {
   316  	for i := 0; i < g.shardCnt; i++ {
   317  		if g.getData(i) == nil {
   318  			m = append(m, i)
   319  		}
   320  	}
   321  	return m
   322  }
   323  
   324  // setData sets the data shard in the RS buffer
   325  func (g *decoder) setData(i int, chdata []byte) {
   326  	g.mu.Lock()
   327  	defer g.mu.Unlock()
   328  
   329  	data := chdata
   330  	// pad the chunk with zeros if it is smaller than swarm.ChunkSize
   331  	if len(data) < swarm.ChunkWithSpanSize {
   332  		g.lastLen = len(data)
   333  		data = make([]byte, swarm.ChunkWithSpanSize)
   334  		copy(data, chdata)
   335  	}
   336  	g.rsbuf[i] = data
   337  }
   338  
   339  // getData returns the data shard from the RS buffer
   340  func (g *decoder) getData(i int) []byte {
   341  	g.mu.Lock()
   342  	defer g.mu.Unlock()
   343  	if i == g.shardCnt-1 && g.lastLen > 0 {
   344  		return g.rsbuf[i][:g.lastLen] // cut padding
   345  	}
   346  	return g.rsbuf[i]
   347  }
   348  
   349  // fly commits to retrieve the chunk (fly and land)
   350  // it marks a chunk as inflight and returns true unless it is already inflight
   351  // the atomic bool implements a singleflight pattern
   352  func (g *decoder) fly(i int) (success bool) {
   353  	return g.inflight[i].CompareAndSwap(false, true)
   354  }
   355  
   356  // save iterate over reconstructed shards and puts the corresponding chunks to local storage
   357  func (g *decoder) save(missing []int) error {
   358  	g.mu.Lock()
   359  	defer g.mu.Unlock()
   360  	for _, i := range missing {
   361  		if err := g.putter.Put(context.Background(), swarm.NewChunk(g.addrs[i], g.rsbuf[i])); err != nil {
   362  			return err
   363  		}
   364  	}
   365  	return nil
   366  }