Files
traefikoidc/internal/cache/backends/review_r76_test.go
T

85 lines
2.5 KiB
Go

//go:build !yaegi
package backends
import (
"context"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// gatedSetBackend wraps a mockBackend whose Set blocks until release is
// closed (once). Lets the test park the backfill worker mid-flight.
type gatedSetBackend struct {
*mockBackend
startOnce sync.Once
started chan struct{}
release chan struct{}
}
func (b *gatedSetBackend) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
b.startOnce.Do(func() { close(b.started) })
<-b.release
return b.mockBackend.Set(ctx, key, value, ttl)
}
// TestHybridBackend_L1Backfill_NoResurrectAfterDelete regresses the L1
// backfill worker resurrecting a key that was deleted after the L2 read
// (Get) captured its value. The worker must re-check L2 and not write a
// value into L1 for a key that is gone from L2.
func TestHybridBackend_L1Backfill_NoResurrectAfterDelete(t *testing.T) {
const key = "k"
inner := newMockBackend()
gated := &gatedSetBackend{mockBackend: inner, started: make(chan struct{}), release: make(chan struct{})}
secondary := newMockBackend()
hybrid, err := NewHybridBackend(&HybridConfig{
Primary: gated,
Secondary: secondary,
AsyncBufferSize: 64,
})
require.NoError(t, err)
defer hybrid.Close()
ctx := context.Background()
// Park the worker on a filler backfill first so `key` (enqueued after
// the filler) is processed only once we delete it.
require.NoError(t, secondary.Set(ctx, "filler", []byte("x"), time.Minute))
_, _, exists, err := hybrid.Get(ctx, "filler")
require.NoError(t, err)
require.True(t, exists)
select {
case <-gated.started:
case <-time.After(2 * time.Second):
t.Fatal("worker never parked on filler Set")
}
// Now trigger a backfill for `key` (this appends it behind the parked
// filler), then delete it before the worker can reach it.
require.NoError(t, secondary.Set(ctx, key, []byte("v"), time.Minute))
_, _, exists, err = hybrid.Get(ctx, key)
require.NoError(t, err)
require.True(t, exists)
_, err = hybrid.Delete(ctx, key)
require.NoError(t, err)
close(gated.release)
// Wait for the worker to drain, failing if the deleted key ever appears
// in L1 (resurrection). On the fixed code it re-checks L2 and skips;
// on the buggy code it writes the stale value shortly after release.
deadline := time.Now().Add(1 * time.Second)
for time.Now().Before(deadline) {
if _, _, ok, _ := inner.Get(ctx, key); ok {
t.Fatal("deleted key was resurrected into L1 by the backfill worker")
}
time.Sleep(5 * time.Millisecond)
}
}