Summary
install_sigterm_handler() in pgdog/src/main.rs ends the process with exit(0) as soon as SIGTERM arrives. That path never calls plugin::shutdown(), so plugins loaded through [[plugins]] do not get their fini hook. The graceful path (shutdown() → plugin::shutdown()) does run fini, so a plugin behaves differently depending on how PgDog was stopped.
SIGTERM is how Kubernetes (and most supervisors) stop a pod, so in practice fini is never invoked on a rollout. Any plugin that buffers work and flushes in fini (a network sink, a metrics exporter, an audit log) silently loses whatever it had queued at that moment.
Observed on v0.1.56; main still has the same code:
// pgdog/src/main.rs
fn install_sigterm_handler() {
...
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
tokio::spawn(async move {
sigterm.recv().await;
info!("🐕 PgDog is shutting down immediately [SIGTERM]");
exit(0);
});
}
}
Reproduction
- Load a plugin whose
fini logs a line and blocks briefly (or just logs).
- Start PgDog, then
kill -TERM <pid>.
- The
fini line never appears; exit status is 0. Sending SIGINT (graceful path) does log it.
Suggestion
Call plugin::shutdown() before exit(0) in the SIGTERM task, so fini runs on both paths, or route SIGTERM through the same shutdown sequence as SIGINT (possibly with the existing shutdown timeout so a misbehaving plugin cannot block the exit).
Workaround
Because the handler uses exit(0) rather than _exit, atexit(3) handlers do run. A plugin can register its flush with libc::atexit from its config/init hook (idempotent with fini). That is what we do now, but it depends on an implementation detail of the signal handler, so it would be better for PgDog to honor fini itself.
Summary
install_sigterm_handler()inpgdog/src/main.rsends the process withexit(0)as soon asSIGTERMarrives. That path never callsplugin::shutdown(), so plugins loaded through[[plugins]]do not get theirfinihook. The graceful path (shutdown()→plugin::shutdown()) does runfini, so a plugin behaves differently depending on how PgDog was stopped.SIGTERMis how Kubernetes (and most supervisors) stop a pod, so in practicefiniis never invoked on a rollout. Any plugin that buffers work and flushes infini(a network sink, a metrics exporter, an audit log) silently loses whatever it had queued at that moment.Observed on v0.1.56;
mainstill has the same code:Reproduction
finilogs a line and blocks briefly (or just logs).kill -TERM <pid>.finiline never appears; exit status is 0. SendingSIGINT(graceful path) does log it.Suggestion
Call
plugin::shutdown()beforeexit(0)in theSIGTERMtask, sofiniruns on both paths, or routeSIGTERMthrough the same shutdown sequence asSIGINT(possibly with the existing shutdown timeout so a misbehaving plugin cannot block the exit).Workaround
Because the handler uses
exit(0)rather than_exit,atexit(3)handlers do run. A plugin can register its flush withlibc::atexitfrom itsconfig/inithook (idempotent withfini). That is what we do now, but it depends on an implementation detail of the signal handler, so it would be better for PgDog to honorfiniitself.