mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-23 00:58:43 +02:00
state: replace zcache with bounded LRU for auth cache
Replace zcache with golang-lru/v2/expirable for both the state auth cache and the OIDC state cache. Add tuning.register_cache_max_entries (default 1024) to cap the number of pending registration entries. Introduce types.RegistrationData to replace caching a full *Node; only the fields the registration callback path reads are retained. Remove the dead HSDatabase.regCache field. Drop zgo.at/zcache/v2 from go.mod.
This commit is contained in:
64
hscontrol/state/auth_cache_test.go
Normal file
64
hscontrol/state/auth_cache_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAuthCacheBoundedLRU verifies that the registration auth cache is
|
||||
// bounded by a maximum entry count, that exceeding the maxEntries evicts the
|
||||
// oldest entry, and that the eviction callback resolves the parked
|
||||
// AuthRequest with ErrRegistrationExpired so any waiting goroutine wakes.
|
||||
func TestAuthCacheBoundedLRU(t *testing.T) {
|
||||
const maxEntries = 4
|
||||
|
||||
cache := expirable.NewLRU[types.AuthID, *types.AuthRequest](
|
||||
maxEntries,
|
||||
func(_ types.AuthID, rn *types.AuthRequest) {
|
||||
rn.FinishAuth(types.AuthVerdict{Err: ErrRegistrationExpired})
|
||||
},
|
||||
time.Hour, // long TTL — we test eviction by size, not by time
|
||||
)
|
||||
|
||||
entries := make([]*types.AuthRequest, 0, maxEntries+1)
|
||||
ids := make([]types.AuthID, 0, maxEntries+1)
|
||||
|
||||
for range maxEntries + 1 {
|
||||
id := types.MustAuthID()
|
||||
entry := types.NewAuthRequest()
|
||||
cache.Add(id, entry)
|
||||
ids = append(ids, id)
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
// Cap should be respected.
|
||||
assert.Equal(t, maxEntries, cache.Len(), "cache must not exceed the configured maxEntries")
|
||||
|
||||
// The oldest entry must have been evicted.
|
||||
_, ok := cache.Get(ids[0])
|
||||
assert.False(t, ok, "oldest entry must be evicted when maxEntries is exceeded")
|
||||
|
||||
// The eviction callback must have woken the parked AuthRequest.
|
||||
select {
|
||||
case verdict := <-entries[0].WaitForAuth():
|
||||
require.False(t, verdict.Accept(), "evicted entry must not signal Accept")
|
||||
require.ErrorIs(t,
|
||||
verdict.Err, ErrRegistrationExpired,
|
||||
"evicted entry must surface ErrRegistrationExpired, got: %v",
|
||||
verdict.Err,
|
||||
)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("eviction callback did not wake the parked AuthRequest")
|
||||
}
|
||||
|
||||
// All non-evicted entries must still be retrievable.
|
||||
for i := 1; i <= maxEntries; i++ {
|
||||
_, ok := cache.Get(ids[i])
|
||||
assert.True(t, ok, "non-evicted entry %d should still be in the cache", i)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user