# How pg_durable Initializes the Duroxide Runtime at Startup via Its Background Worker

> Discover how pg_durable initializes the Duroxide runtime at startup. This PostgreSQL extension utilizes a background worker to spin up Tokio and start the Duroxide state-machine engine.

- Repository: [Microsoft/pg_durable](https://github.com/microsoft/pg_durable)
- Tags: internals
- Published: 2026-06-08

---

**The pg_durable extension registers a PostgreSQL background worker in `_PG_init` that spins up a single-threaded Tokio runtime and, through the `initialize_duroxide_runtime` function in [`src/worker.rs`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs), constructs a PostgreSQL-backed store and starts the Duroxide state-machine engine.**

The `pg_durable` extension by Microsoft orchestrates the Duroxide runtime entirely through a dedicated PostgreSQL background worker. At startup, the worker bootstraps a management connection pool, validates extension metadata, and then launches the runtime inside an asynchronous Tokio context. Understanding this initialization path is essential for anyone operating or debugging the `microsoft/pg_durable` extension.

## Registering the Background Worker in `_PG_init`

In [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs), the PostgreSQL extension entry point `_PG_init` delegates worker registration to `worker::register_background_worker()`. This call instructs PostgreSQL to spawn the `pg_durable_worker` process once recovery completes.

```rust
#[pg_guard]
pub extern "C-unwind" fn _PG_init() {
    // … GUC definitions omitted …
    worker::register_background_worker();
}

```

This registration is defined at [`src/lib.rs:138‑139`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs#L138-L139) in the `microsoft/pg_durable` source tree.

## The Worker Entry Point: `duroxide_worker_main`

PostgreSQL starts the registered worker by invoking `duroxide_worker_main`, the C-ABI entry point located in [`src/worker.rs`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs). The function first attaches signal handlers for `SIGHUP` and `SIGTERM`, initializes the tracing subsystem, and then builds a **single-threaded Tokio runtime** using `tokio::runtime::Builder::new_current_thread()`.

```rust
#[pg_guard]
#[no_mangle]
pub extern "C-unwind" fn duroxide_worker_main(_arg: pg_sys::Datum) {
    BackgroundWorker::attach_signal_handlers(
        SignalWakeFlags::SIGHUP | SignalWakeFlags::SIGTERM,
    );

    init_tracing();               // capture all logs
    log!("pg_durable: duroxide background worker starting...");

    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .expect("failed to create tokio runtime");

    rt.block_on(async { run_duroxide_runtime().await });
    rt.shutdown_timeout(Duration::from_secs(5));
}

```

The runtime blocks on `run_duroxide_runtime().await` and enforces a five-second shutdown timeout. See [`src/worker.rs:56‑84`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L56-L84) for the full implementation.

## Inside `run_duroxide_runtime`: Management Pool and Extension Gating

Before the Duroxide engine can start, `run_duroxide_runtime` constructs a management `PgPool` for polling and epoch handling. It then gates on the existence of the `pg_durable` extension through `wait_for_extension_creation`. Only after the extension is confirmed present does control flow reach `initialize_duroxide_runtime`, where the actual runtime is created.

This intermediate layer ensures the worker never attempts to bootstrap Duroxide against a catalog that has not yet finished installing the extension.

## Core Bootstrap: `initialize_duroxide_runtime`

The `initialize_duroxide_runtime` async function in [`src/worker.rs`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs) performs the heavy lifting. It accepts a PostgreSQL connection string, a retry interval, and the management pool, and returns `Option<Arc<runtime::Runtime>>`.

```rust
async fn initialize_duroxide_runtime(
    pg_conn_str: &str,
    retry_interval: Duration,
    mgmt_pool: &sqlx::PgPool,
) -> Option<Arc<runtime::Runtime>> {
    std::env::set_var(
        "DUROXIDE_PG_POOL_MAX",
        get_max_duroxide_connections().to_string(),
    );

    let user_semaphore = Arc::new(tokio::sync::Semaphore::new(
        get_max_user_connections() as usize,
    ));

    loop {
        if is_shutdown_requested() { return None; }
        if !check_extension_exists(mgmt_pool).await { return None; }
        if !check_duroxide_schema_owned(mgmt_pool).await {
            tokio::time::sleep(retry_interval).await;
            continue;
        }

        if has_extension_owned_duroxide_objects(mgmt_pool).await {
            release_extension_owned_duroxide_objects(mgmt_pool).await.ok()?;
        }

        let store = Arc::new(
            PostgresProvider::new_with_config(worker_provider_config(pg_conn_str))
                .await
                .ok()?,
        );

        let activities = create_activity_registry(
            Arc::new(mgmt_pool.clone()), user_semaphore.clone(),
        );
        let orchestrations = create_orchestration_registry();

        let runtime = runtime::Runtime::start_with_store(
            store, activities, orchestrations,
        )
        .await;

        log!("pg_durable: duroxide runtime started");
        return Some(runtime);
    }
}

```

The function spans [`src/worker.rs:5‑86`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L5-L86) in the repository.

### Configure the Duroxide Environment and User Semaphore

First, the function synchronizes PostgreSQL GUCs with Duroxide’s environment. It sets `DUROXIDE_PG_POOL_MAX` from `pg_durable.max_duroxide_connections`—the values are read through GUC wrappers defined in [`src/types.rs`](https://github.com/microsoft/pg_durable/blob/main/src/types.rs)—and allocates a `tokio::sync::Semaphore` sized to `pg_durable.max_user_connections`.

```rust
std::env::set_var(
    "DUROXIDE_PG_POOL_MAX",
    get_max_duroxide_connections().to_string(),
);

let user_semaphore = Arc::new(tokio::sync::Semaphore::new(
    get_max_user_connections() as usize,
));

```

These steps are implemented at [`src/worker.rs:16‑25`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L16-L25).

### Validate Extension State and Schema Ownership

The function enters a retry loop that checks three conditions in order:

- `is_shutdown_requested()` — abort immediately if the worker received `SIGTERM`.
- `check_extension_exists(mgmt_pool)` — verify the extension is still installed.
- `check_duroxide_schema_owned(mgmt_pool)` — confirm the `duroxide` schema belongs to the extension.

If the schema check fails, the worker sleeps for the configured `retry_interval` and tries again. This logic appears at [`src/worker.rs:26‑44`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L26-L44).

### Release Legacy Extension-Owned Objects

On upgrade paths from early versions, lingering extension-owned objects must be released before the new runtime takes over. The worker detects them with `has_extension_owned_duroxide_objects` and cleans them up via `release_extension_owned_duroxide_objects`.

```rust
if has_extension_owned_duroxide_objects(mgmt_pool).await {
    release_extension_owned_duroxide_objects(mgmt_pool).await.ok()?;
}

```

See [`src/worker.rs:46‑61`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L46-L61).

### Create the Duroxide PostgreSQL Provider Store

With the environment validated, the worker instantiates the **Duroxide store**. It calls `PostgresProvider::new_with_config`, supplying a connection string from `postgres_connection_string()` and the provider configuration from `worker_provider_config`.

```rust
let store = Arc::new(
    PostgresProvider::new_with_config(worker_provider_config(pg_conn_str))
        .await
        .ok()?,
);

```

This store is the persistence layer for the Duroxide runtime. The corresponding code is at [`src/worker.rs:63‑70`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L63-L70).

### Build Activity and Orchestration Registries

Next, the worker constructs the callback registries that feed the runtime. `create_activity_registry` receives a clone of the management pool and the user semaphore, while `create_orchestration_registry` requires no external state.

```rust
let activities = create_activity_registry(
    Arc::new(mgmt_pool.clone()), user_semaphore.clone(),
);
let orchestrations = create_orchestration_registry();

```

These registries are defined in [`src/registry.rs`](https://github.com/microsoft/pg_durable/blob/main/src/registry.rs) and invoked at [`src/worker.rs:78‑81`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L78-L81).

### Launch the Duroxide Runtime

Finally, the worker passes the store and registries into `runtime::Runtime::start_with_store`. This call spawns the Duroxide state-machine engine and returns an `Arc<Runtime>` that the worker retains for its main event loop.

```rust
let runtime = runtime::Runtime::start_with_store(
    store, activities, orchestrations,
)
.await;

log!("pg_durable: duroxide runtime started");
return Some(runtime);

```

This culminating step is located at [`src/worker.rs:83‑88`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs#L83-L88).

## Retry Logic and Graceful Shutdown

If initialization succeeds, the worker enters `run_until_extension_dropped_or_shutdown` to monitor epoch sentinels and the extension catalog. If any step inside the initialization loop fails—for example, if the extension is dropped mid-flight—the worker retries after a short back-off. A concurrent shutdown request, signaled by `SIGTERM`, sets the internal shutdown flag and causes `is_shutdown_requested()` to return `true`, breaking the loop and letting the Tokio runtime drain within its five-second timeout. This makes the `pg_durable` background worker resilient to transient catalog changes without risking orphan runtime processes.

## Summary

- **`_PG_init`** in [`src/lib.rs`](https://github.com/microsoft/pg_durable/blob/main/src/lib.rs) registers the `pg_durable_worker` background worker via `worker::register_background_worker()`.
- **PostgreSQL** spawns the worker after recovery, entering `duroxide_worker_main` in [`src/worker.rs`](https://github.com/microsoft/pg_durable/blob/main/src/worker.rs).
- The worker attaches signal handlers, starts tracing, and creates a **single-threaded Tokio runtime** that blocks on `run_duroxide_runtime`.
- `run_duroxide_runtime` builds a management pool, waits for the extension to appear, and calls **`initialize_duroxide_runtime`**.
- Inside `initialize_duroxide_runtime`, the worker sets environment variables, allocates a user semaphore, validates extension ownership, cleans up legacy objects, creates a `PostgresProvider` store, builds registries, and finally starts the Duroxide `Runtime`.
- The worker then monitors epoch sentinels or extension drops through `run_until_extension_dropped_or_shutdown`, handling graceful shutdown or recreation.

## Frequently Asked Questions

### What triggers the pg_durable background worker to start?

PostgreSQL invokes `_PG_init` when the shared library is loaded. This function registers the background worker, and PostgreSQL later launches it automatically after recovery completes. The worker entry point `duroxide_worker_main` then begins the Duroxide initialization sequence.

### Why does pg_durable use a single-threaded Tokio runtime?

The worker uses `tokio::runtime::Builder::new_current_thread()` to create a single-threaded runtime because the background worker process runs in its own operating-system process. A current-thread reactor keeps the concurrency model simple while still allowing asynchronous I/O through the SQLx connection pools and the Duroxide engine.

### What happens if the pg_durable extension is dropped while the worker is running?

If the extension is dropped, the helper `check_extension_exists` eventually returns `false`, which causes `initialize_duroxide_runtime` to return `None` or abort the loop. The worker then exits its main event loop and shuts down gracefully, preventing a stale Duroxide runtime from continuing to operate against a dropped catalog.

### How does the worker handle graceful shutdown?

The worker installs a `SIGTERM` handler during startup. When PostgreSQL sends a termination signal, `is_shutdown_requested()` flips to `true`, breaking any retry loops and allowing the Tokio runtime to shut down with a five-second timeout. This ensures in-flight Duroxide tasks have a brief window to finish before the process exits.