Skip to content

Commit 5d1df96

Browse files
committed
tests: clear util.callback_mgr between test cases
util.callback_mgr.callbacks was not getting properly cleared between tests. Every time an Abstract_Wallet or an LNWorker (or many other subclasses of EventListener) is instantiated, self.register_callbacks() is called in __init__, which puts callbacks into util.callback_mgr.callbacks. These are only cleaned up if we explicitly call Abstract_Wallet.stop() or LNWorker.stop() later, which we usually do not do in the tests. As a result, when running multiple unit tests in a row, lots of objects created in a given testcase are never GC-ed and leak into subsequent tests. This is not only a memory leak, but wastes compute too: when events are triggered and cbs get called, these old objects also have their cbs called. After running all (~1061) unit tests, I observe util.callback_mgr.callbacks had 30 events with a total of 3156 callbacks stored. On my laptop, running all unit tests previously took ~115 sec, and now it takes ~73 sec.
1 parent fd0ad25 commit 5d1df96

File tree

3 files changed

+13
-16
lines changed

3 files changed

+13
-16
lines changed

electrum/util.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,20 +1953,24 @@ class CallbackManager(Logger):
19531953
def __init__(self):
19541954
Logger.__init__(self)
19551955
self.callback_lock = threading.Lock()
1956-
self.callbacks = defaultdict(list) # note: needs self.callback_lock
1956+
self.callbacks = defaultdict(list) # type: Dict[str, List[Callable]] # note: needs self.callback_lock
19571957

1958-
def register_callback(self, func, events):
1958+
def register_callback(self, func: Callable, events: Sequence[str]) -> None:
19591959
with self.callback_lock:
19601960
for event in events:
19611961
self.callbacks[event].append(func)
19621962

1963-
def unregister_callback(self, callback):
1963+
def unregister_callback(self, callback: Callable) -> None:
19641964
with self.callback_lock:
19651965
for callbacks in self.callbacks.values():
19661966
if callback in callbacks:
19671967
callbacks.remove(callback)
19681968

1969-
def trigger_callback(self, event, *args):
1969+
def clear_all_callbacks(self) -> None:
1970+
with self.callback_lock:
1971+
self.callbacks.clear()
1972+
1973+
def trigger_callback(self, event: str, *args) -> None:
19701974
"""Trigger a callback with given arguments.
19711975
Can be called from any thread. The callback itself will get scheduled
19721976
on the event loop.

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ async def asyncSetUp(self):
7575
util._asyncio_event_loop = loop
7676

7777
def tearDown(self):
78+
util.callback_mgr.clear_all_callbacks()
7879
shutil.rmtree(self.electrum_path)
7980
super().tearDown()
8081
util._asyncio_event_loop = None # cleared here, at the ~last possible moment. asyncTearDown is too early.

tests/test_lnpeer.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,12 +1117,8 @@ async def on_htlc_failed(*args):
11171117
util.register_callback(on_htlc_fulfilled, ["htlc_fulfilled"])
11181118
util.register_callback(on_htlc_failed, ["htlc_failed"])
11191119

1120-
try:
1121-
with self.assertRaises(SuccessfulTest):
1122-
await f()
1123-
finally:
1124-
util.unregister_callback(on_htlc_fulfilled)
1125-
util.unregister_callback(on_htlc_failed)
1120+
with self.assertRaises(SuccessfulTest):
1121+
await f()
11261122

11271123
async def test_payment_recv_mpp_confusion2(self):
11281124
"""Regression test for https://github.com/spesmilo/electrum/security/advisories/GHSA-8r85-vp7r-hjxf"""
@@ -1191,12 +1187,8 @@ async def on_htlc_failed(*args):
11911187
util.register_callback(on_htlc_fulfilled, ["htlc_fulfilled"])
11921188
util.register_callback(on_htlc_failed, ["htlc_failed"])
11931189

1194-
try:
1195-
with self.assertRaises(SuccessfulTest):
1196-
await f()
1197-
finally:
1198-
util.unregister_callback(on_htlc_fulfilled)
1199-
util.unregister_callback(on_htlc_failed)
1190+
with self.assertRaises(SuccessfulTest):
1191+
await f()
12001192

12011193
async def test_legacy_shutdown_low(self):
12021194
await self._test_shutdown(alice_fee=100, bob_fee=150)

0 commit comments

Comments
 (0)