storj.io/uplink@v1.13.0/private/eestream/bundy.go (about)

     1  // Copyright (C) 2023 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package eestream
     5  
     6  import (
     7  	"fmt"
     8  	"sync/atomic"
     9  )
    10  
    11  // PiecesProgress is an interesting concurrency primitive we don't know what
    12  // to call, but it's kind of like those old clocks that employees would clock
    13  // in and out of. Imagine that Looney Toons episode where Sam the Sheepdog and
    14  // Wile E Coyote are clocking in and out.
    15  //
    16  // In our case, PiecesProgress is two dimensional:
    17  //   - There is the neededShares vs total dimension - this is the number of shares
    18  //     that are necessary for Reed Solomon construction, for instance.
    19  //   - There is the current watermark of what stripe specific pieces are working
    20  //     on.
    21  //
    22  // A bunch of piece readers will be keeping PiecesProgress updated with how
    23  // many shares they have downloaded within their piece. When a piece reader
    24  // tells PiecesProgress that they have downloaded n shares, and that was the
    25  // trigger that means we now have the neededShares necessary shares at a certain
    26  // watermark or stripe, PiecesProgress will tell the piece reader to wake
    27  // up the combining layer.
    28  //
    29  // This data structure is designed to cause these wakeups to happen as little
    30  // as possible.
    31  type PiecesProgress struct {
    32  	neededShares        *atomic.Int32
    33  	stripesNeeded       *atomic.Int32
    34  	newStripeReady      *atomic.Int32
    35  	pieceSharesReceived []atomic.Int32
    36  }
    37  
    38  // NewPiecesProgress constructs PiecesProgress with a neededShares number of
    39  // necessary shares per stripe, out of total total shares. PiecesProgress
    40  // doesn't care about how many stripes there are but will keep track of which
    41  // stripe each share reader is on.
    42  func NewPiecesProgress(minimum, total int32) *PiecesProgress {
    43  	// In order to keep all the mutable data on a single cache line, we
    44  	// store all values in a single memory slice. the first few values are
    45  	// named and get dedicated pointers, and the rest are pieceSharesReceived.
    46  	memory := make([]atomic.Int32, total+3)
    47  	memory[0].Store(minimum)
    48  	return &PiecesProgress{
    49  		neededShares:        &memory[0],
    50  		stripesNeeded:       &memory[1],
    51  		newStripeReady:      &memory[2],
    52  		pieceSharesReceived: memory[3:],
    53  	}
    54  }
    55  
    56  // ProgressSnapshot returns a snapshot of the current progress. No locks are held
    57  // so it doesn't represent a single point in time in the presence of concurrent
    58  // mutations.
    59  func (y *PiecesProgress) ProgressSnapshot(out []int32) []int32 {
    60  	for i := range y.pieceSharesReceived {
    61  		out = append(out, y.pieceSharesReceived[i].Load())
    62  	}
    63  	return out
    64  }
    65  
    66  // SetStripesNeeded tells PiecesProgress what neededShares stripe is needed next.
    67  func (y *PiecesProgress) SetStripesNeeded(required int32) {
    68  	y.stripesNeeded.Store(required)
    69  }
    70  
    71  // IncreaseNeededShares tells PiecesProgress that going forward, we need more
    72  // shares per watermark value.
    73  func (y *PiecesProgress) IncreaseNeededShares() bool {
    74  	for {
    75  		min := y.NeededShares()
    76  		if min >= int32(len(y.pieceSharesReceived)) {
    77  			return false
    78  		}
    79  		if y.neededShares.CompareAndSwap(min, min+1) {
    80  			return true
    81  		}
    82  	}
    83  }
    84  
    85  // NeededShares returns the current value of the number of shares required at a given
    86  // watermark.
    87  func (y *PiecesProgress) NeededShares() int32 {
    88  	return y.neededShares.Load()
    89  }
    90  
    91  // PieceSharesReceived returns the current watermark for reader idx.
    92  func (y *PiecesProgress) PieceSharesReceived(idx int) int32 {
    93  	if idx < 0 || idx >= len(y.pieceSharesReceived) {
    94  		return 0
    95  	}
    96  	return y.pieceSharesReceived[idx].Load()
    97  }
    98  
    99  // AcknowledgeNewStripes tells PiecesProgress that the combiner has woken up and
   100  // new alarms are okay to trigger.
   101  func (y *PiecesProgress) AcknowledgeNewStripes() {
   102  	y.newStripeReady.Store(0)
   103  }
   104  
   105  // SharesCompleted adds some read events to a given index. If SharesCompleted
   106  // returns true, then the calling reader should wake up the combiner.
   107  func (y *PiecesProgress) SharesCompleted(idx int, delta int32) bool {
   108  	if idx < 0 || idx >= len(y.pieceSharesReceived) {
   109  		if debugEnabled {
   110  			fmt.Println("bundy", idx, "out of range")
   111  		}
   112  		return false
   113  	}
   114  
   115  	val := y.pieceSharesReceived[idx].Add(delta)
   116  
   117  	stripeNeeded := y.stripesNeeded.Load()
   118  	stripeReady := y.newStripeReady.Load() != 0
   119  
   120  	if debugEnabled {
   121  		fmt.Println("bundy", idx, "delta", delta, "counter", val, "stripeNeed", stripeNeeded, "newStripeReady", stripeReady)
   122  	}
   123  
   124  	// it's only possible to wake if the value we stored could increase the
   125  	// number of shares larger than the required amount, and there is the potential
   126  	// of a wake available.
   127  	if val < stripeNeeded || stripeReady {
   128  		return false
   129  	}
   130  
   131  	// count all of the pieceSharesReceived that are larger than the required share. if we don't
   132  	// have enough, no need to wake yet.
   133  	c := int32(0)
   134  	for i := 0; i < len(y.pieceSharesReceived); i++ {
   135  		if y.pieceSharesReceived[i].Load() >= stripeNeeded {
   136  			c++
   137  		}
   138  	}
   139  	if debugEnabled {
   140  		fmt.Println("bundy", idx, "found", c)
   141  	}
   142  	if c < y.neededShares.Load() {
   143  		return false
   144  	}
   145  
   146  	if debugEnabled {
   147  		fmt.Println("bundy", idx, "attempting wake claim")
   148  	}
   149  	// we are sure there's enough shares required. acquire explicit ownership of notification.
   150  	return y.newStripeReady.CompareAndSwap(0, 1)
   151  }