Kaydet (Commit) 56ab5f52 authored tarafından Florian Apolloner's avatar Florian Apolloner

Merge pull request #2163 from apollo13/signals

Signals
...@@ -6,6 +6,7 @@ include MANIFEST.in ...@@ -6,6 +6,7 @@ include MANIFEST.in
include django/contrib/gis/gdal/LICENSE include django/contrib/gis/gdal/LICENSE
include django/contrib/gis/geos/LICENSE include django/contrib/gis/geos/LICENSE
include django/dispatch/license.txt include django/dispatch/license.txt
include django/dispatch/license.python.txt
recursive-include docs * recursive-include docs *
recursive-include scripts * recursive-include scripts *
recursive-include extras * recursive-include extras *
......
import weakref import sys
import threading import threading
import weakref
from django.dispatch import saferef
from django.utils.six.moves import xrange from django.utils.six.moves import xrange
if sys.version_info < (3,4):
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) from .weakref_backports import WeakMethod
else:
from weakref import WeakMethod
def _make_id(target): def _make_id(target):
...@@ -57,9 +59,7 @@ class Signal(object): ...@@ -57,9 +59,7 @@ class Signal(object):
A function or an instance method which is to receive signals. A function or an instance method which is to receive signals.
Receivers must be hashable objects. Receivers must be hashable objects.
If weak is True, then receiver must be weak-referencable (more If weak is True, then receiver must be weak-referencable.
precisely saferef.safeRef() must be able to create a reference
to the receiver).
Receivers must be able to accept keyword arguments. Receivers must be able to accept keyword arguments.
...@@ -105,20 +105,33 @@ class Signal(object): ...@@ -105,20 +105,33 @@ class Signal(object):
assert argspec[2] is not None, \ assert argspec[2] is not None, \
"Signal receivers must accept keyword arguments (**kwargs)." "Signal receivers must accept keyword arguments (**kwargs)."
receiver_id = _make_id(receiver)
if dispatch_uid: if dispatch_uid:
lookup_key = (dispatch_uid, _make_id(sender)) lookup_key = (dispatch_uid, _make_id(sender))
else: else:
lookup_key = (_make_id(receiver), _make_id(sender)) lookup_key = (receiver_id, _make_id(sender))
if weak: if weak:
receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) ref = weakref.ref
original_receiver = receiver
# Check for bound methods
if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
ref = WeakMethod
original_receiver = original_receiver.__self__
if sys.version_info >= (3, 4):
receiver = ref(receiver)
weakref.finalize(original_receiver, self._remove_receiver, receiver_id=receiver_id)
else:
receiver = ref(receiver, self._remove_receiver)
# Use the id of the weakref, since that's what passed to the weakref callback!
receiver_id = _make_id(receiver)
with self.lock: with self.lock:
for r_key, _ in self.receivers: for r_key, _, _ in self.receivers:
if r_key == lookup_key: if r_key == lookup_key:
break break
else: else:
self.receivers.append((lookup_key, receiver)) self.receivers.append((lookup_key, receiver, receiver_id))
self.sender_receivers_cache.clear() 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):
...@@ -150,7 +163,7 @@ class Signal(object): ...@@ -150,7 +163,7 @@ class Signal(object):
with self.lock: with self.lock:
for index in xrange(len(self.receivers)): for index in xrange(len(self.receivers)):
(r_key, _) = self.receivers[index] (r_key, _, _) = self.receivers[index]
if r_key == lookup_key: if r_key == lookup_key:
del self.receivers[index] del self.receivers[index]
break break
...@@ -242,7 +255,7 @@ class Signal(object): ...@@ -242,7 +255,7 @@ class Signal(object):
with self.lock: with self.lock:
senderkey = _make_id(sender) senderkey = _make_id(sender)
receivers = [] receivers = []
for (receiverkey, r_senderkey), receiver in self.receivers: for (receiverkey, r_senderkey), receiver, _ in self.receivers:
if r_senderkey == NONE_ID or r_senderkey == senderkey: if r_senderkey == NONE_ID or r_senderkey == senderkey:
receivers.append(receiver) receivers.append(receiver)
if self.use_caching: if self.use_caching:
...@@ -253,7 +266,7 @@ class Signal(object): ...@@ -253,7 +266,7 @@ class Signal(object):
self.sender_receivers_cache[sender] = receivers self.sender_receivers_cache[sender] = receivers
non_weak_receivers = [] non_weak_receivers = []
for receiver in receivers: for receiver in receivers:
if isinstance(receiver, WEAKREF_TYPES): if isinstance(receiver, weakref.ReferenceType):
# Dereference the weak reference. # Dereference the weak reference.
receiver = receiver() receiver = receiver()
if receiver is not None: if receiver is not None:
...@@ -262,23 +275,19 @@ class Signal(object): ...@@ -262,23 +275,19 @@ class Signal(object):
non_weak_receivers.append(receiver) non_weak_receivers.append(receiver)
return non_weak_receivers return non_weak_receivers
def _remove_receiver(self, receiver): def _remove_receiver(self, receiver=None, receiver_id=None, _make_id=_make_id):
""" """
Remove dead receivers from connections. Remove dead receivers from connections.
"""
`receiver_id` is used by python 3.4 and up. `receiver` is used in older
versions and is the weakref to the receiver (if the connection was defined
as `weak`). We also need to pass on `_make_id` since the original reference
will be None during module shutdown.
"""
with self.lock: with self.lock:
to_remove = [] if receiver is not None:
for key, connected_receiver in self.receivers: receiver_id = _make_id(receiver)
if connected_receiver == receiver: self.receivers[:] = [val for val in self.receivers if val[2] != receiver_id]
to_remove.append(key)
for key in to_remove:
last_idx = len(self.receivers) - 1
# enumerate in reverse order so that indexes are valid even
# after we delete some items
for idx, (r_key, _) in enumerate(reversed(self.receivers)):
if r_key == key:
del self.receivers[last_idx - idx]
self.sender_receivers_cache.clear() self.sender_receivers_cache.clear()
......
This diff is collapsed.
This diff is collapsed.
"""
weakref_backports is a partial backport of the weakref module for python
versions below 3.4.
Copyright (C) 2013 Python Software Foundation, see license.python.txt for
details.
The following changes were made to the original sources during backporting:
* Added `self` to `super` calls.
* Removed `from None` when raising exceptions.
"""
from weakref import ref
class WeakMethod(ref):
"""
A custom `weakref.ref` subclass which simulates a weak reference to
a bound method, working around the lifetime problem of bound methods.
"""
__slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__"
def __new__(cls, meth, callback=None):
try:
obj = meth.__self__
func = meth.__func__
except AttributeError:
raise TypeError("argument should be a bound method, not {}"
.format(type(meth)))
def _cb(arg):
# The self-weakref trick is needed to avoid creating a reference
# cycle.
self = self_wr()
if self._alive:
self._alive = False
if callback is not None:
callback(self)
self = ref.__new__(cls, obj, _cb)
self._func_ref = ref(func, _cb)
self._meth_type = type(meth)
self._alive = True
self_wr = ref(self)
return self
def __call__(self):
obj = super(WeakMethod, self).__call__()
func = self._func_ref()
if obj is None or func is None:
return None
return self._meth_type(func, obj)
def __eq__(self, other):
if isinstance(other, WeakMethod):
if not self._alive or not other._alive:
return self is other
return ref.__eq__(self, other) and self._func_ref == other._func_ref
return False
def __ne__(self, other):
if isinstance(other, WeakMethod):
if not self._alive or not other._alive:
return self is not other
return ref.__ne__(self, other) or self._func_ref != other._func_ref
return True
__hash__ = ref.__hash__
import unittest
from django.dispatch.saferef import safeRef
from django.utils.six.moves import xrange
class Test1(object):
def x(self):
pass
def test2(obj):
pass
class Test2(object):
def __call__(self, obj):
pass
class SaferefTests(unittest.TestCase):
def setUp(self):
ts = []
ss = []
for x in xrange(5000):
t = Test1()
ts.append(t)
s = safeRef(t.x, self._closure)
ss.append(s)
ts.append(test2)
ss.append(safeRef(test2, self._closure))
for x in xrange(30):
t = Test2()
ts.append(t)
s = safeRef(t, self._closure)
ss.append(s)
self.ts = ts
self.ss = ss
self.closureCount = 0
def tearDown(self):
del self.ts
del self.ss
def testIn(self):
"""Test the "in" operator for safe references (cmp)"""
for t in self.ts[:50]:
self.assertTrue(safeRef(t.x) in self.ss)
def testValid(self):
"""Test that the references are valid (return instance methods)"""
for s in self.ss:
self.assertTrue(s())
def testShortCircuit(self):
"""Test that creation short-circuits to reuse existing references"""
sd = {}
for s in self.ss:
sd[s] = 1
for t in self.ts:
if hasattr(t, 'x'):
self.assertTrue(safeRef(t.x) in sd)
else:
self.assertTrue(safeRef(t) in sd)
def testRepresentation(self):
"""Test that the reference object's representation works
XXX Doesn't currently check the results, just that no error
is raised
"""
repr(self.ss[-1])
def _closure(self, ref):
"""Dumb utility mechanism to increment deletion counter"""
self.closureCount += 1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment