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"])
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
post_init = Signal(providing_args=["instance"])
pre_init = Signal(providing_args=["instance", "args", "kwargs"], use_caching=True)
post_init = Signal(providing_args=["instance"], use_caching=True)
pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"])
post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"])
pre_save = Signal(providing_args=["instance", "raw", "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"])
post_delete = Signal(providing_args=["instance", "using"])
pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
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__'):
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 = 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_senderkey or r_senderkey == senderkey:
if r_senderkey == NONE_ID or r_senderkey == senderkey:
receivers.append(receiver)
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:
receivers.append(receiver)
non_weak_receivers.append(receiver)
else:
receivers.append(receiver)
return receivers
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):
"""