diff options
author | jason kirtland <jek@discorporate.us> | 2015-07-23 12:19:14 +0200 |
---|---|---|
committer | jason kirtland <jek@discorporate.us> | 2015-07-23 12:19:14 +0200 |
commit | bee3b6ebded6daf436b4c1d7f688142d687ae405 (patch) | |
tree | 69523f6ccfe06e8258035a8fdbac9664f7db51c1 | |
parent | efe8d646c932ff091106f0c2b6d5fd79a03efdb4 (diff) | |
download | blinker-bee3b6ebded6daf436b4c1d7f688142d687ae405.tar.gz |
Added Signal._cleanup_bookeeping() to prune stale bookkeeping on demand.
-rw-r--r-- | CHANGES | 2 | ||||
-rw-r--r-- | blinker/base.py | 26 | ||||
-rw-r--r-- | tests/test_signals.py | 4 |
3 files changed, 32 insertions, 0 deletions
@@ -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): |