0
0
mirror of https://github.com/django/django.git synced 2024-12-01 15:42:04 +01:00

Fixed #16679 -- Use caching to speed up signal sending

This commit is contained in:
Anssi Kääriäinen 2012-12-16 03:53:25 +02:00
parent 507c081484
commit 704ee33f50
2 changed files with 60 additions and 30 deletions

View File

@ -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)

View File

@ -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):
""" """