Use old value and update in other goroutine

This commit is contained in:
风扇滑翔翼 2025-06-30 14:16:01 +00:00 committed by GitHub
parent 54774ceca6
commit 8b8bd3c40d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -76,11 +77,31 @@ func ApplyECH(c *Config, config *tls.Config) error {
} }
type ECHConfigCache struct { type ECHConfigCache struct {
echConfig []byte echConfig atomic.Pointer[[]byte]
expire time.Time expire *time.Time
updateLock sync.Mutex 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 ( var (
GlobalECHConfigCache map[string]*ECHConfigCache GlobalECHConfigCache map[string]*ECHConfigCache
GlobalECHConfigCacheAccess sync.Mutex GlobalECHConfigCacheAccess sync.Mutex
@ -99,7 +120,7 @@ func QueryRecord(domain string, server string) ([]byte, error) {
if echConfigCache != nil && echConfigCache.expire.After(time.Now()) { if echConfigCache != nil && echConfigCache.expire.After(time.Now()) {
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain) errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
GlobalECHConfigCacheAccess.Unlock() GlobalECHConfigCacheAccess.Unlock()
return echConfigCache.echConfig, nil return *echConfigCache.echConfig.Load(), nil
} }
if echConfigCache == nil { if echConfigCache == nil {
echConfigCache = &ECHConfigCache{} echConfigCache = &ECHConfigCache{}
@ -107,26 +128,14 @@ func QueryRecord(domain string, server string) ([]byte, error) {
} }
GlobalECHConfigCacheAccess.Unlock() GlobalECHConfigCacheAccess.Unlock()
echConfigCache.updateLock.Lock() // If expire is nil, it means we are in initial state, wait for the query to finish
defer echConfigCache.updateLock.Unlock() // otherwise return old value immediately and update in a goroutine
// Double check cache after acquiring lock if echConfigCache.expire == nil {
if echConfigCache.expire.After(time.Now()) { return echConfigCache.update(domain, server)
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain) } else {
return echConfigCache.echConfig, nil 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. // dnsQuery is the real func for sending type65 query for given domain to given DNS server.