Merge pull request #705 from PR0M3TH3AN/codex/revise-start_background_sync-implementation

Implement async sync worker cleanup
This commit is contained in:
thePR0M3TH3AN
2025-07-31 21:37:14 -04:00
committed by GitHub
4 changed files with 54 additions and 7 deletions

View File

@@ -1056,6 +1056,7 @@ def display_menu(
continue
logging.info("Exiting the program.")
print(colored("Exiting the program.", "green"))
getattr(password_manager, "cleanup", lambda: None)()
password_manager.nostr_client.close_client_pool()
sys.exit(0)
if choice == "1":
@@ -1259,6 +1260,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
print(colored("\nReceived shutdown signal. Exiting gracefully...", "yellow"))
logging.info(f"Received shutdown signal: {sig}. Initiating graceful shutdown.")
try:
getattr(password_manager, "cleanup", lambda: None)()
password_manager.nostr_client.close_client_pool()
logging.info("NostrClient closed successfully.")
except Exception as exc:
@@ -1277,6 +1279,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
logger.info("Program terminated by user via KeyboardInterrupt.")
print(colored("\nProgram terminated by user.", "yellow"))
try:
getattr(password_manager, "cleanup", lambda: None)()
password_manager.nostr_client.close_client_pool()
logging.info("NostrClient closed successfully.")
except Exception as exc:
@@ -1287,6 +1290,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
logger.error(f"A user-related error occurred: {e}", exc_info=True)
print(colored(f"Error: {e}", "red"))
try:
getattr(password_manager, "cleanup", lambda: None)()
password_manager.nostr_client.close_client_pool()
logging.info("NostrClient closed successfully.")
except Exception as exc:
@@ -1297,6 +1301,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
print(colored(f"Error: An unexpected error occurred: {e}", "red"))
try:
getattr(password_manager, "cleanup", lambda: None)()
password_manager.nostr_client.close_client_pool()
logging.info("NostrClient closed successfully.")
except Exception as exc:

View File

@@ -1281,20 +1281,30 @@ class PasswordManager:
async def _worker() -> None:
try:
if hasattr(self, "nostr_client") and hasattr(self, "vault"):
self.attempt_initial_sync()
if hasattr(self, "sync_index_from_nostr"):
self.sync_index_from_nostr()
await self.attempt_initial_sync_async()
await self.sync_index_from_nostr_async()
except Exception as exc:
logger.warning(f"Background sync failed: {exc}")
try:
loop = asyncio.get_running_loop()
except RuntimeError:
threading.Thread(target=lambda: asyncio.run(_worker()), daemon=True).start()
thread = threading.Thread(
target=lambda: asyncio.run(_worker()), daemon=True
)
thread.start()
self._sync_task = thread
else:
self._sync_task = asyncio.create_task(_worker())
def cleanup(self) -> None:
"""Cancel any pending background sync task."""
task = getattr(self, "_sync_task", None)
if isinstance(task, asyncio.Task) and not task.done():
task.cancel()
elif isinstance(task, threading.Thread) and task.is_alive():
task.join(timeout=0.1)
def start_background_relay_check(self) -> None:
"""Check relay health in a background thread."""
if (

View File

@@ -195,6 +195,9 @@ class MainWindow(toga.Window):
bus.unsubscribe("sync_started", self.sync_started)
bus.unsubscribe("sync_finished", self.sync_finished)
bus.unsubscribe("vault_locked", self.vault_locked)
manager = getattr(self.nostr, "_manager", None)
if manager is not None:
manager.cleanup()
class EntryDialog(toga.Window):

View File

@@ -1,4 +1,6 @@
import time
import asyncio
import warnings
from types import SimpleNamespace
from pathlib import Path
import sys
@@ -17,14 +19,15 @@ def test_unlock_triggers_sync(monkeypatch, tmp_path):
pm.initialize_managers = lambda: None
called = {"sync": False}
def fake_sync(self):
async def fake_sync(self):
called["sync"] = True
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr", fake_sync)
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr_async", fake_sync)
pm.unlock_vault("pw")
pm.start_background_sync()
time.sleep(0.05)
pm.cleanup()
assert called["sync"]
@@ -54,3 +57,29 @@ def test_quick_unlock_background_sync(monkeypatch, tmp_path):
pm.exit_managed_account()
assert called["bg"]
def test_start_background_sync_running_loop(monkeypatch):
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
called = {"init": False, "sync": False}
async def fake_attempt(self):
called["init"] = True
async def fake_sync(self):
called["sync"] = True
monkeypatch.setattr(PasswordManager, "attempt_initial_sync_async", fake_attempt)
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr_async", fake_sync)
async def runner():
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
pm.start_background_sync()
await asyncio.sleep(0.01)
assert not any(issubclass(wi.category, RuntimeWarning) for wi in w)
asyncio.run(runner())
pm.cleanup()
assert called["init"] and called["sync"]