NAME
DBIO::Storage::Async::TransactionContext - Generic pinned-connection context for async transactions
VERSION
version 0.900004
DESCRIPTION
The transaction context handed to the coderef of "txn_do_async" in DBIO::Storage::Async. It pins every CRUD operation to the single connection that BEGIN/COMMIT/ROLLBACK ran on, routing them through the storage's shared _run_crud_pinned builder so SQL generation (INSERT post-processing, select_single first-row) is never duplicated.
This is the loop-agnostic default returned by "_txn_context_class" in DBIO::Storage::Async. It knows the pinned connection as txn_conn only -- the key "new" takes it under and the method that answers it -- so a transport backend that overrides "_txn_conn_accessor" in DBIO::Storage::Async to another name supplies a context class of its own through _txn_context_class as well: one whose new takes the connection under that name and that has a method of that name answering it, which is where core asks for it.
ATTRIBUTES
storage
The parent DBIO::Storage::Async instance.
txn_conn
The pinned connection handle for the duration of this transaction. The method core asks a context for its connection through, under the name "_txn_conn_accessor" in DBIO::Storage::Async answers.
pool
Shortcut to $self->storage->pool. It asks the storage each time, so it is the storage's pool as it stands now, not a record of where "txn_conn" came from: after the storage was disconnected it is another pool, and with a transport that builds its pool lazily, calling it builds one. The transaction does not go through it -- "txn_do_async" in DBIO::Storage::Async hands "txn_conn" back to the pool it acquired it from.
in_txn
True while this transaction context is active; false once the transaction has sent its COMMIT or ROLLBACK, or has ended without either (a failed BEGIN, say).
METHODS
new
my $ctx = DBIO::Storage::Async::TransactionContext->new(
storage => $storage,
txn_conn => $conn,
);
Creates a new transaction context. Requires storage and the pinned connection as txn_conn -- the name "_txn_conn_accessor" in DBIO::Storage::Async answers by default, and the only one this class takes: under any other key it dies with txn_conn required, which "txn_do_async" in DBIO::Storage::Async turns into the failure of the transaction.
_context_active
True while the transaction is live. A context-bound DBIO::ResultSet/DBIO::Row/DBIO::ResultSetColumn consults this before routing an *_async op onto the context (ADR 0045).
Unlike DBIO::Storage::Async::PipelineContext, the transaction context stays active while the callback's Future is pending -- its pinned connection is held from BEGIN through the terminal COMMIT/ROLLBACK path, so a context-bound resultset used inside the transaction's own ->then chain legitimately still runs on the pinned connection. The transaction scaffold invalidates it the moment it sends COMMIT or ROLLBACK -- a statement issued after that would run after the transaction, outside it -- and in any case before returning that connection to the pool. Once invalidated, direct context CRUD and nested txn_do_async calls refuse rather than touching a connection whose transaction has ended.
_query_async
Executes a query on the pinned transaction connection without releasing it back to the pool. Uses "_query_async_pinned" in DBIO::Storage::Async.
select_async
select_single_async
insert_async
update_async
delete_async
Same arguments and resolution shapes as the storage methods of the same names ("select_async" in DBIO::Storage::Async etc.), but every statement runs on the pinned "txn_conn", inside the transaction, through the storage's shared pinned CRUD builder. Each throws a DBIO::Exception once the context is no longer active or its ownership cannot be proven.
The context keeps every Future these methods return ("_track_statement"). A statement that fails fails the transaction -- also when nothing awaits its Future, and also when the coderef catches the failure and carries on ("txn_do_async" in DBIO::Storage::Async).
select
select_single
insert
update
delete
Blocking forms: call the *_async method of the same name on this context and wait for it with ->get, blocking the event loop.
sql_maker
debug
Delegate to the parent storage's sql_maker and debug.
pipeline
$txn_ctx->pipeline(sub {
my ($batch) = @_;
...
});
Runs a "pipeline" in DBIO::Storage::Async batch on this transaction's pinned "txn_conn", not on a connection of its own: the bracket (_pipeline_enter/_pipeline_sync/_pipeline_exit) and every statement issued through the batch context run inside the transaction, and the pipeline does not release the connection -- only the transaction boundary does. Throws a DBIO::Exception once this context is no longer active. On a transport that cannot pipeline it returns a failed Future (a DBIO::Exception naming the transport) before anything is sent on "txn_conn"; see "_pipeline_refusal" in DBIO::Storage::Async.
A batch whose _pipeline_exit dies leaves "txn_conn" in pipeline mode. The pipeline cannot hand the connection back itself -- it is the transaction's -- so it marks it through "_mark_txn_conn_unusable", and the transaction ends without waiting on it: no COMMIT, a best-effort ROLLBACK, and the connection is retired from the pool instead of released ("txn_do_async" in DBIO::Storage::Async).
The pipeline's Future is kept like a statement's ("_track_statement"): if it fails, the transaction fails. And like a statement's it is kept until it is ready when the transaction ends first -- the coderef died while the sync was outstanding, say. That Future is the sequence that leaves pipeline mode once the sync is answered; let go of early, it never ran. The statements inside the batch are the batch context's, not this one's; a failure the batch coderef drops is not seen here (see "_txn_commit_rolled_back" in DBIO::Storage::Async for the transport that can see it anyway).
txn_do_async
Refuses with a DBIO::Exception. A nested transaction would have to be a savepoint on "txn_conn", and the async layer has no savepoint seam; the only thing this could otherwise do is open an independent transaction on a second pool connection, which commits even when this one rolls back and deadlocks as soon as it waits on a lock this transaction holds (this transaction's COMMIT in turn waits on it).
When an independent transaction is what you want, ask for it by name:
$txn_ctx->storage->txn_do_async(sub { ... });
_mark_txn_conn_unusable
$txn_ctx->_mark_txn_conn_unusable;
Record that something run on "txn_conn" left it in a state no statement can rely on -- a pipeline whose _pipeline_exit died, so the connection is still in pipeline mode. "pipeline" calls it; the transaction scaffold reads it back through "_txn_conn_unusable". Nothing else changes: the context stays active until the transaction ends.
_txn_conn_unusable
True once "_mark_txn_conn_unusable" was called. The transaction scaffold in "txn_do_async" in DBIO::Storage::Async then sends no COMMIT and waits for no ROLLBACK on "txn_conn", and retires it from the pool instead of releasing it.
_txn_conn_unusable_error
The error "_mark_txn_conn_unusable" was given, the first one. A pipeline that failed on the transaction connection passes the error that ended its batch; the transaction then fails with it instead of the generic refusal. undef when none was given.
_set_txn_guard
$txn_ctx->_set_txn_guard({ pool => $pool, conn => $conn, done => 0, lease => $lease });
_txn_guard
The record "txn_do_async" in DBIO::Storage::Async keeps of what became of the transaction's connection: its pool, the connection, and done once it was released or retired. Every path that hands the connection back or cuts the transaction short asks it, so the connection goes back once -- a cancel and the finalizer, say, do not both retire it.
_track_statement
return $txn_ctx->_track_statement($future);
Keep $future -- the Future of a statement this context hands out -- until the transaction ends, and return it. "_query_async", the five *_async CRUD methods and "pipeline" hand out their Future through it; a context-bound DBIO::ResultSet/DBIO::Row routes through those methods and is covered with them.
Held strongly, so a Future nobody else keeps -- a statement issued and not awaited -- still reports how it ended. A synchronous throw from one of those methods is not tracked: nothing was handed out, and the caller already holds the error.
The holding ends with the statement's answer, not with the transaction. A transaction can end with a statement still in flight: its coderef died or its Future failed, and ROLLBACK went out at once; or its connection was left unusable and nothing is waited for on it. The context lists no statement from then on ("_txn_statements"), but each Future it kept is held until it is ready -- done, failed or cancelled -- through its own retain, where its class has one (Future has). Before, the context let go of them as it ended. A statement's Future is typically a ->then sequence that the Future it was made off holds only weakly; dropped by its one holder, it was freed, and Future warned lost a sequence Future when the answer came. A class without retain holds such a chain from its head (DBIO::Test::Future) or is settled from the start (DBIO::Future::Immediate), and needs none.
_txn_statements
my @futures = $txn_ctx->_txn_statements;
Every statement Future "_track_statement" kept, in the order they were handed out. Before it sends COMMIT, "txn_do_async" in DBIO::Storage::Async waits until all of them have settled; if one failed, the transaction is rolled back and fails with that statement's error. Empty once the transaction has ended -- also when a statement is still in flight then: its Future is kept until it is answered ("_track_statement"), but nothing waits on it any more and it is not listed.
_txn_pending_pipelines
my @futures = $txn_ctx->_txn_pending_pipelines;
The Futures of the pipelines "pipeline" ran on this transaction that are not ready yet, in the order they were started. Unlike "_txn_statements" it keeps answering after the transaction has ended. A pipeline's Future is ready once its sync is answered and the connection has left pipeline mode, which is the point from which a statement can be sent on "txn_conn" again. "txn_do_async" in DBIO::Storage::Async sends the ROLLBACK that follows a block that died only when none is left.
AUTHOR
DBIO & DBIx::Class Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.