Repository navigation
fix(worker): survive a database restart; clean exit when the database is down at start - #42
Aspirant200715 wants to merge 6 commits into
Conversation
…i host, /healthz - deploy/worker-vm: compose file, runbook, env template and preflight for the agent worker on its own VM - worker: refuses a second process with the same WORKER_ID, can require every agent key, sweeps abandoned workspaces, warns when named after its hostname - deploy/worker-vm/db-role.sql: a mergit_worker role that can touch only jobs, pr_requests, plans and tasks - api: GET /healthz (database check), compose healthcheck, update.sh waits for it - Caddy: api.mergit.site serves browser routes and /healthz; /v0/internal only from WORKER_VM_IP - cp_postgres published on CP_DB_BIND (loopback by default)
…e compose The postgres:16 image's pg_hba ends with `host all all all scram-sha-256`, so once CP_DB_BIND publishes cp_postgres on the private network the worker VM can log in as the superuser with the password committed in compose.controlplane.yaml, which makes the restricted role pointless. cp_postgres now uses deploy/postgres/pg_hba.conf: any role from the compose network (samenet), and from anywhere else only mergit_worker on mergit_cp. In-container socket access (backup.sh, wallet-interest.sh, create-db-role.sh) is unchanged.
…ipt, fail-fast db connects Follow-up to Viscous106's review of mergit-io#38. - pg_hba.conf matched the compose network with samenet, which under some Docker network settings (docker-proxy's own bridge gateway, iptables: false) is the address a published port sees a connection arrive from: the superuser, password and all, would then be reachable from outside. The compose network now pins a subnet (networks.default.ipam) and pg_hba.conf matches that exact CIDR instead. - api's 8000:8000 published to every interface, bypassing the Caddy lockdown for a run without compose.server.yaml. Bound to 127.0.0.1. - deploy/worker-vm/README.md ran create-db-role.sh before the stack that creates the control plane's tables, which create-db-role.sh itself says it needs. Swapped the order. - The three places a worker or the api reconnects to Postgres (WorkerIdLock, PgJobStore, PgPrStore) had no connect_timeout, so a database that is down or unreachable hung the call forever instead of letting start-up or a reconnect fail and get retried. Two new tests pin the subnet and the api binding so they cannot drift back to samenet or an open port silently. Checked locally: full control-plane suite (328 passed), ruff and pyright clean, and `docker compose config` resolves correctly for compose.testnet+controlplane, and with compose.server.yaml layered on top (api's port is fully unpublished there, as before).
|
this branch carries #38's commits plus its own on |
Viscous106
left a comment
There was a problem hiding this comment.
I compared the current #42 against #38. #42 is stacked on an older snapshot of #38, not independent: both share a base, and 19 of the 25 files are byte-identical. #42 adds the lock reconnect (still_held), the watch_lock thread, exit 1 when the DB is down at start, exit 2 on lock loss, restart tests in test_worker_role.py and two README rows. I read the diffs only and ran nothing.
Merging this as-is would regress #38's newer fixes, which #42 lacks:
- the pinned subnet
172.30.9.0/24and exact-CIDRpg_hba.conf(#42 is back tosamenet) 127.0.0.1:8000for the API port, with its test- the README order fix
connect_timeout=10inWorkerIdLock.acquire,pr_store.pyandjobs.py(#42 doesn't touch the last two)
Every shared file is add/add, so whichever merges second will conflict. Suggested order: land #38 first (after its own fixes and a rebase on main), then rebase this down to its delta only (startup.py, __main__.py, test_worker_role.py and the two README rows).
Should fix in this PR
- No
connect_timeoutin_take()(startup.py, ~line 130 of the new file). On a blackholed DB the connect hangs, so the "clean exit when the database is down at start" in the title never fires. The same applies to the watcher's reconnect. - Lock still not released after a power loss. There are no TCP keepalives anywhere, and
still_held'sSELECT 1can block forever on a half-open socket, so the watchdog never notices the loss. Setkeepalives_idle,keepalives_intervalandkeepalives_counton the lock connection, ortcp_keepalives_*on the server, and giveSELECT 1a timeout. - Watcher races
close().__main__.pyfinallydoesstop.set(); built.close(); lock.close()while the watcher may be insidestill_held(). It can reassign_connafter_drop(), leaking the connection and the lock (daemon thread, no join). Join the watcher before closing. - Test safety:
test_worker_role.pyrunsDROP ROLE mergit_worker, which is cluster-wide. IfTEST_DATABASE_URLpoints at a dev or prod cluster, it destroys the real role. - Lock-loss timing: exit 2 only happens after the current job finishes, so that job's result is published after another process owns the id. Fine given the 40-minute grace period, but please add a comment.
Everything else from my #38 review (/healthz rate limiting, cleartext DB link, update.sh exit code, CONNECT not revoked from PUBLIC) applies here too.
Not verified: tests, compose, CI, Docker source-IP behaviour, and the current origin/main.
…ress review findings
…n exit when the database is down at start
5540bb4 to
b151281
Compare
Follow-up to the #38 review notes.
What changed
What I verified
Not verified