agentsclimarketplace

Bgworker and extensions

Skill matejformanek/postgres-claude/.claude/skills/bgworker-and-extensions

Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.

Install
npx -y skills add matejformanek/postgres-claude --skill bgworker-and-extensions

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Register a PostgreSQL background worker or layer extension hooks on _PG_init — covers RegisterBackgroundWorker (static at shared_preload_libraries time) vs RegisterDynamicBackgroundWorker (runtime), the BackgroundWorker struct (bgw_flags BGWORKER_SHMEM_ACCESS / BGWORKER_BACKEND_DATABASE_CONNECTION, bgw_start_time, bgw_restart_time, bgw_main_arg, bgw_notify_pid), BackgroundWorkerInitializeConnection, the signal handler skeleton (SignalHandlerForConfigReload + die), the WaitLatch idiom with WL_EXIT_ON_PM_DEATH, and the GetBackgroundWorkerPid / WaitForBackgroundWorkerStartup / TerminateBackgroundWorker lifecycle calls. Use whenever a PG patch or extension registers a bgworker, writes a worker_main function, debugs a worker that fails to start or restart, layers ProcessUtility_hook / planner_hook / ExecutorStart_hook on _PG_init, or coordinates bgw_notify_pid signaling. Skip for Celery / Sidekiq / RQ / Resque / Kafka-consumer worker pools, AWS Lambda / Cloud Run background jobs, Cron / systemd timers, OS-level daemons, and Kubernetes Jobs / CronJobs.

SKILL.md

13.3 KB, as published. Nobody here has run it

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

APIWhere to callRestart?
RegisterBackgroundWorker(&w)_PG_init, only when process_shared_preload_libraries_in_progressYes per bgw_restart_time
RegisterDynamicBackgroundWorker(&w, &handle)Any backend at runtimeYes 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]

FlagMeaning
BGWORKER_SHMEM_ACCESSRequired for any worker that touches shared buffers / LWLocks.
BGWORKER_BACKEND_DATABASE_CONNECTIONWorker may call BackgroundWorkerInitializeConnection*. Requires SHMEM_ACCESS.
BGWORKER_INTERRUPTIBLEWorker exits if its DB is CREATE/ALTER/DROP'd. Requires the two above.
BGWORKER_CLASS_PARALLELDon'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_timeWorker exitRestarted?
BGW_NEVER_RESTART (-1)anyNo — slot is freed.
N secondsproc_exit(0) (return 0)No — clean exit retires the slot regardless of bgw_restart_time.
N secondsproc_exit(1) (return 1)Yes, after N seconds.
N secondscrash / signalYes, 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) and SignalHandlerForConfigReload (source/src/backend/postmaster/interrupt.c:60-65) both implement the only async-signal-safe pattern: flip a flag (ProcDiePending / ConfigReloadPending), call SetLatch(MyLatch), return. The real work — AbortCurrentTransaction() for SIGTERM, ProcessConfigFile(PGC_SIGHUP) for SIGHUP — runs in the main loop body once CHECK_FOR_INTERRUPTS() or the explicit ConfigReloadPending check observes the flag. A custom handler that calls proc_exit(0) directly would: (a) skip transaction abort and leak resowner / lock / snapshot state, (b) run signal-unsafe palloc / LWLock / ereport code, and (c) per the bgworker contract retire the worker's slot regardless of bgw_restart_time. Always set a flag, set the latch, return.
  • Never sleep() / usleep() — always wait on MyLatch with WL_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. flags is currently BGWORKER_BYPASS_ALLOWCONN and/or BGWORKER_BYPASS_ROLELOGINCHECK. [verified-by-code source/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 EXTENSION removes the SQL-level catalog bindings (functions registered by the install script) but does NOT undo _PG_init and 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_flags has at minimum BGWORKER_SHMEM_ACCESS; add BGWORKER_BACKEND_DATABASE_CONNECTION if the worker touches a DB.
  • bgw_library_name and bgw_function_name are correct strings (no quotes, fit in MAXPGPATH / BGW_MAXLEN).
  • Main function is pg_noreturn PGDLLEXPORT void f(Datum).
  • Signal handlers (SIGHUP, SIGTERM) installed before BackgroundWorkerUnblockSignals().
  • Main loop waits on MyLatch with WL_EXIT_ON_PM_DEATH. No sleep().
  • CHECK_FOR_INTERRUPTS() somewhere in the loop body.
  • DB-connecting workers call BackgroundWorkerInitializeConnection* once after unblocking signals.
  • Restart policy explicit (BGW_NEVER_RESTART or finite seconds).
  • Static registration in _PG_init guarded by if (!process_shared_preload_libraries_in_progress) return; before RegisterBackgroundWorker.
  • 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] Whether BGWORKER_INTERRUPTIBLE is recommended for long-running general-purpose workers — most contrib examples don't set it.
  • [unverified] Exact behaviour of bgw_notify_pid when 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.mdBGWORKER_CLASS_PARALLEL is 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.mdpg_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.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.