Fix for negative TTLs by using a different lookups set depending on the value stored in the cache.

This commit is contained in:
Alvaro Saurin committed 2015-04-09 16:17:19 +02:00
1 parent e66225ca9b
commit 6f545a4296
3 files changed
+53 -149

No files matched your search

+16 -68
View File
@@ -22,6 +22,8 @@ const (
defPendingTimeout int = 5 // timeout for a resolution
)
const nullTTL = 0 // a null TTL
type entryStatus uint8
const (
@@ -61,24 +63,15 @@ type cacheEntry struct {
validUntil time.Time // obtained from the reply and stored here for convenience/speed
putTime time.Time
waitChan chan struct{}
index int // for fast lookups in the heap
}
func newCacheEntry(question *dns.Question, reply *dns.Msg, status entryStatus, flags uint8, now time.Time) *cacheEntry {
func newCacheEntry(question *dns.Question, now time.Time) *cacheEntry {
e := &cacheEntry{
Status: status,
Flags: flags,
question: *question,
index: -1,
}
if e.Status == stPending {
e.validUntil = now.Add(time.Duration(defPendingTimeout) * time.Second)
e.waitChan = make(chan struct{})
} else {
e.setReply(reply, flags, now)
Status: stPending,
validUntil: now.Add(time.Second * time.Duration(defPendingTimeout)),
question: *question,
index: -1,
}
return e
@@ -133,9 +126,7 @@ func (e cacheEntry) hasExpired(now time.Time) bool {
// set the reply for the entry
// returns True if the entry has changed the validUntil time
func (e *cacheEntry) setReply(reply *dns.Msg, flags uint8, now time.Time) bool {
shouldNotify := (e.Status == stPending)
func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Time) bool {
var prevValidUntil time.Time
if e.Status == stResolved {
prevValidUntil = e.validUntil
@@ -145,10 +136,9 @@ func (e *cacheEntry) setReply(reply *dns.Msg, flags uint8, now time.Time) bool {
e.Flags = flags
e.putTime = now
if e.Flags&CacheNoLocalReplies != 0 {
// use a fixed timeout for negative local resolutions
e.validUntil = now.Add(time.Second * time.Duration(negLocalTTL))
} else {
if ttl != nullTTL {
e.validUntil = now.Add(time.Second * time.Duration(ttl))
} else if reply != nil {
// calculate the validUntil from the reply TTL
var minTTL uint32 = math.MaxUint32
for _, rr := range reply.Answer {
@@ -165,37 +155,9 @@ func (e *cacheEntry) setReply(reply *dns.Msg, flags uint8, now time.Time) bool {
e.ReplyLen = reply.Len()
}
if shouldNotify {
close(e.waitChan) // notify all the waiters by closing the channel
}
return (prevValidUntil != e.validUntil)
}
// wait until a valid reply is set in the cache
func (e *cacheEntry) waitReply(request *dns.Msg, timeout time.Duration, maxLen int, now time.Time) (*dns.Msg, error) {
if e.Status == stResolved {
return e.getReply(request, maxLen, now)
}
if timeout > 0 {
select {
case <-e.waitChan:
return e.getReply(request, maxLen, now)
case <-time.After(time.Second * timeout):
return nil, errTimeout
}
}
return nil, errCouldNotResolve
}
func (e *cacheEntry) close() {
if e.Status == stPending {
close(e.waitChan)
}
}
//////////////////////////////////////////////////////////////////////////////////////
// An entriesPtrHeap is a min-heap of cache entries.
@@ -280,7 +242,7 @@ func (c *Cache) Purge(now time.Time) {
}
// Add adds a reply to the cache.
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, flags uint8, now time.Time) int {
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8, now time.Time) int {
c.lock.Lock()
defer c.lock.Unlock()
@@ -288,8 +250,7 @@ func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, flags uint8, now time.Time
key := cacheKey(question)
ent, found := c.entries[key]
if found {
Debug.Printf("[cache msgid %d] replacing response in cache", request.MsgHdr.Id)
updated := ent.setReply(reply, flags, now)
updated := ent.setReply(reply, ttl, flags, now)
if updated {
heap.Fix(&c.entriesH, ent.index)
}
@@ -297,10 +258,10 @@ func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, flags uint8, now time.Time
// If we will add a new item and the capacity has been exceeded, make some room...
if len(c.entriesH) >= c.Capacity {
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
lowestEntry.close()
delete(c.entries, cacheKey(lowestEntry.question))
}
ent = newCacheEntry(&question, reply, stResolved, flags, now)
ent = newCacheEntry(&question, now)
ent.setReply(reply, ttl, flags, now)
heap.Push(&c.entriesH, ent)
c.entries[key] = ent
}
@@ -329,20 +290,7 @@ func (c *Cache) Get(request *dns.Msg, maxLen int, now time.Time) (reply *dns.Msg
} else {
// we are the first asking for this name: create an entry with no reply... the caller must wait
Debug.Printf("[cache msgid %d] addind in pending state", request.MsgHdr.Id)
c.entries[key] = newCacheEntry(&question, nil, stPending, 0, now)
}
return
}
// Wait for a reply for a question in the cache
// Notice that the caller could Get() and then Wait() for a question, but the corresponding cache
// entry could have been removed in between. In that case, the caller should retry the query (and
// the user should increase the cache size!)
func (c *Cache) Wait(request *dns.Msg, timeout time.Duration, maxLen int, now time.Time) (reply *dns.Msg, err error) {
// do not try to lock the cache: otherwise, no one else could `Put()` the reply
question := request.Question[0]
if entry, found := c.entries[cacheKey(question)]; found {
reply, err = entry.waitReply(request, timeout, maxLen, now)
c.entries[key] = newCacheEntry(&question, now)
}
return
}