From 61623d6d56282bc344d22bfa70d93c332964cd87 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 30 Jun 2025 22:36:49 +0000 Subject: [PATCH] daemon lockfile: if create_time is in the future, don't wait for it :D - if Electrum cannot do a graceful shutdown, the daemon lockfile won't get cleaned up - the daemon lockfile contains the creation time of the daemon - then, especially for an always offline phone/laptop, if the battery dies, the clock might go back to the past - the bug: next time Electrum starts, the program will wait until create_time + 1.0 before giving up on the daemon and creating a new one closes https://github.com/spesmilo/electrum/issues/9802 - certainly fixes at least https://github.com/spesmilo/electrum/issues/9802#issuecomment-3020220593 - the OP there might or might not be the same issue closes https://github.com/spesmilo/electrum/issues/9529 - same here, might or might not be the same issue the logic bug is quite old, from https://github.com/spesmilo/electrum/commit/e6020975a51f8f0d9e739d860454b2ade5b80e1f --- electrum/daemon.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index be85dbe1c..d5745e403 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -111,12 +111,13 @@ def get_file_descriptor(config: SimpleConfig): def request(config: SimpleConfig, endpoint, args=(), timeout: Union[float, int] = 60): lockfile = get_lockfile(config) - while True: - create_time = None + for attempt in range(5): + create_time = None # type: Optional[float | int] path = None try: with open(lockfile) as f: socktype, address, create_time = ast.literal_eval(f.read()) + int(create_time) # raise if not numeric if socktype == 'unix': path = address (host, port) = "127.0.0.1", 0 @@ -150,10 +151,17 @@ def request(config: SimpleConfig, endpoint, args=(), timeout: Union[float, int] return fut.result(timeout=timeout) except aiohttp.client_exceptions.ClientConnectorError as e: _logger.info(f"failed to connect to JSON-RPC server {e}") - if not create_time or create_time < time.time() - 1.0: + # We cannot communicate with the daemon. + # If daemon's creation time is very recent, it might still be starting up. + # In any other case, we raise: - too old create_time means daemon is likely dead, + # - create_time in future means our clock cannot be trusted. + if not (create_time <= time.time() <= create_time + 1.0): raise DaemonNotRunning() - # Sleep a bit and try again; it might have just been started + # Sleep a bit and try again; daemon might have just been started time.sleep(1.0) + # how did we even get here?! the clock must be going haywire. + _logger.error(f"Failed to connect to JSON-RPC server. Exhausted all attempts.") + raise DaemonNotRunning() def wait_until_daemon_becomes_ready(*, config: SimpleConfig, timeout=5) -> bool: