mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #16679 -- Use caching to speed up signal sending
This commit is contained in:
		| @@ -2,15 +2,16 @@ from django.dispatch import Signal | |||||||
|  |  | ||||||
| class_prepared = Signal(providing_args=["class"]) | class_prepared = Signal(providing_args=["class"]) | ||||||
|  |  | ||||||
| pre_init = Signal(providing_args=["instance", "args", "kwargs"]) | pre_init = Signal(providing_args=["instance", "args", "kwargs"], use_caching=True) | ||||||
| post_init = Signal(providing_args=["instance"]) | post_init = Signal(providing_args=["instance"], use_caching=True) | ||||||
|  |  | ||||||
| pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"]) | pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"], | ||||||
| post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"]) |                  use_caching=True) | ||||||
|  | post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"], use_caching=True) | ||||||
|  |  | ||||||
| pre_delete = Signal(providing_args=["instance", "using"]) | pre_delete = Signal(providing_args=["instance", "using"], use_caching=True) | ||||||
| post_delete = Signal(providing_args=["instance", "using"]) | post_delete = Signal(providing_args=["instance", "using"], use_caching=True) | ||||||
|  |  | ||||||
| post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"]) | post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"], use_caching=True) | ||||||
|  |  | ||||||
| m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"]) | m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True) | ||||||
|   | |||||||
| @@ -10,6 +10,10 @@ def _make_id(target): | |||||||
|     if hasattr(target, '__func__'): |     if hasattr(target, '__func__'): | ||||||
|         return (id(target.__self__), id(target.__func__)) |         return (id(target.__self__), id(target.__func__)) | ||||||
|     return id(target) |     return id(target) | ||||||
|  | NONE_ID = _make_id(None) | ||||||
|  |  | ||||||
|  | # A marker for caching | ||||||
|  | NO_RECEIVERS = object() | ||||||
|  |  | ||||||
| class Signal(object): | class Signal(object): | ||||||
|     """ |     """ | ||||||
| @@ -20,8 +24,7 @@ class Signal(object): | |||||||
|         receivers |         receivers | ||||||
|             { receriverkey (id) : weakref(receiver) } |             { receriverkey (id) : weakref(receiver) } | ||||||
|     """ |     """ | ||||||
|  |     def __init__(self, providing_args=None, use_caching=False): | ||||||
|     def __init__(self, providing_args=None): |  | ||||||
|         """ |         """ | ||||||
|         Create a new signal. |         Create a new signal. | ||||||
|  |  | ||||||
| @@ -33,6 +36,13 @@ class Signal(object): | |||||||
|             providing_args = [] |             providing_args = [] | ||||||
|         self.providing_args = set(providing_args) |         self.providing_args = set(providing_args) | ||||||
|         self.lock = threading.Lock() |         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): |     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): | ||||||
|         """ |         """ | ||||||
| @@ -106,6 +116,7 @@ class Signal(object): | |||||||
|                     break |                     break | ||||||
|             else: |             else: | ||||||
|                 self.receivers.append((lookup_key, receiver)) |                 self.receivers.append((lookup_key, receiver)) | ||||||
|  |             self.sender_receivers_cache = {} | ||||||
|  |  | ||||||
|     def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): |     def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): | ||||||
|         """ |         """ | ||||||
| @@ -140,9 +151,10 @@ class Signal(object): | |||||||
|                 if r_key == lookup_key: |                 if r_key == lookup_key: | ||||||
|                     del self.receivers[index] |                     del self.receivers[index] | ||||||
|                     break |                     break | ||||||
|  |             self.sender_receivers_cache = {} | ||||||
|  |  | ||||||
|     def has_listeners(self, sender=None): |     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): |     def send(self, sender, **named): | ||||||
|         """ |         """ | ||||||
| @@ -163,10 +175,10 @@ class Signal(object): | |||||||
|         Returns a list of tuple pairs [(receiver, response), ... ]. |         Returns a list of tuple pairs [(receiver, response), ... ]. | ||||||
|         """ |         """ | ||||||
|         responses = [] |         responses = [] | ||||||
|         if not self.receivers: |         if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: | ||||||
|             return responses |             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) |             response = receiver(signal=self, sender=sender, **named) | ||||||
|             responses.append((receiver, response)) |             responses.append((receiver, response)) | ||||||
|         return responses |         return responses | ||||||
| @@ -195,12 +207,12 @@ class Signal(object): | |||||||
|         receiver. |         receiver. | ||||||
|         """ |         """ | ||||||
|         responses = [] |         responses = [] | ||||||
|         if not self.receivers: |         if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: | ||||||
|             return responses |             return responses | ||||||
|  |  | ||||||
|         # Call each receiver with whatever arguments it can accept. |         # Call each receiver with whatever arguments it can accept. | ||||||
|         # Return a list of tuple pairs [(receiver, response), ... ]. |         # 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: |             try: | ||||||
|                 response = receiver(signal=self, sender=sender, **named) |                 response = receiver(signal=self, sender=sender, **named) | ||||||
|             except Exception as err: |             except Exception as err: | ||||||
| @@ -209,26 +221,43 @@ class Signal(object): | |||||||
|                 responses.append((receiver, response)) |                 responses.append((receiver, response)) | ||||||
|         return responses |         return responses | ||||||
|  |  | ||||||
|     def _live_receivers(self, senderkey): |     def _live_receivers(self, sender): | ||||||
|         """ |         """ | ||||||
|         Filter sequence of receivers to get resolved, live receivers. |         Filter sequence of receivers to get resolved, live receivers. | ||||||
|  |  | ||||||
|         This checks for weak references and resolves them, then returning only |         This checks for weak references and resolves them, then returning only | ||||||
|         live receivers. |         live receivers. | ||||||
|         """ |         """ | ||||||
|         none_senderkey = _make_id(None) |         receivers = None | ||||||
|         receivers = [] |         if self.use_caching: | ||||||
|  |             receivers = self.sender_receivers_cache.get(sender) | ||||||
|         for (receiverkey, r_senderkey), receiver in self.receivers: |             # We could end up here with NO_RECEIVERS even if we do check this case in | ||||||
|             if r_senderkey == none_senderkey or r_senderkey == senderkey: |             # .send() prior to calling _live_receivers() due to concurrent .send() call. | ||||||
|                 if isinstance(receiver, WEAKREF_TYPES): |             if receivers is NO_RECEIVERS: | ||||||
|                     # Dereference the weak reference. |                 return [] | ||||||
|                     receiver = receiver() |         if receivers is None: | ||||||
|                     if receiver is not 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) |                         receivers.append(receiver) | ||||||
|                 else: |                 if self.use_caching: | ||||||
|                     receivers.append(receiver) |                     if not receivers: | ||||||
|         return 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): |     def _remove_receiver(self, receiver): | ||||||
|         """ |         """ | ||||||
| @@ -246,8 +275,8 @@ class Signal(object): | |||||||
|                 # after we delete some items |                 # after we delete some items | ||||||
|                 for idx, (r_key, _) in enumerate(reversed(self.receivers)): |                 for idx, (r_key, _) in enumerate(reversed(self.receivers)): | ||||||
|                     if r_key == key: |                     if r_key == key: | ||||||
|                         del self.receivers[last_idx-idx] |                         del self.receivers[last_idx - idx] | ||||||
|  |             self.sender_receivers_cache = {} | ||||||
|  |  | ||||||
| def receiver(signal, **kwargs): | def receiver(signal, **kwargs): | ||||||
|     """ |     """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user