Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit ca6e72f

Browse files
authored
Redis: remove RedisKeyValue constructor (#64442)
This PR removes the `redispool.RedisKeyValue` constructor in favor of the `New...KeyValue` methods, which do not take a pool directly. This way callers won't create a `Pool` reference, allowing us to track all direct pool usage through `KeyValue.Pool()`. This also simplifies a few things: * Tests now use `NewTestKeyValue` instead of dialing up localhost directly * We can remove duplicated Redis connection logic in Cody Gateway
1 parent 34ff925 commit ca6e72f

File tree

13 files changed

+77
-197
lines changed

13 files changed

+77
-197
lines changed

cmd/cody-gateway/shared/BUILD.bazel

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go_library(
55
srcs = [
66
"main.go",
77
"metrics.go",
8-
"redis.go",
98
"service.go",
109
"tracing.go",
1110
],
@@ -33,7 +32,6 @@ go_library(
3332
"//internal/goroutine",
3433
"//internal/httpcli",
3534
"//internal/httpserver",
36-
"//internal/lazyregexp",
3735
"//internal/observation",
3836
"//internal/rcache",
3937
"//internal/redispool",

cmd/cody-gateway/shared/main.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
9898
}
9999
}
100100

101-
redisPool := connectToRedis(cfg.RedisEndpoint)
102-
103101
// Create an uncached external doer, we never want to cache any responses.
104102
// Not only is the cache hit rate going to be really low and requests large-ish,
105103
// but also do we not want to retain any data.
@@ -115,7 +113,10 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
115113
return errors.Wrap(err, "init metric 'redis_latency'")
116114
}
117115

118-
redisCache := redispool.RedisKeyValue(redisPool).WithLatencyRecorder(func(call string, latency time.Duration, err error) {
116+
redisCache := redispool.NewKeyValue(cfg.RedisEndpoint, &redis.Pool{
117+
MaxIdle: 10,
118+
IdleTimeout: 240 * time.Second,
119+
}).WithLatencyRecorder(func(call string, latency time.Duration, err error) {
119120
redisLatency.Record(context.Background(), latency.Milliseconds(), metric.WithAttributeSet(attribute.NewSet(
120121
attribute.Bool("error", err != nil),
121122
attribute.String("command", call))))
@@ -240,7 +241,7 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
240241
return errors.Wrap(err, "httpapi.NewHandler")
241242
}
242243
// Diagnostic and Maintenance layers, exposing additional APIs and endpoints.
243-
handler = httpapi.NewDiagnosticsHandler(obctx.Logger, handler, redisPool, cfg.DiagnosticsSecret, sources)
244+
handler = httpapi.NewDiagnosticsHandler(obctx.Logger, handler, redisCache.Pool(), cfg.DiagnosticsSecret, sources)
244245
handler = httpapi.NewMaintenanceHandler(obctx.Logger, handler, cfg, redisCache)
245246

246247
// Collect request client for downstream handlers. Outside of dev, we always set up
@@ -257,7 +258,7 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
257258
})
258259

259260
// Set up redis-based distributed mutex for the source syncer worker
260-
sourceWorkerMutex := redsync.New(redigo.NewPool(redisPool)).NewMutex("source-syncer-worker",
261+
sourceWorkerMutex := redsync.New(redigo.NewPool(redisCache.Pool())).NewMutex("source-syncer-worker",
261262
// Do not retry endlessly becuase it's very likely that someone else has
262263
// a long-standing hold on the mutex. We will try again on the next periodic
263264
// goroutine run.

cmd/cody-gateway/shared/redis.go

Lines changed: 0 additions & 41 deletions
This file was deleted.

cmd/worker/internal/ratelimit/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ go_test(
4747
"//internal/types",
4848
"//lib/pointers",
4949
"//schema",
50-
"@com_github_gomodule_redigo//redis",
5150
"@com_github_google_go_cmp//cmp",
5251
"@com_github_sourcegraph_log//logtest",
5352
"@com_github_stretchr_testify//assert",

cmd/worker/internal/ratelimit/handler_test.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"testing"
66
"time"
77

8-
"github.com/gomodule/redigo/redis"
98
"github.com/google/go-cmp/cmp"
109
"github.com/sourcegraph/log/logtest"
1110
"github.com/stretchr/testify/assert"
@@ -28,22 +27,10 @@ func TestHandler_Handle(t *testing.T) {
2827
db := database.NewDB(logger, dbtest.NewDB(t))
2928

3029
prefix := "__test__" + t.Name()
31-
redisHost := "127.0.0.1:6379"
32-
33-
pool := &redis.Pool{
34-
MaxIdle: 3,
35-
IdleTimeout: 240 * time.Second,
36-
Dial: func() (redis.Conn, error) {
37-
return redis.Dial("tcp", redisHost)
38-
},
39-
TestOnBorrow: func(c redis.Conn, t time.Time) error {
40-
_, err := c.Do("PING")
41-
return err
42-
},
43-
}
30+
kv := redispool.NewTestKeyValue()
4431

4532
t.Cleanup(func() {
46-
if err := redispool.DeleteAllKeysWithPrefix(redispool.RedisKeyValue(pool), prefix); err != nil {
33+
if err := redispool.DeleteAllKeysWithPrefix(kv, prefix); err != nil {
4734
t.Logf("Failed to clear redis: %+v\n", err)
4835
}
4936
})
@@ -69,14 +56,14 @@ func TestHandler_Handle(t *testing.T) {
6956
h := handler{
7057
externalServiceStore: db.ExternalServices(),
7158
newRateLimiterFunc: func(bucketName string) ratelimit.GlobalLimiter {
72-
return ratelimit.NewTestGlobalRateLimiter(pool, prefix, bucketName)
59+
return ratelimit.NewTestGlobalRateLimiter(kv.Pool(), prefix, bucketName)
7360
},
7461
logger: logger,
7562
}
7663
err = h.Handle(ctx)
7764
assert.NoError(t, err)
7865

79-
info, err := ratelimit.GetGlobalLimiterStateFromPool(ctx, pool, prefix)
66+
info, err := ratelimit.GetGlobalLimiterStateFromStore(kv, prefix)
8067
require.NoError(t, err)
8168

8269
if diff := cmp.Diff(map[string]ratelimit.GlobalLimiterInfo{

internal/featureflag/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ go_test(
3535
"//internal/redispool",
3636
"//lib/errors",
3737
"@com_github_derision_test_go_mockgen_v2//testutil/require",
38-
"@com_github_gomodule_redigo//redis",
3938
"@com_github_google_go_cmp//cmp",
4039
"@com_github_rafaeljusto_redigomock_v3//:redigomock",
4140
"@com_github_stretchr_testify//require",

internal/featureflag/middleware_test.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"testing"
88

99
mockrequire "github.com/derision-test/go-mockgen/v2/testutil/require"
10-
"github.com/gomodule/redigo/redis"
1110
"github.com/rafaeljusto/redigomock/v3"
1211
"github.com/stretchr/testify/require"
1312

@@ -173,19 +172,17 @@ func setupRedisTest(t *testing.T) {
173172

174173
t.Cleanup(func() { mockConn.Clear(); mockConn.Close() })
175174

176-
mockConn.GenericCommand("HSET").Handle(func(args []interface{}) (interface{}, error) {
177-
cache[args[0].(string)] = []byte(args[2].(string))
178-
return nil, nil
175+
mockStore := redispool.NewMockKeyValue()
176+
mockStore.HSetFunc.SetDefaultHook(func(key string, field string, value any) error {
177+
cache[key] = []byte(value.(string))
178+
return nil
179179
})
180-
181-
mockConn.GenericCommand("HGET").Handle(func(args []interface{}) (interface{}, error) {
182-
return cache[args[0].(string)], nil
180+
mockStore.HGetFunc.SetDefaultHook(func(key string, field string) redispool.Value {
181+
return redispool.NewValue(cache[key], nil)
183182
})
184-
185-
mockConn.GenericCommand("DEL").Handle(func(args []interface{}) (interface{}, error) {
186-
delete(cache, args[0].(string))
187-
return nil, nil
183+
mockStore.DelFunc.SetDefaultHook(func(key string) error {
184+
delete(cache, key)
185+
return nil
188186
})
189-
190-
evalStore = redispool.RedisKeyValue(&redis.Pool{Dial: func() (redis.Conn, error) { return mockConn, nil }, MaxIdle: 10})
187+
evalStore = mockStore
191188
}

internal/ratelimit/globallimiter.go

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -379,38 +379,21 @@ type GlobalLimiterInfo struct {
379379
// GetGlobalLimiterState reports how all the existing rate limiters are configured,
380380
// keyed by bucket name.
381381
func GetGlobalLimiterState(ctx context.Context) (map[string]GlobalLimiterInfo, error) {
382-
return GetGlobalLimiterStateFromPool(ctx, kv().Pool(), tokenBucketGlobalPrefix)
382+
return GetGlobalLimiterStateFromStore(kv(), tokenBucketGlobalPrefix)
383383
}
384384

385-
func GetGlobalLimiterStateFromPool(ctx context.Context, pool *redis.Pool, prefix string) (map[string]GlobalLimiterInfo, error) {
386-
conn, err := pool.GetContext(ctx)
387-
if err != nil {
388-
return nil, errors.Wrap(err, "failed to get connection")
389-
}
390-
defer conn.Close()
391-
385+
func GetGlobalLimiterStateFromStore(rstore redispool.KeyValue, prefix string) (map[string]GlobalLimiterInfo, error) {
392386
// First, find all known limiters in redis.
393-
resp, err := conn.Do("KEYS", fmt.Sprintf("%s:*:%s", prefix, bucketAllowedBurstKeySuffix))
387+
keys, err := rstore.Keys(fmt.Sprintf("%s:*:%s", prefix, bucketAllowedBurstKeySuffix))
394388
if err != nil {
395389
return nil, errors.Wrap(err, "failed to list keys")
396390
}
397-
keys, ok := resp.([]interface{})
398-
if !ok {
399-
return nil, errors.Newf("invalid response from redis keys command, expected []interface{}, got %T", resp)
400-
}
401391

402392
m := make(map[string]GlobalLimiterInfo, len(keys))
403-
for _, k := range keys {
404-
kchars, ok := k.([]uint8)
405-
if !ok {
406-
return nil, errors.Newf("invalid response from redis keys command, expected string, got %T", k)
407-
}
408-
key := string(kchars)
393+
for _, key := range keys {
409394
limiterName := strings.TrimSuffix(strings.TrimPrefix(key, prefix+":"), ":"+bucketAllowedBurstKeySuffix)
410395
rlKeys := getRateLimiterKeys(prefix, limiterName)
411396

412-
rstore := redispool.RedisKeyValue(pool)
413-
414397
currentCapacity, err := rstore.Get(rlKeys.BucketKey).Int()
415398
if err != nil && err != redis.ErrNil {
416399
return nil, errors.Wrap(err, "failed to read current capacity")
@@ -515,20 +498,10 @@ type TB interface {
515498
func SetupForTest(t TB) {
516499
t.Helper()
517500

518-
pool := &redis.Pool{
519-
MaxIdle: 3,
520-
IdleTimeout: 240 * time.Second,
521-
Dial: func() (redis.Conn, error) {
522-
return redis.Dial("tcp", "127.0.0.1:6379")
523-
},
524-
TestOnBorrow: func(c redis.Conn, t time.Time) error {
525-
_, err := c.Do("PING")
526-
return err
527-
},
528-
}
501+
kvMock = redispool.NewTestKeyValue()
529502

530503
tokenBucketGlobalPrefix = "__test__" + t.Name()
531-
c := pool.Get()
504+
c := kvMock.Pool().Get()
532505
defer c.Close()
533506

534507
// If we are not on CI, skip the test if our redis connection fails.
@@ -539,7 +512,6 @@ func SetupForTest(t TB) {
539512
}
540513
}
541514

542-
kvMock = redispool.RedisKeyValue(pool)
543515
if err := redispool.DeleteAllKeysWithPrefix(kvMock, tokenBucketGlobalPrefix); err != nil {
544516
t.Fatalf("could not clear test prefix: &v", err)
545517
}

internal/ratelimit/globallimiter_test.go

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ func TestGlobalRateLimiter(t *testing.T) {
2424
// This test is verifying the basic functionality of the rate limiter.
2525
// We should be able to get a token once the token bucket config is set.
2626
prefix := "__test__" + t.Name()
27-
pool := redisPoolForTest(t, prefix)
28-
rl := getTestRateLimiter(prefix, pool, testBucketName)
27+
kv := redisKeyValueForTest(t, prefix)
28+
rl := getTestRateLimiter(prefix, kv.Pool(), testBucketName)
2929

3030
clock := glock.NewMockClock()
3131
rl.nowFunc = clock.Now
@@ -135,8 +135,8 @@ func TestGlobalRateLimiter_TimeToWaitExceedsLimit(t *testing.T) {
135135
// This test is verifying that if the amount of time needed to wait for a token
136136
// exceeds the context deadline, a TokenGrantExceedsLimitError is returned.
137137
prefix := "__test__" + t.Name()
138-
pool := redisPoolForTest(t, prefix)
139-
rl := getTestRateLimiter(prefix, pool, testBucketName)
138+
kv := redisKeyValueForTest(t, prefix)
139+
rl := getTestRateLimiter(prefix, kv.Pool(), testBucketName)
140140

141141
clock := glock.NewMockClock()
142142
rl.nowFunc = clock.Now
@@ -176,8 +176,8 @@ func TestGlobalRateLimiter_TimeToWaitExceedsLimit(t *testing.T) {
176176
func TestGlobalRateLimiter_AllBlockedError(t *testing.T) {
177177
// Verify that a limit of 0 means "block all".
178178
prefix := "__test__" + t.Name()
179-
pool := redisPoolForTest(t, prefix)
180-
rl := getTestRateLimiter(prefix, pool, testBucketName)
179+
kv := redisKeyValueForTest(t, prefix)
180+
rl := getTestRateLimiter(prefix, kv.Pool(), testBucketName)
181181

182182
clock := glock.NewMockClock()
183183
rl.nowFunc = clock.Now
@@ -205,8 +205,8 @@ func TestGlobalRateLimiter_AllBlockedError(t *testing.T) {
205205
func TestGlobalRateLimiter_Inf(t *testing.T) {
206206
// Verify that a rate of -1 means inf.
207207
prefix := "__test__" + t.Name()
208-
pool := redisPoolForTest(t, prefix)
209-
rl := getTestRateLimiter(prefix, pool, testBucketName)
208+
kv := redisKeyValueForTest(t, prefix)
209+
rl := getTestRateLimiter(prefix, kv.Pool(), testBucketName)
210210

211211
clock := glock.NewMockClock()
212212
rl.nowFunc = clock.Now
@@ -235,8 +235,8 @@ func TestGlobalRateLimiter_UnconfiguredLimiter(t *testing.T) {
235235
// This test is verifying the basic functionality of the rate limiter.
236236
// We should be able to get a token once the token bucket config is set.
237237
prefix := "__test__" + t.Name()
238-
pool := redisPoolForTest(t, prefix)
239-
rl := getTestRateLimiter(prefix, pool, testBucketName)
238+
kv := redisKeyValueForTest(t, prefix)
239+
rl := getTestRateLimiter(prefix, kv.Pool(), testBucketName)
240240

241241
clock := glock.NewMockClock()
242242
rl.nowFunc = clock.Now
@@ -303,32 +303,23 @@ func getTestRateLimiter(prefix string, pool *redis.Pool, bucketName string) glob
303303

304304
// Mostly copy-pasta from rache. Will clean up later as the relationship
305305
// between the two packages becomes cleaner.
306-
func redisPoolForTest(t *testing.T, prefix string) *redis.Pool {
306+
func redisKeyValueForTest(t *testing.T, prefix string) redispool.KeyValue {
307307
t.Helper()
308308

309-
pool := &redis.Pool{
310-
MaxIdle: 3,
311-
IdleTimeout: 240 * time.Second,
312-
Dial: func() (redis.Conn, error) {
313-
return redis.Dial("tcp", "127.0.0.1:6379")
314-
},
315-
TestOnBorrow: func(c redis.Conn, t time.Time) error {
316-
_, err := c.Do("PING")
317-
return err
318-
},
319-
}
320-
321-
if err := redispool.DeleteAllKeysWithPrefix(redispool.RedisKeyValue(pool), prefix); err != nil {
309+
store := redispool.NewTestKeyValue()
310+
if err := redispool.DeleteAllKeysWithPrefix(store, prefix); err != nil {
322311
t.Logf("Could not clear test prefix name=%q prefix=%q error=%v", t.Name(), prefix, err)
323312
}
324313

325-
return pool
314+
return store
326315
}
327316

328317
func TestLimitInfo(t *testing.T) {
329318
ctx := context.Background()
330319
prefix := "__test__" + t.Name()
331-
pool := redisPoolForTest(t, prefix)
320+
321+
store := redisKeyValueForTest(t, prefix)
322+
pool := store.Pool()
332323

333324
r1 := getTestRateLimiter(prefix, pool, "extsvc:github:1")
334325
// 1/s allowed.
@@ -340,7 +331,7 @@ func TestLimitInfo(t *testing.T) {
340331
// No requests allowed.
341332
require.NoError(t, r3.SetTokenBucketConfig(ctx, 0, time.Hour))
342333

343-
info, err := GetGlobalLimiterStateFromPool(ctx, pool, prefix)
334+
info, err := GetGlobalLimiterStateFromStore(store, prefix)
344335
require.NoError(t, err)
345336

346337
if diff := cmp.Diff(map[string]GlobalLimiterInfo{
@@ -372,7 +363,7 @@ func TestLimitInfo(t *testing.T) {
372363
// Now claim 3 tokens from the limiter.
373364
require.NoError(t, r1.WaitN(ctx, 3))
374365

375-
info, err = GetGlobalLimiterStateFromPool(ctx, pool, prefix)
366+
info, err = GetGlobalLimiterStateFromStore(store, prefix)
376367
require.NoError(t, err)
377368

378369
if diff := cmp.Diff(map[string]GlobalLimiterInfo{

0 commit comments

Comments
 (0)