From 6c3969f879c7d2c334781299b01430db90ab4e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= <mzebrak@syncad.com> Date: Mon, 24 Mar 2025 08:33:25 +0100 Subject: [PATCH] Fix lock/sync race condition - wallet lock status worker is paused and resumed in right places #394 Also there could be observed: ``` clive.__private.core.commands.sync_state_with_beekeeper.InvalidWalletAmountError: Command SyncStateWithBeekeeper failed. Reason: The amount of wallets is invalid. Profile can have either 0 (if not created yet) or 2 wallets. ``` sometimes before this commit. Easier reproduction when setting `CLIVE_BEEKEEPER__REFRESH_TIMEOUT_SECS=0.01` --- clive/__private/ui/app.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/clive/__private/ui/app.py b/clive/__private/ui/app.py index 61a4731567..b4c9dc4158 100644 --- a/clive/__private/ui/app.py +++ b/clive/__private/ui/app.py @@ -191,7 +191,9 @@ class Clive(App[int]): ) self._refresh_beekeeper_wallet_lock_status_interval = self.set_interval( - safe_settings.beekeeper.refresh_timeout_secs, self.update_wallet_lock_status_from_beekeeper + safe_settings.beekeeper.refresh_timeout_secs, + self._retrigger_update_wallet_lock_status_from_beekeeper, + pause=True, ) should_enable_debug_loop = safe_settings.dev.should_enable_debug_loop @@ -254,6 +256,13 @@ class Clive(App[int]): def resume_refresh_node_data_interval(self) -> None: self._refresh_node_data_interval.resume() + def pause_refresh_beekeeper_wallet_lock_status_interval(self) -> None: + self._refresh_beekeeper_wallet_lock_status_interval.pause() + self.workers.cancel_group(self, "wallet_lock_status") + + def resume_refresh_beekeeper_wallet_lock_status_interval(self) -> None: + self._refresh_beekeeper_wallet_lock_status_interval.resume() + def trigger_profile_watchers(self) -> None: self.world.mutate_reactive(TUIWorld.profile_reactive) # type: ignore[arg-type] @@ -308,10 +317,9 @@ class Clive(App[int]): self.trigger_profile_watchers() self.trigger_node_watchers() - @work(name="beekeeper wallet lock status update worker") + @work(name="beekeeper wallet lock status update worker", group="wallet_lock_status") async def update_wallet_lock_status_from_beekeeper(self) -> None: - if self.world._beekeeper_manager: - await self.world.commands.sync_state_with_beekeeper("beekeeper_wallet_lock_status_update_worker") + await self.world.commands.sync_state_with_beekeeper("beekeeper_wallet_lock_status_update_worker") def switch_mode_with_reset(self, new_mode: CliveModes) -> AwaitComplete: """ @@ -349,6 +357,10 @@ class Clive(App[int]): async def switch_mode_into_locked(self, *, save_profile: bool = True) -> None: if save_profile: await self.world.commands.save_profile() + + # needs to be done before beekeeper API call to avoid race condition between manual lock and timeout lock + self.pause_refresh_beekeeper_wallet_lock_status_interval() + await self.world.commands.lock() def run_worker_with_guard(self, awaitable: Awaitable[None], guard: AsyncGuard) -> None: @@ -412,12 +424,17 @@ class Clive(App[int]): if self.is_worker_group_empty("alarms_data"): self.update_alarms_data() + def _retrigger_update_wallet_lock_status_from_beekeeper(self) -> None: + if self.is_worker_group_empty("wallet_lock_status"): + self.update_wallet_lock_status_from_beekeeper() + async def _switch_mode_into_locked(self, source: LockSource) -> None: if source == "beekeeper_wallet_lock_status_update_worker": self.notify("Switched to the LOCKED mode due to timeout.", timeout=10) self.pause_refresh_node_data_interval() self.pause_refresh_alarms_data_interval() + self.pause_refresh_beekeeper_wallet_lock_status_interval() self.world.node.cached.clear() await self.switch_mode_with_reset("unlock") await self.world.switch_profile(None) @@ -427,3 +444,4 @@ class Clive(App[int]): self.update_alarms_data_on_newest_node_data(suppress_cancelled_error=True) self.resume_refresh_node_data_interval() self.resume_refresh_alarms_data_interval() + self.resume_refresh_beekeeper_wallet_lock_status_interval() -- GitLab