159 lines
4.2 KiB
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)
|
|
}
|
|
})
|
|
}
|
|
}
|