Skip to content

Commit

Permalink
Fixed #20943 -- Weakly reference senders when caching their associate…
Browse files Browse the repository at this point in the history
…d receivers

Backport of e55ca60 from master.
  • Loading branch information
charettes committed Aug 20, 2013
1 parent e7a6eaf commit f0bc286
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 5 deletions.
2 changes: 1 addition & 1 deletion django/db/models/signals.py
Expand Up @@ -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)
12 changes: 8 additions & 4 deletions django/dispatch/dispatcher.py
Expand Up @@ -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__))
Expand All @@ -15,6 +17,7 @@ def _make_id(target):
# A marker for caching
NO_RECEIVERS = object()


class Signal(object):
"""
Base class for all signals
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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):
"""
Expand Down
21 changes: 21 additions & 0 deletions 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
Expand Down Expand Up @@ -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)"""
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f0bc286

Please sign in to comment.