1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
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)
|