Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions pkg/replicas/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import (
// then the probability of Swarmageddon is less than 0.000001
// assuming the error rate of chunk retrievals stays below the level expressed
// as depth by the publisher.
var ErrSwarmageddon = errors.New("swarmageddon has begun")
var (
ErrSwarmageddon = errors.New("swarmageddon has begun")
// errGetterExhausted is returned when the retry loop exhausts all levels without
// receiving a result or enough errors to trigger ErrSwarmageddon.
// This path should never be reached under normal operation.
errGetterExhausted = errors.New("replicas getter: exhausted all levels without result (unexpected)")
)

// getter is the private implementation of storage.Getter, an interface for
// retrieving chunks. This getter embeds the original simple chunk getter and extends it
Expand All @@ -48,6 +54,9 @@ func NewGetter(g storage.Getter, level redundancy.Level) storage.Getter {

// Get makes the getter satisfy the storage.Getter interface
func (g *getter) Get(ctx context.Context, addr swarm.Address) (ch swarm.Chunk, err error) {
if g.level == redundancy.NONE {
return g.Getter.Get(ctx, addr)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()

Expand Down Expand Up @@ -77,7 +86,11 @@ func (g *getter) Get(ctx context.Context, addr swarm.Address) (ch swarm.Chunk, e
target := 2 // the number of replicas attempted to download in this batch
total := g.level.GetReplicaCount()

//
// The replicator feeds replica addresses in batches that double each RetryInterval
// (2, 2, 4, 8, 16 for PARANOID). The replicator exhausts exactly at the last iteration,
// which then drains a nil from `next` and becomes an indefinite wait on resultC/errc.
// The loop therefore always terminates via resultC (success) or Swarmageddon (all fail),
// never by falling through to the return below.
rr := newReplicator(addr, g.level)
next := rr.c
var wait <-chan time.Time // nil channel to disable case
Expand Down Expand Up @@ -142,5 +155,6 @@ func (g *getter) Get(ctx context.Context, addr swarm.Address) (ch swarm.Chunk, e
}
}

return nil, nil
// unreachable: the loop always exits via resultC or Swarmageddon (see comment above)
return nil, errGetterExhausted
}
6 changes: 4 additions & 2 deletions pkg/replicas/getter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ func TestGetter(t *testing.T) {
}

t.Run("returns correct error", func(t *testing.T) {
if !errors.Is(err, replicas.ErrSwarmageddon) {
t.Fatalf("incorrect error. want Swarmageddon. got %v", err)
if tc.level > 0 {
if !errors.Is(err, replicas.ErrSwarmageddon) {
t.Fatalf("incorrect error. want Swarmageddon. got %v", err)
}
}
if !errors.Is(err, tc.failure.err) {
t.Fatalf("incorrect error. want it to wrap %v. got %v", tc.failure.err, err)
Expand Down
Loading