[client] Scope mgmt cache initial resolve to server lifetime

Background initial resolves used context.Background(), so they outlived
a server Stop() and kept running (up to dnsTimeout) against a cache
that was already being discarded. Thread the server-lifetime ctx from
UpdateFromServerDomains through kickoffResolve into AddDomain so Stop()
cancels in-flight resolves. The ctx is not per-sync, so a fast-returning
sync still won't cancel them.
This commit is contained in:
Zoltán Papp
2026-06-22 22:19:51 +02:00
parent b6c79f1f71
commit 08ac4855f6
+11 -9
View File
@@ -520,9 +520,10 @@ func (m *Resolver) GetCachedDomains() domain.List {
// Empty updates are ignored to prevent clearing infrastructure domains during partial updates. // Empty updates are ignored to prevent clearing infrastructure domains during partial updates.
// UpdateFromServerDomains records the requested domains and kicks off their // UpdateFromServerDomains records the requested domains and kicks off their
// resolution in the background, returning without blocking on DNS so it stays // resolution in the background, returning without blocking on DNS so it stays
// off the engine sync lock held by the caller. ctx is unused: background // off the engine sync lock held by the caller. ctx scopes the background
// resolves use context.Background() so a fast-returning sync can't cancel them. // resolves to the server lifetime: it is not per-sync, so a fast-returning
func (m *Resolver) UpdateFromServerDomains(_ context.Context, serverDomains dnsconfig.ServerDomains) (domain.List, error) { // sync won't cancel them, but a server Stop will.
func (m *Resolver) UpdateFromServerDomains(ctx context.Context, serverDomains dnsconfig.ServerDomains) (domain.List, error) {
newDomains := m.extractDomainsFromServerDomains(serverDomains) newDomains := m.extractDomainsFromServerDomains(serverDomains)
var removedDomains domain.List var removedDomains domain.List
@@ -540,14 +541,14 @@ func (m *Resolver) UpdateFromServerDomains(_ context.Context, serverDomains dnsc
removedDomains = m.removeStaleDomains(currentDomains, allDomains) removedDomains = m.removeStaleDomains(currentDomains, allDomains)
} }
m.kickoffResolve(newDomains) m.kickoffResolve(ctx, newDomains)
return removedDomains, nil return removedDomains, nil
} }
// kickoffResolve marks each domain pending and starts a background resolve, // kickoffResolve marks each domain pending and starts a background resolve,
// skipping ones already fresh or in flight. Returns immediately. // skipping ones already fresh or in flight. Returns immediately.
func (m *Resolver) kickoffResolve(domains domain.List) { func (m *Resolver) kickoffResolve(ctx context.Context, domains domain.List) {
for _, d := range domains { for _, d := range domains {
dnsName := strings.ToLower(dns.Fqdn(d.PunycodeString())) dnsName := strings.ToLower(dns.Fqdn(d.PunycodeString()))
@@ -563,17 +564,18 @@ func (m *Resolver) kickoffResolve(domains domain.List) {
continue continue
} }
m.scheduleInitialResolve(d, dnsName) m.scheduleInitialResolve(ctx, d, dnsName)
} }
} }
// scheduleInitialResolve runs AddDomain in the background, deduped per domain // scheduleInitialResolve runs AddDomain in the background, deduped per domain
// by resolveGroup, clearing the pending marker when it finishes. // by resolveGroup, clearing the pending marker when it finishes. ctx is the
func (m *Resolver) scheduleInitialResolve(d domain.Domain, dnsName string) { // server-lifetime context so a Stop cancels in-flight resolves.
func (m *Resolver) scheduleInitialResolve(ctx context.Context, d domain.Domain, dnsName string) {
key := "initial|" + dnsName key := "initial|" + dnsName
_ = m.resolveGroup.DoChan(key, func() (any, error) { _ = m.resolveGroup.DoChan(key, func() (any, error) {
defer m.clearPending(dnsName) defer m.clearPending(dnsName)
if err := m.AddDomain(context.Background(), d); err != nil { if err := m.AddDomain(ctx, d); err != nil {
log.Warnf("failed to add/update domain=%s: %v", d.SafeString(), err) log.Warnf("failed to add/update domain=%s: %v", d.SafeString(), err)
return struct{}{}, err return struct{}{}, err
} }