caddy/modules/internal/network/cache_test.go

159 lines
4.2 KiB
Go

// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"context"
"errors"
"testing"
"time"
"go.uber.org/zap"
)
func TestLookupGate(t *testing.T) {
g := newLookupGate(2, 1)
for i := range 2 {
if err := g.acquire(context.Background()); err != nil {
t.Fatalf("acquire %d: %v", i, err)
}
}
// the third caller queues; with the queue then full, a fourth is shed
queued := make(chan error, 1)
queuedCtx, cancelQueued := context.WithCancel(context.Background())
go func() { queued <- g.acquire(queuedCtx) }()
for g.pending.Load() != 1 {
time.Sleep(time.Millisecond)
}
if err := g.acquire(context.Background()); !errors.Is(err, errTooManyLookups) {
t.Fatalf("acquire with full queue: err = %v, want errTooManyLookups", err)
}
// a queued caller can give up
cancelQueued()
if err := <-queued; !errors.Is(err, context.Canceled) {
t.Fatalf("cancelled queued acquire: err = %v, want context.Canceled", err)
}
// and a released slot is usable again
g.release()
if err := g.acquire(context.Background()); err != nil {
t.Fatalf("acquire after release: %v", err)
}
}
func TestEvictedResetMarkerRejectsStaleLookup(t *testing.T) {
c := newCache("test")
started := make(chan struct{})
release := make(chan struct{})
done := make(chan error, 1)
go func() {
_, err := c.get(context.Background(), "key", time.Minute, 0, zap.NewNop(), func(context.Context) ([]Target, error) {
close(started)
<-release
return []Target{{Host: "old.example"}}, nil
})
done <- err
}()
<-started
c.reset("key")
c.mu.Lock()
delete(c.entries, "key")
c.mu.Unlock()
close(release)
if err := <-done; err != nil {
t.Fatal(err)
}
c.mu.RLock()
_, exists := c.entries["key"]
c.mu.RUnlock()
if exists {
t.Fatal("lookup repopulated an entry after its reset marker was evicted")
}
}
func TestDifferentGracePoliciesDoNotShareLookup(t *testing.T) {
for _, tc := range []struct {
name string
graceFirst bool
}{
{name: "no grace first"},
{name: "grace first", graceFirst: true},
} {
t.Run(tc.name, func(t *testing.T) {
c := newCache("test")
c.entries["key"] = cacheEntry{
refresh: time.Minute,
freshness: time.Now().Add(-time.Hour),
targets: []Target{{Host: "stale.example"}},
}
started := make(chan struct{}, 2)
release := make(chan struct{})
lookup := func(context.Context) ([]Target, error) {
started <- struct{}{}
<-release
return nil, errors.New("lookup failed")
}
type result struct {
targets []Target
err error
}
start := func(grace time.Duration) <-chan result {
ch := make(chan result, 1)
go func() {
targets, err := c.get(context.Background(), "key", time.Minute, grace, zap.NewNop(), lookup)
ch <- result{targets, err}
}()
return ch
}
firstGrace := time.Duration(0)
secondGrace := time.Minute
if tc.graceFirst {
firstGrace, secondGrace = secondGrace, firstGrace
}
first := start(firstGrace)
<-started
second := start(secondGrace)
select {
case <-started:
case <-time.After(time.Second):
close(release)
<-first
<-second
t.Fatalf("different grace policies shared one lookup")
}
close(release)
firstResult := <-first
secondResult := <-second
if tc.graceFirst {
firstResult, secondResult = secondResult, firstResult
}
if firstResult.err == nil {
t.Fatal("caller without grace did not receive the lookup error")
}
if secondResult.err != nil || len(secondResult.targets) != 1 || secondResult.targets[0].Host != "stale.example" {
t.Fatalf("caller with grace: targets = %v, err = %v", secondResult.targets, secondResult.err)
}
})
}
}