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 }