cache.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. # -*- test-case-name: twisted.names.test -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. An in-memory caching resolver.
  6. """
  7. from __future__ import division, absolute_import
  8. from twisted.names import dns, common
  9. from twisted.python import failure, log
  10. from twisted.internet import defer
  11. class CacheResolver(common.ResolverBase):
  12. """
  13. A resolver that serves records from a local, memory cache.
  14. @ivar _reactor: A provider of L{interfaces.IReactorTime}.
  15. """
  16. cache = None
  17. def __init__(self, cache=None, verbose=0, reactor=None):
  18. common.ResolverBase.__init__(self)
  19. self.cache = {}
  20. self.verbose = verbose
  21. self.cancel = {}
  22. if reactor is None:
  23. from twisted.internet import reactor
  24. self._reactor = reactor
  25. if cache:
  26. for query, (seconds, payload) in cache.items():
  27. self.cacheResult(query, payload, seconds)
  28. def __setstate__(self, state):
  29. self.__dict__ = state
  30. now = self._reactor.seconds()
  31. for (k, (when, (ans, add, ns))) in self.cache.items():
  32. diff = now - when
  33. for rec in ans + add + ns:
  34. if rec.ttl < diff:
  35. del self.cache[k]
  36. break
  37. def __getstate__(self):
  38. for c in self.cancel.values():
  39. c.cancel()
  40. self.cancel.clear()
  41. return self.__dict__
  42. def _lookup(self, name, cls, type, timeout):
  43. now = self._reactor.seconds()
  44. q = dns.Query(name, type, cls)
  45. try:
  46. when, (ans, auth, add) = self.cache[q]
  47. except KeyError:
  48. if self.verbose > 1:
  49. log.msg('Cache miss for ' + repr(name))
  50. return defer.fail(failure.Failure(dns.DomainError(name)))
  51. else:
  52. if self.verbose:
  53. log.msg('Cache hit for ' + repr(name))
  54. diff = now - when
  55. try:
  56. result = (
  57. [dns.RRHeader(r.name.name, r.type, r.cls, r.ttl - diff,
  58. r.payload) for r in ans],
  59. [dns.RRHeader(r.name.name, r.type, r.cls, r.ttl - diff,
  60. r.payload) for r in auth],
  61. [dns.RRHeader(r.name.name, r.type, r.cls, r.ttl - diff,
  62. r.payload) for r in add])
  63. except ValueError:
  64. return defer.fail(failure.Failure(dns.DomainError(name)))
  65. else:
  66. return defer.succeed(result)
  67. def lookupAllRecords(self, name, timeout = None):
  68. return defer.fail(failure.Failure(dns.DomainError(name)))
  69. def cacheResult(self, query, payload, cacheTime=None):
  70. """
  71. Cache a DNS entry.
  72. @param query: a L{dns.Query} instance.
  73. @param payload: a 3-tuple of lists of L{dns.RRHeader} records, the
  74. matching result of the query (answers, authority and additional).
  75. @param cacheTime: The time (seconds since epoch) at which the entry is
  76. considered to have been added to the cache. If L{None} is given,
  77. the current time is used.
  78. """
  79. if self.verbose > 1:
  80. log.msg('Adding %r to cache' % query)
  81. self.cache[query] = (cacheTime or self._reactor.seconds(), payload)
  82. if query in self.cancel:
  83. self.cancel[query].cancel()
  84. s = list(payload[0]) + list(payload[1]) + list(payload[2])
  85. if s:
  86. m = s[0].ttl
  87. for r in s:
  88. m = min(m, r.ttl)
  89. else:
  90. m = 0
  91. self.cancel[query] = self._reactor.callLater(m, self.clearEntry, query)
  92. def clearEntry(self, query):
  93. del self.cache[query]
  94. del self.cancel[query]