mirror of https://github.com/fail2ban/fail2ban
				
				
				
			closes gh-2277: fixed and optimized cache facilities (operations on OrderedDict are not atomic); increased max-size of IPAddr cache; don't cache raw objects (it is fast enough).
							parent
							
								
									14f997231d
								
							
						
					
					
						commit
						e30ebb1f3b
					
				| 
						 | 
				
			
			@ -197,7 +197,7 @@ class IPAddr(object):
 | 
			
		|||
	__slots__ = '_family','_addr','_plen','_maskplen','_raw'
 | 
			
		||||
 | 
			
		||||
	# todo: make configurable the expired time and max count of cache entries:
 | 
			
		||||
	CACHE_OBJ = Utils.Cache(maxCount=1000, maxTime=5*60)
 | 
			
		||||
	CACHE_OBJ = Utils.Cache(maxCount=10000, maxTime=5*60)
 | 
			
		||||
 | 
			
		||||
	CIDR_RAW = -2
 | 
			
		||||
	CIDR_UNSPEC = -1
 | 
			
		||||
| 
						 | 
				
			
			@ -205,6 +205,10 @@ class IPAddr(object):
 | 
			
		|||
	FAM_IPv6 = CIDR_RAW - socket.AF_INET6
 | 
			
		||||
 | 
			
		||||
	def __new__(cls, ipstr, cidr=CIDR_UNSPEC):
 | 
			
		||||
		if cidr == IPAddr.CIDR_RAW: # don't cache raw
 | 
			
		||||
			ip = super(IPAddr, cls).__new__(cls)
 | 
			
		||||
			ip.__init(ipstr, cidr)
 | 
			
		||||
			return ip
 | 
			
		||||
		# check already cached as IPAddr
 | 
			
		||||
		args = (ipstr, cidr)
 | 
			
		||||
		ip = IPAddr.CACHE_OBJ.get(args)
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +225,8 @@ class IPAddr(object):
 | 
			
		|||
					return ip
 | 
			
		||||
		ip = super(IPAddr, cls).__new__(cls)
 | 
			
		||||
		ip.__init(ipstr, cidr)
 | 
			
		||||
		IPAddr.CACHE_OBJ.set(args, ip)
 | 
			
		||||
		if ip._family != IPAddr.CIDR_RAW:
 | 
			
		||||
			IPAddr.CACHE_OBJ.set(args, ip)
 | 
			
		||||
		return ip
 | 
			
		||||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,31 +95,35 @@ class Utils():
 | 
			
		|||
			
 | 
			
		||||
		def set(self, k, v):
 | 
			
		||||
			t = time.time()
 | 
			
		||||
			cache = self._cache  # for shorter local access
 | 
			
		||||
			# clean cache if max count reached:
 | 
			
		||||
			if len(cache) >= self.maxCount:
 | 
			
		||||
				# avoid multiple modification of list multi-threaded:
 | 
			
		||||
				with self.__lock:
 | 
			
		||||
					if len(cache) >= self.maxCount:
 | 
			
		||||
						for (ck, cv) in cache.items():
 | 
			
		||||
			# avoid multiple modification of dict multi-threaded:
 | 
			
		||||
			cache = self._cache
 | 
			
		||||
			with self.__lock:
 | 
			
		||||
				# clean cache if max count reached:
 | 
			
		||||
				if len(cache) >= self.maxCount:
 | 
			
		||||
					if OrderedDict is not dict:
 | 
			
		||||
						# ordered (so remove some from ahead, FIFO)
 | 
			
		||||
						while cache:
 | 
			
		||||
							(ck, cv) = cache.popitem(last=False)
 | 
			
		||||
							# if not yet expired (but has free slot for new entry):
 | 
			
		||||
							if cv[1] > t and len(cache) < self.maxCount:
 | 
			
		||||
								break
 | 
			
		||||
					else: # pragma: 3.x no cover (dict is in 2.6 only)
 | 
			
		||||
						remlst = []
 | 
			
		||||
						for (ck, cv) in cache.iteritems():
 | 
			
		||||
							# if expired:
 | 
			
		||||
							if cv[1] <= t:
 | 
			
		||||
								self.unset(ck)
 | 
			
		||||
							elif OrderedDict is not dict:
 | 
			
		||||
								break
 | 
			
		||||
								remlst.append(ck)
 | 
			
		||||
						for ck in remlst:
 | 
			
		||||
							self._cache.pop(ck, None)
 | 
			
		||||
						# if still max count - remove any one:
 | 
			
		||||
						if len(cache) >= self.maxCount:
 | 
			
		||||
							if OrderedDict is not dict: # first (older):
 | 
			
		||||
								cache.popitem(False)
 | 
			
		||||
							else: # pragma: 3.x no cover
 | 
			
		||||
								cache.popitem()
 | 
			
		||||
			cache[k] = (v, t + self.maxTime)
 | 
			
		||||
						while cache and len(cache) >= self.maxCount:
 | 
			
		||||
							cache.popitem()
 | 
			
		||||
				# set now:
 | 
			
		||||
				cache[k] = (v, t + self.maxTime)
 | 
			
		||||
 | 
			
		||||
		def unset(self, k):
 | 
			
		||||
			try:
 | 
			
		||||
				del self._cache[k]
 | 
			
		||||
			except KeyError:
 | 
			
		||||
				pass
 | 
			
		||||
			with self.__lock:
 | 
			
		||||
				self._cache.pop(k, None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@staticmethod
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue