From f0bc2865ff9d85c952fa86ae19aee062a5e883cd Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 19 Aug 2013 23:14:21 -0400 Subject: [PATCH] Fixed #20943 -- Weakly reference senders when caching their associated receivers Backport of e55ca60903 from master. --- django/db/models/signals.py | 2 +- django/dispatch/dispatcher.py | 12 ++++++++---- tests/dispatch/tests/test_dispatcher.py | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 3e321893c1eef..07824421d8822 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -13,6 +13,6 @@ 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 65c5c408ff2c4..a8cdc93b215da 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -4,8 +4,10 @@ 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 @@ def _make_id(target): # A marker for caching NO_RECEIVERS = object() + class Signal(object): """ Base class for all signals @@ -42,7 +45,7 @@ def __init__(self, providing_args=None, use_caching=False): # 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 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): 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 @@ def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): 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 @@ def _remove_receiver(self, receiver): 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 a1d4c7e176922..2586aeee62c09 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 @@ def a(self, val, **kwargs): 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 @@ def testGarbageCollected(self): 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)