bgworker-and-extensions — background workers + extension integration
This is the procedural cookbook for registering and writing PostgreSQL
background workers and for layering extension hooks on the postmaster
lifecycle. For the conceptual model see
knowledge/idioms/bgworker-and-parallel.md.
This skill is one of three siblings that share the _PG_init /
postmaster-lifecycle boundary:
gucs-config— custom GUC variables.- bgworker-and-extensions (this skill) — background workers + hooks.
parallel-query— ParallelContext + parallel-safe markings.
1. Static vs dynamic registration
| API | Where to call | Restart? |
|---|---|---|
RegisterBackgroundWorker(&w) |
_PG_init, only when process_shared_preload_libraries_in_progress |
Yes per bgw_restart_time |
RegisterDynamicBackgroundWorker(&w, &handle) |
Any backend at runtime | Yes per bgw_restart_time, but worker is forgotten once handle goes away unless re-registered |
[verified-by-code source/src/include/postmaster/bgworker.h:122-133]
RegisterBackgroundWorker errors out unless called from _PG_init during
shared-library preload (i.e. process_shared_preload_libraries_in_progress
is true). From any other site — including a regular backend at runtime —
use RegisterDynamicBackgroundWorker instead.
2. Filling the BackgroundWorker struct
BackgroundWorker worker;
memset(&worker, 0, sizeof(worker));
snprintf(worker.bgw_name, BGW_MAXLEN, "my_ext worker %d", i);
snprintf(worker.bgw_type, BGW_MAXLEN, "my_ext"); /* shown in pg_stat_activity */
worker.bgw_flags = BGWORKER_SHMEM_ACCESS
| BGWORKER_BACKEND_DATABASE_CONNECTION;
worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
worker.bgw_restart_time = BGW_NEVER_RESTART; /* or seconds */
sprintf(worker.bgw_library_name, "my_ext"); /* shared object name */
sprintf(worker.bgw_function_name, "my_ext_main");
worker.bgw_main_arg = Int32GetDatum(i);
/* bgw_extra: up to BGW_EXTRALEN bytes the launcher hands to the worker
as an opaque blob. worker_spi packs Oid dboid + Oid roleoid + uint32
flags here (worker_spi.c:470-475); worker_spi_main memcpy's them out
in its main function (worker_spi.c:152-157). */
worker.bgw_notify_pid = MyProcPid; /* 0 = no SIGUSR1 */
RegisterBackgroundWorker(&worker);
[verified-by-code source/src/include/postmaster/bgworker.h:96-108;
source/src/test/modules/worker_spi/worker_spi.c:362-385]
3. Flag cheatsheet
[verified-by-code source/src/include/postmaster/bgworker.h:50-75]
| Flag | Meaning |
|---|---|
BGWORKER_SHMEM_ACCESS |
Required for any worker that touches shared buffers / LWLocks. |
BGWORKER_BACKEND_DATABASE_CONNECTION |
Worker may call BackgroundWorkerInitializeConnection*. Requires SHMEM_ACCESS. |
BGWORKER_INTERRUPTIBLE |
Worker exits if its DB is CREATE/ALTER/DROP'd. Requires the two above. |
BGWORKER_CLASS_PARALLEL |
Don't set — internal, counts against max_parallel_workers. See parallel-query. |
4. Start times
[verified-by-code source/src/include/postmaster/bgworker.h:84-89]
BgWorkerStart_PostmasterStart— earliest. No DB access yet; replication / archive only.BgWorkerStart_ConsistentState— DB is consistent (during recovery, after WAL apply reached a consistent point). Hot-standby readers can run here.BgWorkerStart_RecoveryFinished— primary mode only, or standby promotion. This is what you usually want.
5. Restart policy
Two knobs decide restart; both must allow it.
bgw_restart_time |
Worker exit | Restarted? |
|---|---|---|
BGW_NEVER_RESTART (-1) |
any | No — slot is freed. |
| N seconds | proc_exit(0) (return 0) |
No — clean exit retires the slot regardless of bgw_restart_time. |
| N seconds | proc_exit(1) (return 1) |
Yes, after N seconds. |
| N seconds | crash / signal | Yes, after N seconds. |
[from-comment source/src/include/postmaster/bgworker.h:14-27]
6. Worker main function skeleton
pg_noreturn PGDLLEXPORT void my_ext_main(Datum main_arg);
void
my_ext_main(Datum main_arg)
{
/* Install signal handlers BEFORE unblocking signals. */
pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
/* Optional: connect to a database. Requires
BGWORKER_BACKEND_DATABASE_CONNECTION in bgw_flags. */
BackgroundWorkerInitializeConnection("mydb", NULL, 0);
for (;;)
{
int rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
naptime_ms,
PG_WAIT_EXTENSION);
ResetLatch(MyLatch);
CHECK_FOR_INTERRUPTS();
if (ConfigReloadPending)
{
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
/* ... do work, possibly inside StartTransactionCommand() ... */
}
}
[verified-by-code source/src/test/modules/worker_spi/worker_spi.c:134-225]
Hard rules inside a worker
- Why these signal handlers?
die(source/src/backend/tcop/postgres.c:3023-3058) andSignalHandlerForConfigReload(source/src/backend/postmaster/interrupt.c:60-65) both implement the only async-signal-safe pattern: flip a flag (ProcDiePending/ConfigReloadPending), callSetLatch(MyLatch), return. The real work —AbortCurrentTransaction()for SIGTERM,ProcessConfigFile(PGC_SIGHUP)for SIGHUP — runs in the main loop body onceCHECK_FOR_INTERRUPTS()or the explicitConfigReloadPendingcheck observes the flag. A custom handler that callsproc_exit(0)directly would: (a) skip transaction abort and leak resowner / lock / snapshot state, (b) run signal-unsafepalloc/ LWLock /ereportcode, and (c) per the bgworker contract retire the worker's slot regardless ofbgw_restart_time. Always set a flag, set the latch, return. - Never
sleep()/usleep()— always wait onMyLatchwithWL_EXIT_ON_PM_DEATH, otherwise an orphaned worker survives the postmaster's death. - Connect to a DB only via
BackgroundWorkerInitializeConnection/BackgroundWorkerInitializeConnectionByOid— these set up locks, xact state, etc.flagsis currentlyBGWORKER_BYPASS_ALLOWCONNand/orBGWORKER_BYPASS_ROLELOGINCHECK. [verified-by-codesource/src/include/postmaster/bgworker.h:154-167] - Run SQL inside
StartTransactionCommand()/CommitTransactionCommand()pairs, or via SPI.
7. Querying / terminating dynamic workers
RegisterDynamicBackgroundWorker returns a BackgroundWorkerHandle*.
With it the launcher backend can:
GetBackgroundWorkerPid(handle, &pid)— non-blocking status.WaitForBackgroundWorkerStartup(handle, &pid)— block until started.WaitForBackgroundWorkerShutdown(handle)— block until exit.TerminateBackgroundWorker(handle)— SIGTERM, no restart.
If bgw_notify_pid is set to the launcher's PID, the launcher gets
SIGUSR1 on worker start/stop transitions — useful with the wait calls.
[verified-by-code source/src/include/postmaster/bgworker.h:128-137]
8. Layering hooks on _PG_init
Extensions installed via shared_preload_libraries,
session_preload_libraries, or local_preload_libraries get their
_PG_init called at the right time to install hooks
(postmaster-startup, per-backend-connect, or per-backend-connect-by-
unprivileged-user respectively). The canonical pattern: save the
previous hook, install yours, call the previous one inside yours so
chains compose.
The planner_hook callback prototype matches planner_hook_type exactly
— five parameters with ExplainState *es as the trailing argument:
[verified-by-code source/src/include/optimizer/planner.h:28-32]
static planner_hook_type prev_planner_hook = NULL;
static PlannedStmt *
my_planner(Query *parse, const char *query_string,
int cursorOptions, ParamListInfo boundParams,
ExplainState *es)
{
PlannedStmt *result;
if (prev_planner_hook)
result = prev_planner_hook(parse, query_string,
cursorOptions, boundParams, es);
else
result = standard_planner(parse, query_string,
cursorOptions, boundParams, es);
/* ... my modifications to result ... */
return result;
}
void
_PG_init(void)
{
prev_planner_hook = planner_hook;
planner_hook = my_planner;
/* ... other hooks, GUCs, RegisterBackgroundWorker ... */
}
Common hook variables to chain (all in their respective header):
ProcessUtility_hook, planner_hook, ExecutorStart_hook,
ExecutorRun_hook, ExecutorFinish_hook, ExecutorEnd_hook,
emit_log_hook, shmem_request_hook, shmem_startup_hook.
No _PG_fini: libraries never unload
PG's dynamic loader (dfmgr.c:295-299) dlsyms _PG_init from each
loaded library and calls it once. There is no symmetric _PG_fini —
PG never unloads a shared library for the life of the backend.
Implications:
- Once your hook is installed, it stays installed until the backend exits.
DROP EXTENSIONremoves the SQL-level catalog bindings (functions registered by the install script) but does NOT undo_PG_initand does NOT unload the.so. Any chained hook is still wired in.- On postmaster shutdown each backend exits and the OS unmaps the library; no per-process cleanup needed.
[verified-by-code source/src/backend/utils/fmgr/dfmgr.c:295-299]
9. Checklist
-
bgw_flagshas at minimumBGWORKER_SHMEM_ACCESS; addBGWORKER_BACKEND_DATABASE_CONNECTIONif the worker touches a DB. -
bgw_library_nameandbgw_function_nameare correct strings (no quotes, fit inMAXPGPATH/BGW_MAXLEN). - Main function is
pg_noreturn PGDLLEXPORT void f(Datum). - Signal handlers (
SIGHUP,SIGTERM) installed beforeBackgroundWorkerUnblockSignals(). - Main loop waits on
MyLatchwithWL_EXIT_ON_PM_DEATH. Nosleep(). -
CHECK_FOR_INTERRUPTS()somewhere in the loop body. - DB-connecting workers call
BackgroundWorkerInitializeConnection*once after unblocking signals. - Restart policy explicit (
BGW_NEVER_RESTARTor finite seconds). - Static registration in
_PG_initguarded byif (!process_shared_preload_libraries_in_progress) return;beforeRegisterBackgroundWorker. - Each chained hook saves the previous and invokes it (or the
standard_*default) before / after its own logic.
10. Useful greps
- All bgworker registrations:
grep -RIn 'RegisterBackgroundWorker\|RegisterDynamicBackgroundWorker' source/src source/contrib - All hook installations:
grep -RIn '_hook = ' source/contrib - BackgroundWorker struct usage examples:
source/src/test/modules/worker_spi/worker_spi.c
Open questions / [unverified]
[unverified]WhetherBGWORKER_INTERRUPTIBLEis recommended for long-running general-purpose workers — most contrib examples don't set it.[unverified]Exact behaviour ofbgw_notify_pidwhen the notify-target backend exits before the worker starts (likely: silently ignored).
Cross-references
.claude/skills/gucs-config/SKILL.md— custom GUCs in_PG_init; SIGHUP signal-handler reloads them..claude/skills/parallel-query/SKILL.md—BGWORKER_CLASS_PARALLELis NOT for extensions; this is the parallel-query worker side..claude/skills/extension-development/SKILL.md—.control, install SQL,shared_preload_libraries, PGXS vs meson..claude/skills/locking/SKILL.md— shmem hook + LWLock allocation for workers that need shared state..claude/skills/coding-style/SKILL.md—pg_noreturn,PGDLLEXPORT, backend C conventions.knowledge/idioms/bgworker-and-parallel.md— conceptual model.knowledge/docs-distilled/bgworker.md— SGML-distilled reference.knowledge/files/src/include/postmaster/bgworker.h.md— per-file doc for the public API.source/src/test/modules/worker_spi/— canonical in-tree example.