summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/ext/signal_dispatcher.py30
-rw-r--r--test/tc_signal.py54
2 files changed, 74 insertions, 10 deletions
diff --git a/ranger/ext/signal_dispatcher.py b/ranger/ext/signal_dispatcher.py
index 95bc11f3..2762493b 100644
--- a/ranger/ext/signal_dispatcher.py
+++ b/ranger/ext/signal_dispatcher.py
@@ -14,6 +14,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
+import weakref
 
 class Signal(dict):
 	stopped = False
@@ -41,13 +42,15 @@ class SignalDispatcher(object):
 
 	signal_clear = __init__
 
-	def signal_bind(self, signal_name, function, priority=0.5):
+	def signal_bind(self, signal_name, function, priority=0.5, weak=False):
 		assert isinstance(signal_name, str)
 		try:
 			handlers = self._signals[signal_name]
 		except:
 			handlers = self._signals[signal_name] = []
 		nargs = function.__code__.co_argcount - hasattr(function, 'im_func')
+		if weak:
+			function = weakref.proxy(function)
 		handler = SignalHandler(signal_name, function, priority, nargs > 0)
 		handlers.append(handler)
 		handlers.sort(key=lambda handler: -handler.priority)
@@ -56,10 +59,13 @@ class SignalDispatcher(object):
 	def signal_unbind(self, signal_handler):
 		try:
 			handlers = self._signals[signal_handler.signal_name]
-		except KeyError:
+		except:
 			pass
 		else:
-			handlers.remove(signal_handler)
+			try:
+				handlers.remove(signal_handler)
+			except:
+				pass
 
 	def signal_emit(self, signal_name, **kw):
 		assert isinstance(signal_name, str)
@@ -72,14 +78,18 @@ class SignalDispatcher(object):
 
 		signal = Signal(origin=self, name=signal_name, **kw)
 
-		for handler in handlers:  # propagate
+		# propagate
+		for handler in tuple(handlers):
 			if handler.active:
-				if handler.pass_signal:
-					handler.function(signal)
-				else:
-					handler.function()
-				if signal.stopped:
-					return
+				try:
+					if handler.pass_signal:
+						handler.function(signal)
+					else:
+						handler.function()
+					if signal.stopped:
+						return
+				except ReferenceError:
+					handlers.remove(handler)
 
 class RegexpSignalDispatcher(SignalDispatcher):
 	"""
diff --git a/test/tc_signal.py b/test/tc_signal.py
index a585a86b..639cf7c3 100644
--- a/test/tc_signal.py
+++ b/test/tc_signal.py
@@ -15,6 +15,7 @@
 
 if __name__ == '__main__': from __init__ import init; init()
 import unittest
+import gc
 from ranger.ext.signal_dispatcher import *
 
 class TestSignal(unittest.TestCase):
@@ -75,6 +76,59 @@ class TestSignal(unittest.TestCase):
 		sd.signal_emit('setnumber', number=100)
 		self.assertEqual(None, lst[-1])
 
+	def test_weak_refs(self):
+		sd = self.sd
+		is_deleted = [False]
+
+		class Foo(object):
+			def __init__(self):
+				self.alphabet = ['a']
+			def calc(self, signal):
+				self.alphabet.append(chr(ord(self.alphabet[-1]) + 1))
+			def __del__(self):
+				is_deleted[0] = True
+
+		foo = Foo()
+		alphabet = foo.alphabet
+		calc = foo.calc
+
+		del foo
+		self.assertEqual('a', ''.join(alphabet))
+		sd.signal_bind('mysignal', calc, weak=True)
+		sd.signal_emit('mysignal')
+		self.assertEqual('ab', ''.join(alphabet))
+		self.assertFalse(is_deleted[0])
+
+		del calc
+		self.assertTrue(is_deleted[0])
+
+	def test_weak_refs_dead_on_arrival(self):
+		sd = self.sd
+		is_deleted = [False]
+
+		class Foo(object):
+			def __init__(self):
+				self.alphabet = ['a']
+			def calc(self, signal):
+				self.alphabet.append(chr(ord(self.alphabet[-1]) + 1))
+			def __del__(self):
+				is_deleted[0] = True
+
+		foo = Foo()
+		alphabet = foo.alphabet
+
+		self.assertEqual('a', ''.join(alphabet))
+		sd.signal_bind('mysignal', foo.calc, weak=True)
+		del foo
+
+		sd.signal_emit('mysignal')
+		self.assertEqual('ab', ''.join(alphabet))
+		self.assertFalse(is_deleted[0])
+
+		del calc
+		self.assertTrue(is_deleted[0])
+
+
 	def test_regexp_signals(self):
 		sd = RegexpSignalDispatcher()
 		lst = []