Browse Source

util.CallbackManager: make sure exceptions in cbs are logged

this resolves the existing FIXME
master
SomberNight 2 years ago
parent
commit
5e1e7dd9b3
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 29
      electrum/util.py

29
electrum/util.py

@ -21,6 +21,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import binascii
import concurrent.futures
import os, sys, re, json
from collections import defaultdict, OrderedDict
from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any,
@ -1729,11 +1730,12 @@ def randrange(bound: int) -> int:
return secrets.randbelow(bound - 1) + 1
class CallbackManager:
class CallbackManager(Logger):
# callbacks set by the GUI or any thread
# guarantee: the callbacks will always get triggered from the asyncio thread.
def __init__(self):
Logger.__init__(self)
self.callback_lock = threading.Lock()
self.callbacks = defaultdict(list) # note: needs self.callback_lock
self._running_cb_futs = set()
@ -1759,18 +1761,25 @@ class CallbackManager:
with self.callback_lock:
callbacks = self.callbacks[event][:]
for callback in callbacks:
# FIXME: if callback throws, we will lose the traceback
if asyncio.iscoroutinefunction(callback):
if asyncio.iscoroutinefunction(callback): # async cb
fut = asyncio.run_coroutine_threadsafe(callback(*args), loop)
# keep strong references around to avoid GC issues:
self._running_cb_futs.add(fut)
fut.add_done_callback(lambda fut_: self._running_cb_futs.remove(fut_))
elif get_running_loop() == loop:
# run callback immediately, so that it is guaranteed
# to have been executed when this method returns
callback(*args)
else:
loop.call_soon_threadsafe(callback, *args)
def on_done(fut_: concurrent.futures.Future):
assert fut_.done()
self._running_cb_futs.remove(fut_)
if exc := fut_.exception():
self.logger.error(f"cb errored. {event=}. {exc=}", exc_info=exc)
fut.add_done_callback(on_done)
else: # non-async cb
# note: the cb needs to run in the asyncio thread
if get_running_loop() == loop:
# run callback immediately, so that it is guaranteed
# to have been executed when this method returns
callback(*args)
else:
# note: if cb raises, asyncio will log the exception
loop.call_soon_threadsafe(callback, *args)
callback_mgr = CallbackManager()

Loading…
Cancel
Save