Skip to content

Commit db54c77

Browse files
authored
Handle errors from SetAddrs in Valkey ring updater (#4024)
We were ignoring errors returned from `ring.SetAddr`, so [if the creation of the new valkey client failed](https://github.com/zalando/skipper/blob/8fb2ebe40f5f7aafe9db010c85c5efc8bc283ac3/net/valkey.go#L222), the `clientMap` stays in the old (or incomlete) state, but in the [updater](https://github.com/zalando/skipper/blob/8fb2ebe40f5f7aafe9db010c85c5efc8bc283ac3/net/valkey.go#L483) it was considered successful and we never tried to recreate the client or update clientMap. --------- Signed-off-by: Aleksandr Ponimaskin <aleksandr.ponimaskin@zalando.de>
1 parent 5575efd commit db54c77

2 files changed

Lines changed: 94 additions & 8 deletions

File tree

net/valkey.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,13 @@ func (vrc *ValkeyRingClient) startUpdater(ctx context.Context) {
434434

435435
init := true
436436
if len(old) != 0 {
437-
vrc.SetAddrs(ctx, old)
438-
vrc.log.Infof("Valkey updater initial set to %d shards", len(old))
439-
init = false
437+
err := vrc.SetAddrs(ctx, old)
438+
if err != nil {
439+
vrc.log.Errorf("Failed to init valkey ring: %v", err)
440+
} else {
441+
vrc.log.Infof("Valkey updater initial set to %d shards", len(old))
442+
init = false
443+
}
440444
}
441445

442446
for {
@@ -456,18 +460,26 @@ func (vrc *ValkeyRingClient) startUpdater(ctx context.Context) {
456460

457461
if init {
458462
if len(addrs) != 0 {
463+
err := vrc.SetAddrs(ctx, addrs)
464+
if err != nil {
465+
vrc.log.Errorf("Failed to init valkey ring: %v", err)
466+
continue
467+
}
468+
459469
init = false
460-
vrc.SetAddrs(ctx, addrs)
461470
vrc.log.Infof("Valkey updater initial set to %d shards", len(addrs))
462471
old = addrs
463472
}
464473
continue
465474
}
466475

467476
if !slices.Equal(old, addrs) {
468-
vrc.SetAddrs(ctx, addrs)
477+
err := vrc.SetAddrs(ctx, addrs)
478+
if err != nil {
479+
vrc.log.Errorf("Failed to update valkey ring: %v", err)
480+
continue
481+
}
469482
vrc.log.Infof("Valkey updater updated old(%d) -> new(%d)", len(old), len(addrs))
470-
471483
old = addrs
472484
}
473485
}
@@ -477,9 +489,12 @@ func (vrc *ValkeyRingClient) StartSpan(operationName string, opts ...opentracing
477489
return vrc.tracer.StartSpan(operationName, opts...)
478490
}
479491

480-
func (vrc *ValkeyRingClient) SetAddrs(ctx context.Context, addrs []string) {
481-
vrc.ring.SetAddr(addrs)
492+
func (vrc *ValkeyRingClient) SetAddrs(ctx context.Context, addrs []string) error {
493+
if err := vrc.ring.SetAddr(addrs); err != nil {
494+
return err
495+
}
482496
vrc.metrics.UpdateGauge(vrc.metricsPrefix+"shards", float64(vrc.ring.Len()))
497+
return nil
483498
}
484499

485500
func (vrc *ValkeyRingClient) RingAvailable(ctx context.Context) bool {

net/valkey_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package net
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"strings"
8+
"sync"
79
"testing"
810
"testing/synctest"
911
"time"
@@ -17,6 +19,19 @@ import (
1719
"github.com/zalando/skipper/tracing/tracingtest"
1820
)
1921

22+
func (vr *valkeyRing) hasAddr(addr string) bool {
23+
vr.mu.Lock()
24+
defer vr.mu.Unlock()
25+
_, ok := vr.clientMap[addr]
26+
return ok
27+
}
28+
29+
func (vr *valkeyRing) safeLen() int {
30+
vr.mu.Lock()
31+
defer vr.mu.Unlock()
32+
return vr.Len()
33+
}
34+
2035
func TestValkeyDifference(t *testing.T) {
2136
for _, tt := range []struct {
2237
name string
@@ -1593,3 +1608,59 @@ func TestValkeyRingUpdateShardsDeterministic(t *testing.T) {
15931608
require.Equal(t, snapABC[slot], snapCBA[slot], "shard assignment should be the same")
15941609
}
15951610
}
1611+
1612+
func TestValkeyUpdaterRetriesAfterSetAddrsFailure(t *testing.T) {
1613+
valkeyAddr, done := valkeytest.NewTestValkey(t)
1614+
defer done()
1615+
1616+
newValkeyAddr, done := valkeytest.NewTestValkey(t)
1617+
defer done()
1618+
1619+
l, err := net.Listen("tcp", "127.0.0.1:0")
1620+
if err != nil {
1621+
t.Fatalf("Failed to listen: %v", err)
1622+
}
1623+
deadAddr := l.Addr().String()
1624+
l.Close()
1625+
1626+
var mu sync.Mutex
1627+
callCount := 0
1628+
1629+
cli, err := NewValkeyRingClient(&ValkeyOptions{
1630+
Addrs: []string{},
1631+
AddrUpdater: func() ([]string, error) {
1632+
mu.Lock()
1633+
defer mu.Unlock()
1634+
callCount++
1635+
if callCount == 1 {
1636+
return []string{valkeyAddr}, nil
1637+
}
1638+
if callCount == 2 {
1639+
// new address, but unreachable
1640+
return []string{deadAddr}, nil
1641+
}
1642+
return []string{newValkeyAddr}, nil
1643+
},
1644+
UpdateInterval: 50 * time.Millisecond,
1645+
})
1646+
if err != nil {
1647+
t.Fatalf("Failed to create valkey ring client: %v", err)
1648+
}
1649+
defer cli.Close()
1650+
1651+
time.Sleep(cli.options.UpdateInterval + 10*time.Millisecond)
1652+
if n := cli.ring.safeLen(); n != 1 {
1653+
t.Fatalf("expected original shard to remain after failed update, got %d shards", n)
1654+
}
1655+
if !cli.ring.hasAddr(valkeyAddr) {
1656+
t.Fatal("expected original shard address to still be in clientMap")
1657+
}
1658+
1659+
time.Sleep(cli.options.UpdateInterval + 10*time.Millisecond)
1660+
if n := cli.ring.safeLen(); n != 1 {
1661+
t.Fatalf("expected 1 shard after successful retry, got %d", n)
1662+
}
1663+
if !cli.ring.hasAddr(newValkeyAddr) {
1664+
t.Fatal("expected new shard address to be in clientMap")
1665+
}
1666+
}

0 commit comments

Comments
 (0)