Environment
pg_auto_failover 2.2 line, Linux (openEuler / EL). Reproduced under fault injection (blade kill --signal 9 targeting postgres by process name) during node startup.
Symptom
A node hangs forever in its current state (health=0, no FSM progress, no monitor heartbeat) until pgautofailover is restarted manually. postmaster.pid still exists and points at a pid that ps shows as <defunct>, parented by the pg_autoctl: start/stop postgres controller sub-process.
Sequence
- The Postgres controller sub-process forks the postmaster.
- Postgres is SIGKILLed mid-startup, before it writes
PM_STATUS_READY into postmaster.pid. The postmaster becomes a zombie child of the controller.
pg_setup_is_ready() -> get_pgpid() reads the stale pid from postmaster.pid and probes it with kill(pid, 0) (src/bin/common/pgsetup.c, currently line 497). A zombie still has a process-table entry, so this returns 0 and the pid is reported as alive.
pg_setup_wait_until_is_ready() therefore keeps retrying every 350ms forever, waiting for a ready status that a dead process will never write.
- Because the controller is stuck inside that retry loop, it never reaches the
waitpid(-1, WNOHANG) in its main loop. The zombie is never reaped, its shared memory segment stays attached, and no new postmaster can be started.
Proposed fix
Replace the bare kill(pid, 0) probe in get_pgpid() with a helper:
kill(pid, 0) != 0 -> not alive (unchanged fast path)
waitpid(pid, &st, WNOHANG) == pid -> the pid was our zombie child; it is now reaped, report not alive
waitpid(...) == 0 -> child is genuinely running -> alive (so slow-startup behaviour is unchanged)
waitpid(...) == -1, ECHILD -> pid is not our child (keeper or CLI reading postmaster.pid) -> fall back to the pre-existing kill(pid, 0) semantics
Environment
pg_auto_failover 2.2 line, Linux (openEuler / EL). Reproduced under fault injection (
blade kill --signal 9targeting postgres by process name) during node startup.Symptom
A node hangs forever in its current state (health=0, no FSM progress, no monitor heartbeat) until
pgautofailoveris restarted manually.postmaster.pidstill exists and points at a pid thatpsshows as<defunct>, parented by thepg_autoctl: start/stop postgrescontroller sub-process.Sequence
PM_STATUS_READYintopostmaster.pid. The postmaster becomes a zombie child of the controller.pg_setup_is_ready()->get_pgpid()reads the stale pid frompostmaster.pidand probes it withkill(pid, 0)(src/bin/common/pgsetup.c, currently line 497). A zombie still has a process-table entry, so this returns 0 and the pid is reported as alive.pg_setup_wait_until_is_ready()therefore keeps retrying every 350ms forever, waiting for a ready status that a dead process will never write.waitpid(-1, WNOHANG)in its main loop. The zombie is never reaped, its shared memory segment stays attached, and no new postmaster can be started.Proposed fix
Replace the bare
kill(pid, 0)probe inget_pgpid()with a helper:kill(pid, 0) != 0-> not alive (unchanged fast path)waitpid(pid, &st, WNOHANG) == pid-> the pid was our zombie child; it is now reaped, report not alivewaitpid(...) == 0-> child is genuinely running -> alive (so slow-startup behaviour is unchanged)waitpid(...) == -1, ECHILD-> pid is not our child (keeper or CLI readingpostmaster.pid) -> fall back to the pre-existingkill(pid, 0)semantics