#7904·coredns

Enhancement: deduplicate prefetch goroutines in cache plugin

Author: sammiee5311Created Mar 6, 2026Updated Sep 14, 2026
Labelsenhancement

Hello coredns team,

What would you like to be added:

Add deduplication to the doPrefetch goroutine path in the cache plugin. Currently, when serve_stale is enabled and a cache entry goes stale, every concurrent request for the same domain spawns a separate go c.doPrefetch() goroutine (handler.go:58-59), even though they all do the same upstream query.

Why is this needed:

When the upstream is slow or temporarily unreachable, these duplicate prefetch goroutines pile up since each one blocks until the forward timeout (~2s). In a quick test, 1,000 concurrent queries for a single stale domain created 1,001 goroutines all doing the same work. With deduplication, only one goroutine would be spawned per unique query.

The same applies to the shouldPrefetch path (handler.go:63-64).

Example using a sync.Map to skip spawning a goroutine when one is already in-flight for the same query:

go
// In Cache struct, add:
prefetching sync.Map

// In the serve_stale path:
if !c.verifyStale {
    k := hash(state.Name(), state.QType(), state.Do(), state.Req.CheckingDisabled)
    if _, loaded := c.prefetching.LoadOrStore(k, true); !loaded {
        go func() {
            defer c.prefetching.Delete(k)
            cw := newPrefetchResponseWriter(server, state, c)
            c.doPrefetch(ctx, state, cw, i, now)
        }()
    }
}

Happy to submit a PR if this sounds reasonable.

Thanks!