summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjason kirtland <jek@discorporate.us>2015-07-23 12:19:14 +0200
committerjason kirtland <jek@discorporate.us>2015-07-23 12:19:14 +0200
commitbee3b6ebded6daf436b4c1d7f688142d687ae405 (patch)
tree69523f6ccfe06e8258035a8fdbac9664f7db51c1
parentefe8d646c932ff091106f0c2b6d5fd79a03efdb4 (diff)
downloadblinker-bee3b6ebded6daf436b4c1d7f688142d687ae405.tar.gz
Added Signal._cleanup_bookeeping() to prune stale bookkeeping on demand.
-rw-r--r--CHANGES2
-rw-r--r--blinker/base.py26
-rw-r--r--tests/test_signals.py4
3 files changed, 32 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
index b57f5a3..cdb578e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,8 @@ Version 1.4dev
- Additional bookkeeping cleanup for non-ANY connections at disconnect
time.
+- Added Signal._cleanup_bookeeping() to prune stale bookkeeping on
+ demand
Version 1.3
-----------
diff --git a/blinker/base.py b/blinker/base.py
index b6b890b..cc5880e 100644
--- a/blinker/base.py
+++ b/blinker/base.py
@@ -348,6 +348,32 @@ class Signal(object):
for receiver_id in self._by_sender.pop(sender_id, ()):
self._by_receiver[receiver_id].discard(sender_id)
+ def _cleanup_bookkeeping(self):
+ """Prune unused sender/receiver bookeeping. Not threadsafe.
+
+ Connecting & disconnecting leave behind a small amount of bookeeping
+ for the receiver and sender values. Typical workloads using Blinker,
+ for example in most web apps, Flask, CLI scripts, etc., are not
+ adversely affected by this bookkeeping.
+
+ With a long-running Python process performing dynamic signal routing
+ with high volume- e.g. connecting to function closures, "senders" are
+ all unique object instances, and doing all of this over and over- you
+ may see memory usage will grow due to extraneous bookeeping. (An empty
+ set() for each stale sender/receiver pair.)
+
+ This method will prune that bookeeping away, with the caveat that such
+ pruning is not threadsafe. The risk is that cleanup of a fully
+ disconnected receiver/sender pair occurs while another thread is
+ connecting that same pair. If you are in the highly dynamic, unique
+ receiver/sender situation that has lead you to this method, that
+ failure mode is perhaps not a big deal for you.
+ """
+ for mapping in (self._by_sender, self._by_receiver):
+ for _id, bucket in list(mapping.items()):
+ if not bucket:
+ mapping.pop(_id, None)
+
def _clear_state(self):
"""Throw away all signal state. Useful for unit tests."""
self._weak_senders.clear()
diff --git a/tests/test_signals.py b/tests/test_signals.py
index c6076f9..a1172ed 100644
--- a/tests/test_signals.py
+++ b/tests/test_signals.py
@@ -211,6 +211,10 @@ def test_empty_bucket_growth():
assert senders() == (1, 0)
assert receivers() == (2, 0)
+ sig._cleanup_bookkeeping()
+ assert senders() == (0, 0)
+ assert receivers() == (0, 0)
+
def test_meta_connect_failure():
def meta_received(sender, **kw):