Files
headscale/hscontrol/mapper/dns_extrarecords_race_test.go
T
Kristoffer Dalby 5a70a72988 types: lock tailcfg DNS config access for extra-records updates
The watcher write raced per-client map builds cloning ExtraRecords.
2026-06-08 10:04:49 +02:00

61 lines
1.4 KiB
Go

package mapper
import (
"sync"
"testing"
"github.com/juanfont/headscale/hscontrol/types"
"tailscale.com/tailcfg"
)
// TestExtraRecordsConcurrentUpdateNoRace exercises the extra-records watcher
// write path (Config.SetExtraRecords) concurrently with per-client DNS config
// builds (generateDNSConfig -> Config.CloneTailcfgDNSConfig). Both must go
// through the shared lock so the run is race-free under -race.
func TestExtraRecordsConcurrentUpdateNoRace(t *testing.T) {
uid := uint(1)
cfg := &types.Config{
TailcfgDNSConfig: &tailcfg.DNSConfig{
ExtraRecords: []tailcfg.DNSRecord{
{Name: "initial.example.com", Type: "A", Value: "100.64.0.1"},
},
},
}
node := (&types.Node{
Hostname: "race-node",
UserID: &uid,
User: &types.User{Name: "racer"},
}).View()
const iterations = 2000
var wg sync.WaitGroup
// Writer: the extra-records update path.
wg.Go(func() {
for i := range iterations {
recs := []tailcfg.DNSRecord{{Name: "a.example.com", Type: "A", Value: "100.64.0.2"}}
if i%2 == 0 {
recs = append(recs, tailcfg.DNSRecord{Name: "b.example.com", Type: "A", Value: "100.64.0.3"})
}
cfg.SetExtraRecords(recs)
}
})
// Readers: the per-client map build path.
const readers = 8
for range readers {
wg.Go(func() {
for range iterations {
if d := generateDNSConfig(cfg, node, nil); d != nil {
_ = len(d.ExtraRecords)
}
}
})
}
wg.Wait()
}