mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #16679 -- Use caching to speed up signal sending
This commit is contained in:
		| @@ -10,6 +10,10 @@ def _make_id(target): | ||||
|     if hasattr(target, '__func__'): | ||||
|         return (id(target.__self__), id(target.__func__)) | ||||
|     return id(target) | ||||
| NONE_ID = _make_id(None) | ||||
|  | ||||
| # A marker for caching | ||||
| NO_RECEIVERS = object() | ||||
|  | ||||
| class Signal(object): | ||||
|     """ | ||||
| @@ -20,8 +24,7 @@ class Signal(object): | ||||
|         receivers | ||||
|             { receriverkey (id) : weakref(receiver) } | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, providing_args=None): | ||||
|     def __init__(self, providing_args=None, use_caching=False): | ||||
|         """ | ||||
|         Create a new signal. | ||||
|  | ||||
| @@ -33,6 +36,13 @@ class Signal(object): | ||||
|             providing_args = [] | ||||
|         self.providing_args = set(providing_args) | ||||
|         self.lock = threading.Lock() | ||||
|         self.use_caching = use_caching | ||||
|         # For convenience we create empty caches even if they are not used. | ||||
|         # A note about caching: if use_caching is defined, then for each | ||||
|         # distinct sender we cache the receivers that sender has in | ||||
|         # 'sender_receivers_cache'. The cache is cleaned when .connect() or | ||||
|         # .disconnect() is called and populated on send(). | ||||
|         self.sender_receivers_cache = {} | ||||
|  | ||||
|     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): | ||||
|         """ | ||||
| @@ -106,6 +116,7 @@ class Signal(object): | ||||
|                     break | ||||
|             else: | ||||
|                 self.receivers.append((lookup_key, receiver)) | ||||
|             self.sender_receivers_cache = {} | ||||
|  | ||||
|     def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): | ||||
|         """ | ||||
| @@ -140,9 +151,10 @@ class Signal(object): | ||||
|                 if r_key == lookup_key: | ||||
|                     del self.receivers[index] | ||||
|                     break | ||||
|             self.sender_receivers_cache = {} | ||||
|  | ||||
|     def has_listeners(self, sender=None): | ||||
|         return bool(self._live_receivers(_make_id(sender))) | ||||
|         return bool(self._live_receivers(sender)) | ||||
|  | ||||
|     def send(self, sender, **named): | ||||
|         """ | ||||
| @@ -163,10 +175,10 @@ class Signal(object): | ||||
|         Returns a list of tuple pairs [(receiver, response), ... ]. | ||||
|         """ | ||||
|         responses = [] | ||||
|         if not self.receivers: | ||||
|         if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: | ||||
|             return responses | ||||
|  | ||||
|         for receiver in self._live_receivers(_make_id(sender)): | ||||
|         for receiver in self._live_receivers(sender): | ||||
|             response = receiver(signal=self, sender=sender, **named) | ||||
|             responses.append((receiver, response)) | ||||
|         return responses | ||||
| @@ -195,12 +207,12 @@ class Signal(object): | ||||
|         receiver. | ||||
|         """ | ||||
|         responses = [] | ||||
|         if not self.receivers: | ||||
|         if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: | ||||
|             return responses | ||||
|  | ||||
|         # Call each receiver with whatever arguments it can accept. | ||||
|         # Return a list of tuple pairs [(receiver, response), ... ]. | ||||
|         for receiver in self._live_receivers(_make_id(sender)): | ||||
|         for receiver in self._live_receivers(sender): | ||||
|             try: | ||||
|                 response = receiver(signal=self, sender=sender, **named) | ||||
|             except Exception as err: | ||||
| @@ -209,26 +221,43 @@ class Signal(object): | ||||
|                 responses.append((receiver, response)) | ||||
|         return responses | ||||
|  | ||||
|     def _live_receivers(self, senderkey): | ||||
|     def _live_receivers(self, sender): | ||||
|         """ | ||||
|         Filter sequence of receivers to get resolved, live receivers. | ||||
|  | ||||
|         This checks for weak references and resolves them, then returning only | ||||
|         live receivers. | ||||
|         """ | ||||
|         none_senderkey = _make_id(None) | ||||
|         receivers = [] | ||||
|  | ||||
|         for (receiverkey, r_senderkey), receiver in self.receivers: | ||||
|             if r_senderkey == none_senderkey or r_senderkey == senderkey: | ||||
|                 if isinstance(receiver, WEAKREF_TYPES): | ||||
|                     # Dereference the weak reference. | ||||
|                     receiver = receiver() | ||||
|                     if receiver is not None: | ||||
|         receivers = None | ||||
|         if self.use_caching: | ||||
|             receivers = self.sender_receivers_cache.get(sender) | ||||
|             # We could end up here with NO_RECEIVERS even if we do check this case in | ||||
|             # .send() prior to calling _live_receivers() due to concurrent .send() call. | ||||
|             if receivers is NO_RECEIVERS: | ||||
|                 return [] | ||||
|         if receivers is None: | ||||
|             with self.lock: | ||||
|                 senderkey = _make_id(sender) | ||||
|                 receivers = [] | ||||
|                 for (receiverkey, r_senderkey), receiver in self.receivers: | ||||
|                     if r_senderkey == NONE_ID or r_senderkey == senderkey: | ||||
|                         receivers.append(receiver) | ||||
|                 else: | ||||
|                     receivers.append(receiver) | ||||
|         return receivers | ||||
|                 if self.use_caching: | ||||
|                     if not receivers: | ||||
|                         self.sender_receivers_cache[sender] = NO_RECEIVERS | ||||
|                     else: | ||||
|                         # Note, we must cache the weakref versions. | ||||
|                         self.sender_receivers_cache[sender] = receivers | ||||
|         non_weak_receivers = [] | ||||
|         for receiver in receivers: | ||||
|             if isinstance(receiver, WEAKREF_TYPES): | ||||
|                 # Dereference the weak reference. | ||||
|                 receiver = receiver() | ||||
|                 if receiver is not None: | ||||
|                     non_weak_receivers.append(receiver) | ||||
|             else: | ||||
|                 non_weak_receivers.append(receiver) | ||||
|         return non_weak_receivers | ||||
|  | ||||
|     def _remove_receiver(self, receiver): | ||||
|         """ | ||||
| @@ -246,8 +275,8 @@ class Signal(object): | ||||
|                 # after we delete some items | ||||
|                 for idx, (r_key, _) in enumerate(reversed(self.receivers)): | ||||
|                     if r_key == key: | ||||
|                         del self.receivers[last_idx-idx] | ||||
|  | ||||
|                         del self.receivers[last_idx - idx] | ||||
|             self.sender_receivers_cache = {} | ||||
|  | ||||
| def receiver(signal, **kwargs): | ||||
|     """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user