Browse Source

fix main script hanging (not exiting after exception) in some cases

Previously an unhandled exception in the main script could cause the main thread to die
but the process to hang, as the event loop thread would keep running.

example:
$ ./run_electrum -o signmessage tb1qeh090ruc3cs5hry90tev4fsvrnegulw8xssdzx "mymsg" -w ~/.electrum/testnet/wallets/test_segwit_2
Traceback (most recent call last):
  File "./run_electrum", line 424, in <module>
    init_cmdline(config_options, wallet_path, False)
  File "./run_electrum", line 146, in init_cmdline
    db = WalletDB(storage.read(), manual_upgrades=False)
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 72, in __init__
    self.load_data(raw)
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 103, in load_data
    self._after_upgrade_tasks()
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 189, in _after_upgrade_tasks
    self._load_transactions()
  File "/home/user/wspace/electrum/electrum/util.py", line 406, in <lambda>
    return lambda *args, **kw_args: do_profile(args, kw_args)
  File "/home/user/wspace/electrum/electrum/util.py", line 402, in do_profile
    o = func(*args, **kw_args)
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 1139, in _load_transactions
    self.data = StoredDict(self.data, self, [])
  File "/home/user/wspace/electrum/electrum/json_db.py", line 79, in __init__
    self.__setitem__(k, v)
  File "/home/user/wspace/electrum/electrum/json_db.py", line 44, in wrapper
    return func(self, *args, **kwargs)
  File "/home/user/wspace/electrum/electrum/json_db.py", line 105, in __setitem__
    v = self.db._convert_dict(self.path, key, v)
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 1182, in _convert_dict
    v = dict((k, Invoice.from_json(x)) for k, x in v.items())
  File "/home/user/wspace/electrum/electrum/wallet_db.py", line 1182, in <genexpr>
    v = dict((k, Invoice.from_json(x)) for k, x in v.items())
  File "/home/user/wspace/electrum/electrum/invoices.py", line 110, in from_json
    return OnchainInvoice(**x)
  File "<attrs generated init electrum.invoices.OnchainInvoice>", line 8, in __init__
  File "/home/user/wspace/electrum/electrum/invoices.py", line 68, in _decode_outputs
    output = PartialTxOutput.from_legacy_tuple(*output)
  File "/home/user/wspace/electrum/electrum/transaction.py", line 131, in from_legacy_tuple
    return cls.from_address_and_value(addr, val)
  File "/home/user/wspace/electrum/electrum/transaction.py", line 104, in from_address_and_value
    return cls(scriptpubkey=bfh(bitcoin.address_to_script(address)),
  File "/home/user/wspace/electrum/electrum/bitcoin.py", line 422, in address_to_script
    raise BitcoinException(f"invalid bitcoin address: {addr}")
electrum.util.BitcoinException: invalid bitcoin address: tb1qckp4ztmstwtyxzml3dmfvegeq5mfxwu2h3q94l
patch-4
SomberNight 5 years ago
parent
commit
19f17a2bff
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 40
      run_electrum

40
run_electrum

@ -37,7 +37,7 @@ if sys.version_info[:3] < _min_python_version_tuple:
import warnings
import asyncio
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
script_dir = os.path.dirname(os.path.realpath(__file__))
@ -95,6 +95,8 @@ from electrum import keystore
from electrum.util import create_and_start_event_loop
if TYPE_CHECKING:
import threading
from electrum.plugin import Plugins
_logger = get_logger(__name__)
@ -113,7 +115,7 @@ def prompt_password(prompt, confirm=True):
return password
def init_cmdline(config_options, wallet_path, server):
def init_cmdline(config_options, wallet_path, server, *, config: 'SimpleConfig'):
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
@ -259,13 +261,20 @@ def init_plugins(config, gui_name):
from electrum.plugin import Plugins
return Plugins(config, gui_name)
loop = None # type: Optional[asyncio.AbstractEventLoop]
stop_loop = None # type: Optional[asyncio.Future]
loop_thread = None # type: Optional[threading.Thread]
def sys_exit(i):
# stop event loop and exit
loop.call_soon_threadsafe(stop_loop.set_result, 1)
loop_thread.join(timeout=1)
if loop:
loop.call_soon_threadsafe(stop_loop.set_result, 1)
loop_thread.join(timeout=1)
sys.exit(i)
if __name__ == '__main__':
def main():
# The hook will only be used in the Qt GUI right now
util.setup_thread_excepthook()
# on macOS, delete Process Serial Number arg generated for apps launched in Finder
@ -368,8 +377,21 @@ if __name__ == '__main__':
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
global loop, stop_loop, loop_thread
loop, stop_loop, loop_thread = create_and_start_event_loop()
try:
handle_cmd(
cmdname=cmdname,
config=config,
config_options=config_options,
)
except Exception:
_logger.exception("")
sys_exit(1)
def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
if cmdname == 'gui':
configure_logging(config)
fd = daemon.get_file_descriptor(config)
@ -404,7 +426,7 @@ if __name__ == '__main__':
cmd = known_commands[cmdname]
wallet_path = config.get_wallet_path()
if not config.get('offline'):
init_cmdline(config_options, wallet_path, True)
init_cmdline(config_options, wallet_path, True, config=config)
timeout = config.get('timeout', 60)
if timeout: timeout = int(timeout)
try:
@ -421,7 +443,7 @@ if __name__ == '__main__':
if cmd.requires_network:
print_msg("This command cannot be run offline")
sys_exit(1)
init_cmdline(config_options, wallet_path, False)
init_cmdline(config_options, wallet_path, False, config=config)
plugins = init_plugins(config, 'cmdline')
coro = run_offline_command(config, config_options, plugins)
fut = asyncio.run_coroutine_threadsafe(coro, loop)
@ -438,3 +460,7 @@ if __name__ == '__main__':
elif result is not None:
print_msg(json_encode(result))
sys_exit(0)
if __name__ == '__main__':
main()

Loading…
Cancel
Save