Fixed #20943 -- Weakly reference senders when caching their associated receivers
This commit is contained in:
parent
fdbf492946
commit
e55ca60903
|
@ -13,6 +13,6 @@ pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
|
||||||
post_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"])
|
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)
|
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
|
||||||
|
|
|
@ -4,8 +4,10 @@ import threading
|
||||||
from django.dispatch import saferef
|
from django.dispatch import saferef
|
||||||
from django.utils.six.moves import xrange
|
from django.utils.six.moves import xrange
|
||||||
|
|
||||||
|
|
||||||
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
||||||
|
|
||||||
|
|
||||||
def _make_id(target):
|
def _make_id(target):
|
||||||
if hasattr(target, '__func__'):
|
if hasattr(target, '__func__'):
|
||||||
return (id(target.__self__), id(target.__func__))
|
return (id(target.__self__), id(target.__func__))
|
||||||
|
@ -15,6 +17,7 @@ NONE_ID = _make_id(None)
|
||||||
# A marker for caching
|
# A marker for caching
|
||||||
NO_RECEIVERS = object()
|
NO_RECEIVERS = object()
|
||||||
|
|
||||||
|
|
||||||
class Signal(object):
|
class Signal(object):
|
||||||
"""
|
"""
|
||||||
Base class for all signals
|
Base class for all signals
|
||||||
|
@ -42,7 +45,7 @@ class Signal(object):
|
||||||
# distinct sender we cache the receivers that sender has in
|
# distinct sender we cache the receivers that sender has in
|
||||||
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
|
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
|
||||||
# .disconnect() is called and populated on send().
|
# .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):
|
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
|
||||||
"""
|
"""
|
||||||
|
@ -116,7 +119,7 @@ class Signal(object):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.receivers.append((lookup_key, receiver))
|
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):
|
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
|
||||||
"""
|
"""
|
||||||
|
@ -151,7 +154,7 @@ class Signal(object):
|
||||||
if r_key == lookup_key:
|
if r_key == lookup_key:
|
||||||
del self.receivers[index]
|
del self.receivers[index]
|
||||||
break
|
break
|
||||||
self.sender_receivers_cache = {}
|
self.sender_receivers_cache.clear()
|
||||||
|
|
||||||
def has_listeners(self, sender=None):
|
def has_listeners(self, sender=None):
|
||||||
return bool(self._live_receivers(sender))
|
return bool(self._live_receivers(sender))
|
||||||
|
@ -276,7 +279,8 @@ class Signal(object):
|
||||||
for idx, (r_key, _) in enumerate(reversed(self.receivers)):
|
for idx, (r_key, _) in enumerate(reversed(self.receivers)):
|
||||||
if r_key == key:
|
if r_key == key:
|
||||||
del self.receivers[last_idx - idx]
|
del self.receivers[last_idx - idx]
|
||||||
self.sender_receivers_cache = {}
|
self.sender_receivers_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
def receiver(signal, **kwargs):
|
def receiver(signal, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,6 +2,7 @@ import gc
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
import weakref
|
||||||
|
|
||||||
from django.dispatch import Signal, receiver
|
from django.dispatch import Signal, receiver
|
||||||
|
|
||||||
|
@ -35,6 +36,8 @@ class Callable(object):
|
||||||
a_signal = Signal(providing_args=["val"])
|
a_signal = Signal(providing_args=["val"])
|
||||||
b_signal = Signal(providing_args=["val"])
|
b_signal = Signal(providing_args=["val"])
|
||||||
c_signal = Signal(providing_args=["val"])
|
c_signal = Signal(providing_args=["val"])
|
||||||
|
d_signal = Signal(providing_args=["val"], use_caching=True)
|
||||||
|
|
||||||
|
|
||||||
class DispatcherTests(unittest.TestCase):
|
class DispatcherTests(unittest.TestCase):
|
||||||
"""Test suite for dispatcher (barely started)"""
|
"""Test suite for dispatcher (barely started)"""
|
||||||
|
@ -72,6 +75,24 @@ class DispatcherTests(unittest.TestCase):
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
self._testIsClean(a_signal)
|
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):
|
def testMultipleRegistration(self):
|
||||||
a = Callable()
|
a = Callable()
|
||||||
a_signal.connect(a)
|
a_signal.connect(a)
|
||||||
|
|
Loading…
Reference in New Issue