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'
 | 
						__slots__ = '_family','_addr','_plen','_maskplen','_raw'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# todo: make configurable the expired time and max count of cache entries:
 | 
						# 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_RAW = -2
 | 
				
			||||||
	CIDR_UNSPEC = -1
 | 
						CIDR_UNSPEC = -1
 | 
				
			||||||
| 
						 | 
					@ -205,6 +205,10 @@ class IPAddr(object):
 | 
				
			||||||
	FAM_IPv6 = CIDR_RAW - socket.AF_INET6
 | 
						FAM_IPv6 = CIDR_RAW - socket.AF_INET6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __new__(cls, ipstr, cidr=CIDR_UNSPEC):
 | 
						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
 | 
							# check already cached as IPAddr
 | 
				
			||||||
		args = (ipstr, cidr)
 | 
							args = (ipstr, cidr)
 | 
				
			||||||
		ip = IPAddr.CACHE_OBJ.get(args)
 | 
							ip = IPAddr.CACHE_OBJ.get(args)
 | 
				
			||||||
| 
						 | 
					@ -221,6 +225,7 @@ class IPAddr(object):
 | 
				
			||||||
					return ip
 | 
										return ip
 | 
				
			||||||
		ip = super(IPAddr, cls).__new__(cls)
 | 
							ip = super(IPAddr, cls).__new__(cls)
 | 
				
			||||||
		ip.__init(ipstr, cidr)
 | 
							ip.__init(ipstr, cidr)
 | 
				
			||||||
 | 
							if ip._family != IPAddr.CIDR_RAW:
 | 
				
			||||||
			IPAddr.CACHE_OBJ.set(args, ip)
 | 
								IPAddr.CACHE_OBJ.set(args, ip)
 | 
				
			||||||
		return ip
 | 
							return ip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,31 +95,35 @@ class Utils():
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
		def set(self, k, v):
 | 
							def set(self, k, v):
 | 
				
			||||||
			t = time.time()
 | 
								t = time.time()
 | 
				
			||||||
			cache = self._cache  # for shorter local access
 | 
								# avoid multiple modification of dict multi-threaded:
 | 
				
			||||||
 | 
								cache = self._cache
 | 
				
			||||||
 | 
								with self.__lock:
 | 
				
			||||||
				# clean cache if max count reached:
 | 
									# clean cache if max count reached:
 | 
				
			||||||
				if len(cache) >= self.maxCount:
 | 
									if len(cache) >= self.maxCount:
 | 
				
			||||||
				# avoid multiple modification of list multi-threaded:
 | 
										if OrderedDict is not dict:
 | 
				
			||||||
				with self.__lock:
 | 
											# ordered (so remove some from ahead, FIFO)
 | 
				
			||||||
					if len(cache) >= self.maxCount:
 | 
											while cache:
 | 
				
			||||||
						for (ck, cv) in cache.items():
 | 
												(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 expired:
 | 
				
			||||||
							if cv[1] <= t:
 | 
												if cv[1] <= t:
 | 
				
			||||||
								self.unset(ck)
 | 
													remlst.append(ck)
 | 
				
			||||||
							elif OrderedDict is not dict:
 | 
											for ck in remlst:
 | 
				
			||||||
								break
 | 
												self._cache.pop(ck, None)
 | 
				
			||||||
						# if still max count - remove any one:
 | 
											# if still max count - remove any one:
 | 
				
			||||||
						if len(cache) >= self.maxCount:
 | 
											while cache and len(cache) >= self.maxCount:
 | 
				
			||||||
							if OrderedDict is not dict: # first (older):
 | 
					 | 
				
			||||||
								cache.popitem(False)
 | 
					 | 
				
			||||||
							else: # pragma: 3.x no cover
 | 
					 | 
				
			||||||
							cache.popitem()
 | 
												cache.popitem()
 | 
				
			||||||
 | 
									# set now:
 | 
				
			||||||
				cache[k] = (v, t + self.maxTime)
 | 
									cache[k] = (v, t + self.maxTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		def unset(self, k):
 | 
							def unset(self, k):
 | 
				
			||||||
			try:
 | 
								with self.__lock:
 | 
				
			||||||
				del self._cache[k]
 | 
									self._cache.pop(k, None)
 | 
				
			||||||
			except KeyError:
 | 
					 | 
				
			||||||
				pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@staticmethod
 | 
						@staticmethod
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue