package resolver import "strings" func RunTimeoutQuarantineRecheck( domains []string, now int, limit int, workers int, domainCache *DomainCacheState, cacheSourceForHost func(string) DomainCacheSource, resolveHost func(string) ([]string, DNSMetrics), ) ResolverTimeoutRecheckStats { stats := ResolverTimeoutRecheckStats{} if limit <= 0 || now <= 0 || domainCache == nil || resolveHost == nil { return stats } if workers < 1 { workers = 1 } if workers > 200 { workers = 200 } resolveSource := cacheSourceForHost if resolveSource == nil { resolveSource = func(string) DomainCacheSource { return DomainCacheSourceDirect } } seen := map[string]struct{}{} capHint := len(domains) if capHint > limit { capHint = limit } candidates := make([]string, 0, capHint) for _, raw := range domains { host := strings.TrimSpace(strings.ToLower(raw)) if host == "" { continue } if _, ok := seen[host]; ok { continue } seen[host] = struct{}{} source := resolveSource(host) if _, _, ok := domainCache.GetQuarantine(host, source, now); !ok { continue } kind, ok := domainCache.GetLastErrorKind(host, source) if !ok || kind != DNSErrorTimeout { continue } candidates = append(candidates, host) if len(candidates) >= limit { break } } if len(candidates) == 0 { return stats } recoveredIPSet := map[string]struct{}{} type result struct { host string source DomainCacheSource ips []string dns DNSMetrics } jobs := make(chan string, len(candidates)) results := make(chan result, len(candidates)) for i := 0; i < workers; i++ { go func() { for host := range jobs { src := resolveSource(host) ips, dnsStats := resolveHost(host) results <- result{host: host, source: src, ips: ips, dns: dnsStats} } }() } for _, host := range candidates { jobs <- host } close(jobs) for i := 0; i < len(candidates); i++ { r := <-results stats.Checked++ if len(r.ips) > 0 { for _, ip := range r.ips { ip = strings.TrimSpace(ip) if ip == "" { continue } recoveredIPSet[ip] = struct{}{} } domainCache.Set(r.host, r.source, r.ips, now) stats.Recovered++ continue } if r.dns.TotalErrors() > 0 { domainCache.SetErrorWithStats(r.host, r.source, r.dns, now) } kind, ok := ClassifyHostErrorKind(r.dns) if !ok { stats.NoSignal++ continue } switch kind { case DNSErrorTimeout: stats.StillTimeout++ case DNSErrorNXDomain: stats.NowNXDomain++ case DNSErrorTemporary: stats.NowTemporary++ default: stats.NowOther++ } } stats.RecoveredIPs = len(recoveredIPSet) return stats }