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 }