Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #20943 -- Weakly reference senders when caching their associate…

…d receivers
  • Loading branch information...
commit e55ca60903adcfd525938335b1ad9dbb6fd96c3e 1 parent fdbf492
@charettes charettes authored
View
2  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)
View
12 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):
"""
View
21 tests/dispatch/tests/test_dispatcher.py
@@ -2,6 +2,7 @@
import sys
import time
import unittest
+import weakref
from django.dispatch import Signal, receiver
@@ -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)

2 comments on commit e55ca60

@elena

Hi there,

This patch seems to have caused this issue: etianen/django-reversion#255

Not sure if this expected behavior. Thanks!

@charettes
Collaborator

Commented on the issue.

This is related to the use of ._live_receivers by reversion which is an internal API that changed it's signature when caching was introduced 704ee33.

Please sign in to comment.
Something went wrong with that request. Please try again.