summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2013-08-19 23:14:21 -0400
committerSimon Charette <charette.s@gmail.com>2013-08-20 02:12:50 -0400
commitf0bc2865ff9d85c952fa86ae19aee062a5e883cd (patch)
treec29b25ca440dc1b984d1e8508c938cc8d75ca96b
parente7a6eaf5fee9c5854e0d2d86f21132ac4a1415f6 (diff)
downloaddjango-f0bc2865ff9d85c952fa86ae19aee062a5e883cd.tar.gz
Fixed #20943 -- Weakly reference senders when caching their associated receivers
Backport of e55ca60903 from master.
-rw-r--r--django/db/models/signals.py2
-rw-r--r--django/dispatch/dispatcher.py12
-rw-r--r--tests/dispatch/tests/test_dispatcher.py21
3 files changed, 30 insertions, 5 deletions
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
index 3e321893c1..07824421d8 100644
--- a/django/db/models/signals.py
+++ b/django/db/models/signals.py
@@ -13,6 +13,6 @@ pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
pre_syncdb = Signal(providing_args=["app", "create_models", "verbosity", "interactive", "db"])
-post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"], use_caching=True)
+post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"])
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index 65c5c408ff..a8cdc93b21 100644
--- a/django/dispatch/dispatcher.py
+++ b/django/dispatch/dispatcher.py
@@ -4,8 +4,10 @@ import threading
from django.dispatch import saferef
from django.utils.six.moves import xrange
+
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
def _make_id(target):
if hasattr(target, '__func__'):
return (id(target.__self__), id(target.__func__))
@@ -15,6 +17,7 @@ NONE_ID = _make_id(None)
# A marker for caching
NO_RECEIVERS = object()
+
class Signal(object):
"""
Base class for all signals
@@ -42,7 +45,7 @@ class Signal(object):
# 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 = {}
+ self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
"""
@@ -116,7 +119,7 @@ class Signal(object):
break
else:
self.receivers.append((lookup_key, receiver))
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
"""
@@ -151,7 +154,7 @@ class Signal(object):
if r_key == lookup_key:
del self.receivers[index]
break
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
def has_listeners(self, sender=None):
return bool(self._live_receivers(sender))
@@ -276,7 +279,8 @@ class Signal(object):
for idx, (r_key, _) in enumerate(reversed(self.receivers)):
if r_key == key:
del self.receivers[last_idx - idx]
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
+
def receiver(signal, **kwargs):
"""
diff --git a/tests/dispatch/tests/test_dispatcher.py b/tests/dispatch/tests/test_dispatcher.py
index a1d4c7e176..2586aeee62 100644
--- a/tests/dispatch/tests/test_dispatcher.py
+++ b/tests/dispatch/tests/test_dispatcher.py
@@ -1,6 +1,7 @@
import gc
import sys
import time
+import weakref
from django.dispatch import Signal, receiver
from django.utils import unittest
@@ -35,6 +36,8 @@ class Callable(object):
a_signal = Signal(providing_args=["val"])
b_signal = Signal(providing_args=["val"])
c_signal = Signal(providing_args=["val"])
+d_signal = Signal(providing_args=["val"], use_caching=True)
+
class DispatcherTests(unittest.TestCase):
"""Test suite for dispatcher (barely started)"""
@@ -72,6 +75,24 @@ class DispatcherTests(unittest.TestCase):
self.assertEqual(result, expected)
self._testIsClean(a_signal)
+ def testCachedGarbagedCollected(self):
+ """
+ Make sure signal caching sender receivers don't prevent garbage
+ collection of senders.
+ """
+ class sender:
+ pass
+ wref = weakref.ref(sender)
+ d_signal.connect(receiver_1_arg)
+ d_signal.send(sender, val='garbage')
+ del sender
+ garbage_collect()
+ try:
+ self.assertIsNone(wref())
+ finally:
+ # Disconnect after reference check since it flushes the tested cache.
+ d_signal.disconnect(receiver_1_arg)
+
def testMultipleRegistration(self):
a = Callable()
a_signal.connect(a)