Skip to content
Snippets Groups Projects
Verified Commit 6c3969f8 authored by Mateusz Żebrak's avatar Mateusz Żebrak
Browse files

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`
parent a966bf40
No related branches found
No related tags found
No related merge requests found
...@@ -191,7 +191,9 @@ class Clive(App[int]): ...@@ -191,7 +191,9 @@ class Clive(App[int]):
) )
self._refresh_beekeeper_wallet_lock_status_interval = self.set_interval( 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 should_enable_debug_loop = safe_settings.dev.should_enable_debug_loop
...@@ -254,6 +256,13 @@ class Clive(App[int]): ...@@ -254,6 +256,13 @@ class Clive(App[int]):
def resume_refresh_node_data_interval(self) -> None: def resume_refresh_node_data_interval(self) -> None:
self._refresh_node_data_interval.resume() 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: def trigger_profile_watchers(self) -> None:
self.world.mutate_reactive(TUIWorld.profile_reactive) # type: ignore[arg-type] self.world.mutate_reactive(TUIWorld.profile_reactive) # type: ignore[arg-type]
...@@ -308,10 +317,9 @@ class Clive(App[int]): ...@@ -308,10 +317,9 @@ class Clive(App[int]):
self.trigger_profile_watchers() self.trigger_profile_watchers()
self.trigger_node_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: 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: def switch_mode_with_reset(self, new_mode: CliveModes) -> AwaitComplete:
""" """
...@@ -349,6 +357,10 @@ class Clive(App[int]): ...@@ -349,6 +357,10 @@ class Clive(App[int]):
async def switch_mode_into_locked(self, *, save_profile: bool = True) -> None: async def switch_mode_into_locked(self, *, save_profile: bool = True) -> None:
if save_profile: if save_profile:
await self.world.commands.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() await self.world.commands.lock()
def run_worker_with_guard(self, awaitable: Awaitable[None], guard: AsyncGuard) -> None: def run_worker_with_guard(self, awaitable: Awaitable[None], guard: AsyncGuard) -> None:
...@@ -412,12 +424,17 @@ class Clive(App[int]): ...@@ -412,12 +424,17 @@ class Clive(App[int]):
if self.is_worker_group_empty("alarms_data"): if self.is_worker_group_empty("alarms_data"):
self.update_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: async def _switch_mode_into_locked(self, source: LockSource) -> None:
if source == "beekeeper_wallet_lock_status_update_worker": if source == "beekeeper_wallet_lock_status_update_worker":
self.notify("Switched to the LOCKED mode due to timeout.", timeout=10) self.notify("Switched to the LOCKED mode due to timeout.", timeout=10)
self.pause_refresh_node_data_interval() self.pause_refresh_node_data_interval()
self.pause_refresh_alarms_data_interval() self.pause_refresh_alarms_data_interval()
self.pause_refresh_beekeeper_wallet_lock_status_interval()
self.world.node.cached.clear() self.world.node.cached.clear()
await self.switch_mode_with_reset("unlock") await self.switch_mode_with_reset("unlock")
await self.world.switch_profile(None) await self.world.switch_profile(None)
...@@ -427,3 +444,4 @@ class Clive(App[int]): ...@@ -427,3 +444,4 @@ class Clive(App[int]):
self.update_alarms_data_on_newest_node_data(suppress_cancelled_error=True) self.update_alarms_data_on_newest_node_data(suppress_cancelled_error=True)
self.resume_refresh_node_data_interval() self.resume_refresh_node_data_interval()
self.resume_refresh_alarms_data_interval() self.resume_refresh_alarms_data_interval()
self.resume_refresh_beekeeper_wallet_lock_status_interval()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment