From 8b8bd3c40d65235c3df96ee35f68eedb5470b9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Mon, 30 Jun 2025 14:16:01 +0000 Subject: [PATCH] Use old value and update in other goroutine --- transport/internet/tls/ech.go | 53 ++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index e624fc9b..8327f359 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -12,6 +12,7 @@ import ( "net/http" "strings" "sync" + "sync/atomic" "time" "github.com/miekg/dns" @@ -76,11 +77,31 @@ func ApplyECH(c *Config, config *tls.Config) error { } type ECHConfigCache struct { - echConfig []byte - expire time.Time + echConfig atomic.Pointer[[]byte] + expire *time.Time updateLock sync.Mutex } +func (c *ECHConfigCache) update(domain string, server string) ([]byte, error) { + c.updateLock.Lock() + defer c.updateLock.Unlock() + // Double check cache after acquiring lock + if c.expire.After(time.Now()) { + errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain) + return *c.echConfig.Load(), nil + } + // Query ECH config from DNS server + errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) + echConfig, ttl, err := dnsQuery(server, domain) + if err != nil { + return nil, err + } + c.echConfig.Store(&echConfig) + expire := time.Now().Add(time.Duration(ttl) * time.Second) + c.expire = &expire + return *c.echConfig.Load(), nil +} + var ( GlobalECHConfigCache map[string]*ECHConfigCache GlobalECHConfigCacheAccess sync.Mutex @@ -99,7 +120,7 @@ func QueryRecord(domain string, server string) ([]byte, error) { if echConfigCache != nil && echConfigCache.expire.After(time.Now()) { errors.LogDebug(context.Background(), "Cache hit for domain: ", domain) GlobalECHConfigCacheAccess.Unlock() - return echConfigCache.echConfig, nil + return *echConfigCache.echConfig.Load(), nil } if echConfigCache == nil { echConfigCache = &ECHConfigCache{} @@ -107,26 +128,14 @@ func QueryRecord(domain string, server string) ([]byte, error) { } GlobalECHConfigCacheAccess.Unlock() - echConfigCache.updateLock.Lock() - defer echConfigCache.updateLock.Unlock() - // Double check cache after acquiring lock - if echConfigCache.expire.After(time.Now()) { - errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain) - return echConfigCache.echConfig, nil + // If expire is nil, it means we are in initial state, wait for the query to finish + // otherwise return old value immediately and update in a goroutine + if echConfigCache.expire == nil { + return echConfigCache.update(domain, server) + } else { + go echConfigCache.update(domain, server) + return *echConfigCache.echConfig.Load(), nil } - // Query ECH config from DNS server - errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) - echConfig, ttl, err := dnsQuery(server, domain) - if err != nil { - return nil, err - } - // Set minimum TTL to 600 seconds - if ttl < 600 { - ttl = 600 - } - echConfigCache.echConfig = echConfig - echConfigCache.expire = time.Now().Add(time.Second * time.Duration(ttl)) - return echConfigCache.echConfig, nil } // dnsQuery is the real func for sending type65 query for given domain to given DNS server.