func (r *Resolver) lookupFunc(key string) func() (interface{}, error) {
	if len(key) == 0 {
		panic("lookupFunc with empty key")
	}
	var resolver DNSResolver = net.DefaultResolver
	if r.Resolver != nil {
		resolver = r.Resolver
	}
	switch key[0] {
	case 'h':
		return func() (interface{}, error) {
			ctx, cancel := r.getCtx()
			defer cancel()
			return resolver.LookupHost(ctx, key[1:])
		}
	case 'r':
		return func() (interface{}, error) {
			ctx, cancel := r.getCtx()
			defer cancel()
			return resolver.LookupAddr(ctx, key[1:])
		}
	default:
		panic("lookupFunc invalid key type: " + key)
	}
}
lookupFunc returns lookup function for key. The type of the key is stored as
the first char and the lookup subject is the rest of the key.
package dnscache
import (
	"context"
	"net"
	"sync"
	"time"
	"golang.org/x/sync/singleflight"
)
type DNSResolver interface {
	LookupHost(ctx context.Context, host string) (addrs []string, err error)
	LookupAddr(ctx context.Context, addr string) (names []string, err error)
}
type Resolver struct {
	Timeout time.Duration
	Resolver DNSResolver
	once  sync.Once
	mu    sync.RWMutex
	cache map[string]*cacheEntry
	OnCacheMiss func()
}
type ResolverRefreshOptions struct {
	ClearUnused      bool
	PersistOnFailure bool
}
type cacheEntry struct {
	rrs  []string
	err  error
	used bool
}
func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error) {
	r.once.Do(r.init)
	return r.lookup(ctx, "r"+addr)
}
func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) {
	r.once.Do(r.init)
	return r.lookup(ctx, "h"+host)
}
func (r *Resolver) refreshRecords(clearUnused bool, persistOnFailure bool) {
	r.once.Do(r.init)
	r.mu.RLock()
	update := make([]string, 0, len(r.cache))
	del := make([]string, 0, len(r.cache))
	for key, entry := range r.cache {
		if entry.used {
			update = append(update, key)
		} else if clearUnused {
			del = append(del, key)
		}
	}
	r.mu.RUnlock()
	if len(del) > 0 {
		r.mu.Lock()
		for _, key := range del {
			delete(r.cache, key)
		}
		r.mu.Unlock()
	}
	for _, key := range update {
		r.update(context.Background(), key, false, persistOnFailure)
	}
}
func (r *Resolver) Refresh(clearUnused bool) {
	r.refreshRecords(clearUnused, false)
}
func (r *Resolver) RefreshWithOptions(options ResolverRefreshOptions) {
	r.refreshRecords(options.ClearUnused, options.PersistOnFailure)
}
func (r *Resolver) init() {
	r.cache = make(map[string]*cacheEntry)
}
var lookupGroup singleflight.Group
func (r *Resolver) lookup(ctx context.Context, key string) (rrs []string, err error) {
	var found bool
	rrs, err, found = r.load(key)
	if !found {
		if r.OnCacheMiss != nil {
			r.OnCacheMiss()
		}
		rrs, err = r.update(ctx, key, true, false)
	}
	return
}
func (r *Resolver) update(ctx context.Context, key string, used bool, persistOnFailure bool) (rrs []string, err error) {
	c := lookupGroup.DoChan(key, r.lookupFunc(key))
	select {
	case <-ctx.Done():
		err = ctx.Err()
		if err == context.DeadlineExceeded {
			lookupGroup.Forget(key)
		}
	case res := <-c:
		if res.Shared {
			var found bool
			rrs, err, found = r.load(key)
			if found {
				return
			}
		}
		err = res.Err
		if err == nil {
			rrs, _ = res.Val.([]string)
		}
		if err != nil && persistOnFailure {
			var found bool
			rrs, err, found = r.load(key)
			if found {
				return
			}
		}
		r.mu.Lock()
		r.storeLocked(key, rrs, used, err)
		r.mu.Unlock()
	}
	return
}