How pgrust Implements Transaction and Concurrency Control: A Deep Dive into the Rust PostgreSQL Clone

pgrust implements PostgreSQL's transaction and concurrency control by mirroring the upstream C-layer mechanics through safe Rust abstractions, using callback-driven seams for transaction lifecycle management, MVCC snapshot handling, and resource-owner hierarchies.

The pgrust project is a Rust reimplementation of PostgreSQL's core database engine. Its transaction and concurrency control subsystem faithfully reproduces the original server's ACID guarantees and MVCC semantics while leveraging Rust's memory safety guarantees to eliminate entire classes of memory-related bugs present in the C implementation.

Transaction Lifecycle and Sub-Transaction Management

The pgrust engine handles transaction boundaries by registering callbacks that hook into PostgreSQL's extension points. In crates/pl/plpgsql/src/handler/src/lib.rs, the system invokes RegisterXactCallback and RegisterSubXactCallback during PL/pgSQL extension initialization to establish Rust-side handlers for transaction events.

These callbacks drive the seam architecture—thin abstraction layers that expose C entry-points as safe Rust functions. The crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs file defines the critical seams for sub-transaction control, including begin_internal_subtransaction, release_current_subtransaction, and rollback_and_release_current_subtransaction. This design allows the Rust executor to invoke PostgreSQL-compatible transaction logic while maintaining type safety and preventing undefined behavior.

Snapshot and MVCC Management

Multiversion Concurrency Control (MVCC) in pgrust relies on snapshot management that mirrors PostgreSQL's snapmgr.c implementation. The crates/backend/utils/time/snapmgr/src/lib.rs file provides the AtEOXact_Snapshot function, which clears per-transaction snapshot state at transaction boundaries and respects the IsolationUsesXactSnapshot flag.

The snapshot manager tracks exportable snapshots, read-only transaction flags, and isolation levels through a safe Rust interface. The crates/backend/utils/time/snapmgr_seams/src/lib.rs file wires these snapshot operations to the transaction callback system, ensuring that snapshot cleanup occurs deterministically when transactions commit or abort.

Locking and Resource-Owner Handling

Resource management in pgrust uses a hierarchy of ResourceOwner objects that mirror PostgreSQL's resource owner chain. The crates/backend/utils/mmgr/portalmem/src/lib.rs file implements the creation and linking of resource owners, establishing parent/child relationships for nested sub-transactions.

When sub-transactions commit or abort, the system invokes functions analogous to AtSubCommit_Portals and AtSubAbort_Portals through the seam layer in crates/backend/utils/mmgr/portalmem_seams/src/lib.rs. This ensures that locks, buffers, and portal resources release deterministically, preventing resource leaks that could lead to deadlocks or memory exhaustion.

Client-Side Transaction Status

The libpq-compatible interface exposes transaction state through the PQtransactionStatus API. The crates/interfaces/libpq/fe/src/client.rs and crates/interfaces/libpq/fe/src/result.rs files implement the client-side transaction status reporting, returning states such as Idle, InTransaction, and InError.

This allows client applications to query the current connection state using standard PostgreSQL semantics, receiving accurate status information even when transactions nest or encounter error conditions.

Error Handling and Sub-Transaction Abort

Error recovery in pgrust uses Rust's catch_unwind mechanism to handle panics during statement execution. The executor in crates/pl/plpgsql/src/exec/src/lib.rs wraps sub-transaction bodies in catch_unwind blocks, explicitly calling rollback_and_release_current_subtransaction when errors occur.

This approach mirrors the C-level AbortSubTransaction and AtEOSubXact_SPI functions while providing memory safety guarantees. The rollback operation cleans up the ResourceOwner hierarchy and releases associated snapshots before propagating the error to the caller.

How the Transaction Flow Works

The pgrust transaction implementation follows a precise lifecycle that integrates all subsystems:

  1. Startup: When the PL/pgSQL extension loads, handler/src/lib.rs registers xact and sub-xact callbacks with PostgreSQL's callback system.

  2. Begin: On BEGIN or implicit block entry, the callback invokes begin_internal_subtransaction, which creates a new ResourceOwner and initializes a fresh snapshot via snapmgr::AtEOXact_Snapshot(false, false).

  3. Execute: Statements run inside the Rust executor. If execution panics, the catch_unwind block triggers rollback_and_release_current_subtransaction, mirroring AbortSubTransaction.

  4. Commit/Abort: At block end, the system calls either release_current_subtransaction or rollback_and_release_current_subtransaction, which:

    • Invokes AtEOXact_Snapshot(is_commit, reset_xmin) to clean up MVCC state
    • Walks the ResourceOwner chain to release locks and buffers
    • Updates the transaction status returned by client::transaction_status()
  5. Nesting: Nested BEGIN … EXCEPTION … END blocks trigger this sequence recursively, each maintaining independent ResourceOwner and snapshot instances, exactly as PostgreSQL's SubTransactionId hierarchy operates.

Practical Code Examples

The following example demonstrates sub-transaction handling with proper error recovery:

// Start a sub-transaction inside PL/pgSQL
let _ = ::plpgsql_exec_seams::begin_internal_subtransaction::call()?;

// Run the body; any panic will be caught and turned into a rollback
let result = std::panic::catch_unwind(|| {
    // ... execute statements ...
});

match result {
    Ok(Ok(())) => {
        // Commit the sub-transaction
        ::plpgsql_exec_seams::release_current_subtransaction::call()?;
    }
    Err(_) | Ok(Err(_)) => {
        // Abort the sub-transaction
        ::plpgsql_exec_seams::rollback_and_release_current_subtransaction::call()?;
    }
}

To query transaction status from a client application:

// Query the current transaction status from a libpq client
let conn = PgConnection::connect(conninfo)?;
match conn.transaction_status() {
    PgTransactionStatusType::Idle      => println!("no transaction"),
    PgTransactionStatusType::InTrans   => println!("active transaction"),
    PgTransactionStatusType::InError   => println!("transaction aborted"),
}

Summary

  • pgrust implements PostgreSQL's transaction semantics through a seam-based architecture that wraps C-level callbacks in safe Rust interfaces.
  • Transaction lifecycle management uses RegisterXactCallback and RegisterSubXactCallback in crates/pl/plpgsql/src/handler/src/lib.rs to coordinate begin, commit, and abort operations.
  • MVCC snapshot isolation relies on AtEOXact_Snapshot in crates/backend/utils/time/snapmgr/src/lib.rs to manage transaction visibility and cleanup.
  • Resource owners in crates/backend/utils/mmgr/portalmem/src/lib.rs maintain hierarchical lock and buffer management for nested sub-transactions.
  • Error recovery combines Rust's catch_unwind with explicit rollback_and_release_current_subtransaction calls to ensure safe abort semantics.
  • Client compatibility is maintained through the PQtransactionStatus API implemented in crates/interfaces/libpq/fe/src/client.rs.

Frequently Asked Questions

How does pgrust handle sub-transaction rollbacks?

When a sub-transaction encounters an error, pgrust uses Rust's catch_unwind mechanism to intercept panics, then explicitly calls rollback_and_release_current_subtransaction from crates/pl/plpgsql/src/plpgsql_exec_seams/src/lib.rs. This function invokes the snapshot cleanup in snapmgr::AtEOXact_Snapshot and walks the ResourceOwner chain to release locks and buffers, mirroring PostgreSQL's AbortSubTransaction behavior while maintaining memory safety.

What mechanism ensures MVCC snapshot isolation?

The snapshot manager in crates/backend/utils/time/snapmgr/src/lib.rs implements the AtEOXact_Snapshot hook that PostgreSQL uses to manage transaction visibility. It tracks exportable snapshots, isolation levels, and read-only flags, cleaning up per-transaction snapshot state at commit or abort through the seam layer in snapmgr_seams/src/lib.rs. This ensures each transaction sees a consistent view of the database according to its isolation level.

How are resource owners managed in nested transactions?

pgrust creates a new ResourceOwner for each sub-transaction in crates/backend/utils/mmgr/portalmem/src/lib.rs, linking them in a parent/child hierarchy that mirrors PostgreSQL's SubTransactionId chain. When sub-transactions commit or abort, the system invokes AtSubCommit_Portals or AtSubAbort_Portals equivalents through the portalmem seam layer, ensuring deterministic cleanup of locks, buffers, and temporary objects at each nesting level.

How does the libpq interface expose transaction status?

The client-side API in crates/interfaces/libpq/fe/src/client.rs implements PQtransactionStatus, returning enum variants Idle, InTransaction, or InError based on the current connection state. This status updates automatically as the transaction manager processes commits, aborts, or error conditions, providing real-time visibility into the transaction state for client applications using the standard PostgreSQL wire protocol.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →