From 198ebf855c8e858a750d32d92c80dd9e4557dd36 Mon Sep 17 00:00:00 2001 From: neptunehub Date: Mon, 8 Jun 2026 11:57:22 +0200 Subject: [PATCH 1/3] Windows build - fix icon try button --- windows/db_backend.py | 57 +++++++++++++++++++++++++++++++++++++------ windows/paths.py | 12 +++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/windows/db_backend.py b/windows/db_backend.py index 4e095d1a..e5aa1612 100644 --- a/windows/db_backend.py +++ b/windows/db_backend.py @@ -34,6 +34,8 @@ _USE_PGSERVER = None +_NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) + def _check_pgserver(): """Return True if pgserver can be imported (has a Windows wheel).""" @@ -91,6 +93,7 @@ def _preinit_scram(data_dir, password): "--auth-host=scram-sha-256", "--auth-local=scram-sha-256", "--encoding=utf8", f"--pwfile={pwfile}"], check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + stdin=subprocess.DEVNULL, creationflags=_NO_WINDOW, ) finally: if pwfile and os.path.exists(pwfile): @@ -159,9 +162,49 @@ def _clear_stale_data_dir(data_dir): shutil.rmtree(data_dir, ignore_errors=True) +def _patch_pgserver_pg_ctl(): + """Make pgserver's ``pg_ctl start`` survive a double-click (tray) launch. + + The bundled pgserver starts the cluster with ``pg_ctl -w start`` under a + hardcoded 10s :func:`subprocess.run` timeout and with no Windows creation + flags, so the spawned ``postgres`` inherits the launching console. That is + fine for ``AudioMuse-AI.exe start`` (supervisor on the main thread of a real + console), but not for a double-click, where the supervisor boots from a + daemon thread under a hidden console: the inherited console stalls + ``pg_ctl``'s readiness wait past 10s, the timeout fires, and the half-started + postgres is left orphaned holding the data dir -- bricking every later start. + + Replace the ``pg_ctl`` symbol pgserver calls with a wrapper that runs every + ``start`` detached from any console and with a generous timeout. Idempotent; + a no-op off Windows. + """ + if os.name != "nt": + return + import pgserver.postgres_server as ps + if getattr(ps, "_audiomuse_pg_ctl_patched", False): + return + original = ps.pg_ctl + timeout = paths.pg_start_timeout() + + def pg_ctl(args, **kwargs): + if "start" in args: + if kwargs.get("timeout") is None or kwargs["timeout"] < timeout: + kwargs["timeout"] = timeout + kwargs.setdefault("stdin", subprocess.DEVNULL) + kwargs["creationflags"] = kwargs.get("creationflags", 0) | _NO_WINDOW + env = dict(kwargs.get("env") or os.environ) + env.setdefault("PGCTLTIMEOUT", str(timeout)) + kwargs["env"] = env + return original(args, **kwargs) + + ps.pg_ctl = pg_ctl + ps._audiomuse_pg_ctl_patched = True + + def start_embedded(data_dir): pw = paths.db_password() if _check_pgserver(): + _patch_pgserver_pg_ctl() import database fresh = not os.path.exists(os.path.join(data_dir, "PG_VERSION")) if fresh: @@ -170,14 +213,13 @@ def start_embedded(data_dir): try: uri = database.start_embedded(data_dir) except Exception: - if os.path.isdir(data_dir): - logger.warning("PostgreSQL data dir stale after crash — clearing and retrying") - import shutil - shutil.rmtree(data_dir, ignore_errors=True) - _preinit_scram(data_dir, pw) - uri = database.start_embedded(data_dir) - else: + if not fresh: raise + logger.warning("Fresh PostgreSQL cluster failed to start — clearing and retrying once") + import shutil + shutil.rmtree(data_dir, ignore_errors=True) + _preinit_scram(data_dir, pw) + uri = database.start_embedded(data_dir) if not fresh: _harden_existing(data_dir, pw, uri) return _conn_from_uri(uri, pw) @@ -188,6 +230,7 @@ def start_embedded(data_dir): def ensure_embedded_running(data_dir): pw = paths.db_password() if _check_pgserver(): + _patch_pgserver_pg_ctl() import database return _conn_from_uri(database.ensure_embedded_running(data_dir), pw) from windows import embedded_pg diff --git a/windows/paths.py b/windows/paths.py index ccc15bb3..702853f4 100644 --- a/windows/paths.py +++ b/windows/paths.py @@ -136,6 +136,18 @@ def pg_port(): return 5432 +def pg_start_timeout(): + """Seconds to wait for embedded PostgreSQL to report ready before giving up. + + pgserver's built-in 10s is too short when the supervisor boots from the tray + app's daemon thread under a hidden console, and on a cold first start while + Windows Defender scans the freshly extracted binaries. A premature timeout + orphans postgres.exe holding the data dir and bricks the next start, so allow + a generous window. + """ + return 120 + + def redis_url(): """Password-bearing URL for the embedded Redis (loopback TCP, scram-equivalent gate).""" return f"redis://:{quote(redis_password(), safe='')}@127.0.0.1:{redis_port()}/0" From f8d2fbe8e09cbcf8e6b6b09a9510967ae3aff39d Mon Sep 17 00:00:00 2001 From: neptunehub Date: Mon, 8 Jun 2026 12:03:03 +0200 Subject: [PATCH 2/3] gemini review --- windows/db_backend.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/windows/db_backend.py b/windows/db_backend.py index e5aa1612..3f749c93 100644 --- a/windows/db_backend.py +++ b/windows/db_backend.py @@ -26,6 +26,7 @@ import os import subprocess import tempfile +import threading from urllib.parse import urlparse from windows import paths @@ -35,6 +36,7 @@ _USE_PGSERVER = None _NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) +_patch_lock = threading.Lock() def _check_pgserver(): @@ -183,22 +185,26 @@ def _patch_pgserver_pg_ctl(): import pgserver.postgres_server as ps if getattr(ps, "_audiomuse_pg_ctl_patched", False): return - original = ps.pg_ctl - timeout = paths.pg_start_timeout() - - def pg_ctl(args, **kwargs): - if "start" in args: - if kwargs.get("timeout") is None or kwargs["timeout"] < timeout: - kwargs["timeout"] = timeout - kwargs.setdefault("stdin", subprocess.DEVNULL) - kwargs["creationflags"] = kwargs.get("creationflags", 0) | _NO_WINDOW - env = dict(kwargs.get("env") or os.environ) - env.setdefault("PGCTLTIMEOUT", str(timeout)) - kwargs["env"] = env - return original(args, **kwargs) - - ps.pg_ctl = pg_ctl - ps._audiomuse_pg_ctl_patched = True + with _patch_lock: + if getattr(ps, "_audiomuse_pg_ctl_patched", False): + return + original = ps.pg_ctl + timeout = paths.pg_start_timeout() + + def pg_ctl(args, **kwargs): + if args and "start" in args: + if kwargs.get("timeout") is None or kwargs["timeout"] < timeout: + kwargs["timeout"] = timeout + kwargs.setdefault("stdin", subprocess.DEVNULL) + kwargs["creationflags"] = (kwargs.get("creationflags") or 0) | _NO_WINDOW + env = kwargs.get("env") + env = dict(os.environ if env is None else env) + env.setdefault("PGCTLTIMEOUT", str(timeout)) + kwargs["env"] = env + return original(args, **kwargs) + + ps.pg_ctl = pg_ctl + ps._audiomuse_pg_ctl_patched = True def start_embedded(data_dir): From 8cc9ed4ea9b1db3f3a9d2391f942398e0fca1993 Mon Sep 17 00:00:00 2001 From: neptunehub Date: Mon, 8 Jun 2026 12:43:12 +0200 Subject: [PATCH 3/3] backu prestore race condition fix --- windows/db_backend.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/windows/db_backend.py b/windows/db_backend.py index 3f749c93..da45e45a 100644 --- a/windows/db_backend.py +++ b/windows/db_backend.py @@ -37,6 +37,7 @@ _NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) _patch_lock = threading.Lock() +_embedded_lock = threading.Lock() def _check_pgserver(): @@ -216,16 +217,17 @@ def start_embedded(data_dir): if fresh: _clear_stale_data_dir(data_dir) _preinit_scram(data_dir, pw) - try: - uri = database.start_embedded(data_dir) - except Exception: - if not fresh: - raise - logger.warning("Fresh PostgreSQL cluster failed to start — clearing and retrying once") - import shutil - shutil.rmtree(data_dir, ignore_errors=True) - _preinit_scram(data_dir, pw) - uri = database.start_embedded(data_dir) + with _embedded_lock: + try: + uri = database.start_embedded(data_dir) + except Exception: + if not fresh: + raise + logger.warning("Fresh PostgreSQL cluster failed to start — clearing and retrying once") + import shutil + shutil.rmtree(data_dir, ignore_errors=True) + _preinit_scram(data_dir, pw) + uri = database.start_embedded(data_dir) if not fresh: _harden_existing(data_dir, pw, uri) return _conn_from_uri(uri, pw) @@ -238,14 +240,16 @@ def ensure_embedded_running(data_dir): if _check_pgserver(): _patch_pgserver_pg_ctl() import database - return _conn_from_uri(database.ensure_embedded_running(data_dir), pw) + with _embedded_lock: + return _conn_from_uri(database.ensure_embedded_running(data_dir), pw) from windows import embedded_pg return embedded_pg.ensure_running(data_dir, pw) def stop_embedded(): - if _check_pgserver(): - import database - return database.stop_embedded() - from windows import embedded_pg - return embedded_pg.stop() + with _embedded_lock: + if _check_pgserver(): + import database + return database.stop_embedded() + from windows import embedded_pg + return embedded_pg.stop()