From f402cb3cd16ccf2b17f39e2ba215f96df43a782b Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 3 Nov 2025 17:54:48 +0100 Subject: [PATCH] plugin: watchtower: call start_watching() threadsafe Launch `WatchtowerPlugin.watchtower.start_watching()` through asyncio.run_coroutine_threadsafe instead of ensure_future to prevent the following exception from happening when running python with `PYTHONASYNCIODEBUG=1`. ``` 20251103T165007.087746Z | ERROR | plugin.Plugins | cannot initialize plugin watchtower: Error loading watchtower plugin: RuntimeError('Non-thread-safe operation invoked on an event loop other than the current one') Traceback (most recent call last): File "/home/user/code/electrum-fork/electrum/plugin.py", line 629, in load_plugin_by_name plugin = module.Plugin(self, self.config, name) File "/home/user/code/electrum-fork/electrum/plugins/watchtower/watchtower.py", line 59, in __init__ asyncio.ensure_future(self.watchtower.start_watching()) ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib64/python3.14/asyncio/tasks.py", line 732, in ensure_future return loop.create_task(coro_or_future) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/usr/lib64/python3.14/asyncio/base_events.py", line 468, in create_task return self._task_factory(self, coro, **kwargs) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/code/electrum-fork/electrum/util.py", line 1773, in factory task = asyncio.Task(coro, loop=loop_, **kwargs) File "/usr/lib64/python3.14/asyncio/base_events.py", line 829, in call_soon self._check_thread() ~~~~~~~~~~~~~~~~~~^^ File "/usr/lib64/python3.14/asyncio/base_events.py", line 866, in _check_thread raise RuntimeError( "Non-thread-safe operation invoked on an event loop other " "than the current one") RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/home/user/code/electrum-fork/electrum/plugin.py", line 184, in load_plugins self.load_plugin_by_name(name) ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/home/user/code/electrum-fork/electrum/plugin.py", line 631, in load_plugin_by_name raise Exception(f"Error loading {name} plugin: {repr(e)}") from e Exception: Error loading watchtower plugin: RuntimeError('Non-thread-safe operation invoked on an event loop other than the current one') ``` --- electrum/plugins/watchtower/watchtower.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/plugins/watchtower/watchtower.py b/electrum/plugins/watchtower/watchtower.py index 6bb08a801..04212c2a9 100644 --- a/electrum/plugins/watchtower/watchtower.py +++ b/electrum/plugins/watchtower/watchtower.py @@ -56,7 +56,7 @@ class WatchtowerPlugin(BasePlugin): return self.watchtower = WatchTower(self.network) - asyncio.ensure_future(self.watchtower.start_watching()) + asyncio.run_coroutine_threadsafe(self.watchtower.start_watching(), self.network.asyncio_loop) if watchtower_port := self.config.WATCHTOWER_SERVER_PORT: self.server = WatchTowerServer(self.watchtower, self.network, watchtower_port) asyncio.run_coroutine_threadsafe(self.network.taskgroup.spawn(self.server.run), self.network.asyncio_loop)