Files

142 lines
5.1 KiB
Python
Raw Permalink Normal View History

import asyncio
import weakref
from electrum import util
from electrum.util import EventListener, event_listener, trigger_callback
from electrum.utils.memory_leak import count_objects_in_memory, wait_until_obj_is_garbage_collected
from electrum.simple_config import SimpleConfig
from . import ElectrumTestCase, restore_wallet_from_text__for_unittest
class MyEventListener(EventListener):
def __init__(self, *, autostart: bool = False):
self._satoshi_cnt = 0
self._hal_cnt = 0
if autostart:
self.start()
def start(self):
self.register_callbacks()
def stop(self):
self.unregister_callbacks()
@event_listener
async def on_event_satoshi_moves_his_coins(self, arg1, arg2):
self._satoshi_cnt += 1
@event_listener
def on_event_hal_moves_his_coins(self, arg1, arg2): # non-async
self._hal_cnt += 1
_count_all_callbacks = util.callback_mgr.count_all_callbacks
async def fast_sleep():
# sleep a few event loop iterations
for i in range(5):
await asyncio.sleep(0)
class TestCallbackMgr(ElectrumTestCase):
def test_multiple_calls_to_register_callbacks(self):
self.assertEqual(0, _count_all_callbacks())
el1 = MyEventListener()
el2 = MyEventListener()
self.assertEqual(0, _count_all_callbacks())
el1.start()
self.assertEqual(2, _count_all_callbacks())
el2.start()
self.assertEqual(4, _count_all_callbacks())
el1.start()
self.assertEqual(4, _count_all_callbacks())
el1.stop()
self.assertEqual(2, _count_all_callbacks())
el1.stop()
self.assertEqual(2, _count_all_callbacks())
el1.stop()
self.assertEqual(2, _count_all_callbacks())
el2.stop()
self.assertEqual(0, _count_all_callbacks())
async def test_trigger_callback(self):
el1 = MyEventListener()
el1.start()
el2 = MyEventListener()
el2.start()
# trigger some cbs
self.assertEqual(el1._satoshi_cnt, 0)
self.assertEqual(el1._hal_cnt, 0)
trigger_callback('satoshi_moves_his_coins', 0, 0)
trigger_callback('satoshi_moves_his_coins', 0, 0)
trigger_callback('satoshi_moves_his_coins', 0, 0)
trigger_callback('hal_moves_his_coins', 0, 0)
await fast_sleep()
self.assertEqual(el1._satoshi_cnt, 3)
self.assertEqual(el2._satoshi_cnt, 3)
self.assertEqual(el1._hal_cnt, 1)
self.assertEqual(el2._hal_cnt, 1)
# stop one listener, see new triggers are only seen by other one still running
el1.stop()
trigger_callback('satoshi_moves_his_coins', 0, 0)
trigger_callback('hal_moves_his_coins', 0, 0)
await fast_sleep()
self.assertEqual(el1._satoshi_cnt, 3)
self.assertEqual(el2._satoshi_cnt, 4)
self.assertEqual(el1._hal_cnt, 1)
self.assertEqual(el2._hal_cnt, 2)
async def test_gc(self):
objmap = count_objects_in_memory([MyEventListener])
self.assertEqual(len(objmap[MyEventListener]), 0)
self.assertEqual(_count_all_callbacks(), 0)
el1 = MyEventListener()
el1.start()
el2 = MyEventListener()
el2.start()
objmap = count_objects_in_memory([MyEventListener])
self.assertEqual(len(objmap[MyEventListener]), 2)
self.assertEqual(_count_all_callbacks(), 4)
# test if we can get GC-ed if we explicitly unregister cbs:
el1.stop() # calls unregister_callbacks
del el1
objmap = count_objects_in_memory([MyEventListener])
self.assertEqual(len(objmap[MyEventListener]), 1)
self.assertEqual(_count_all_callbacks(), 2)
# test if we can get GC-ed even without unregistering cbs:
del el2
objmap = count_objects_in_memory([MyEventListener])
self.assertEqual(len(objmap[MyEventListener]), 0)
self.assertEqual(_count_all_callbacks(), 0)
async def test_gc2(self):
def func():
el1 = MyEventListener(autostart=True)
el1.el2 = MyEventListener(autostart=True)
el1.el2.el3 = MyEventListener(autostart=True)
self.assertEqual(_count_all_callbacks(), 6)
func()
self.assertEqual(_count_all_callbacks(), 0)
async def test_gc_complex_using_wallet(self):
"""This test showcases why EventListener uses WeakMethodProper instead of weakref.WeakMethod.
We need the custom __eq__ for some reason.
"""
self.assertEqual(_count_all_callbacks(), 0)
config = SimpleConfig({'electrum_path': self.electrum_path})
wallet = restore_wallet_from_text__for_unittest(
"9dk", path=None, config=config,
)["wallet"]
assert wallet.lnworker is not None
# now delete the wallet, and wait for it to get GC-ed
# note: need to wait for cyclic GC. example: wallet.lnworker.wallet
wr = weakref.ref(wallet)
del wallet
async with util.async_timeout(5):
await wait_until_obj_is_garbage_collected(wr)
# by now, all callbacks must have been cleaned up:
self.assertEqual(_count_all_callbacks(), 0)