DBIO
view release on metacpan or search on metacpan
lib/DBIO/Storage/Async/PipelineContext.pm view on Meta::CPAN
sub _track_statement {
my ($self, $future) = @_;
return $future unless Scalar::Util::blessed($future)
&& $future->can('then') && $future->can('is_ready')
&& !$future->is_ready;
my $gate = ref($future)->new;
$self->{storage}->_hold_derived($future->then(
sub { $gate->done(@_) unless $gate->is_ready; return },
sub { $gate->fail(@_) unless $gate->is_ready; return },
));
push @{ $self->{_statements} }, $gate;
return $gate;
}
# Every kept statement Future, answered or not, in the order handed out.
sub _statements { return @{ $_[0]->{_statements} || [] } }
# Every statement still open fails with the error that ended the batch.
sub _fail_open_statements {
my ($self, $error) = @_;
for my $gate (grep { !$_->is_ready } $self->_statements) {
$gate->fail($error);
}
return;
}
# CRUD inside the batch MUST run on the connection the scaffold put into
# pipeline mode, not a fresh pooled one -- that is the whole point of the
# bracket. Each method routes through the storage's shared CRUD builder with
# the bracketed connection, so SQL generation (insert post-processing,
# select_single first-row) is not duplicated. Forwarding to
# $storage->select_async etc. would acquire a separate pool connection and run
# the query OUTSIDE the pipeline.
sub select_async {
my $self = shift;
$self->_assert_context_active;
return $self->_track_statement(
$self->{storage}->_run_crud_pinned('select', $self->{pipeline_conn}, @_)
);
}
sub select_single_async {
my $self = shift;
$self->_assert_context_active;
return $self->_track_statement(
$self->{storage}->_run_crud_pinned('select_single', $self->{pipeline_conn}, @_)
);
}
sub insert_async {
my $self = shift;
$self->_assert_context_active;
return $self->_track_statement(
$self->{storage}->_run_crud_pinned('insert', $self->{pipeline_conn}, @_)
);
}
sub update_async {
my $self = shift;
$self->_assert_context_active;
return $self->_track_statement(
$self->{storage}->_run_crud_pinned('update', $self->{pipeline_conn}, @_)
);
}
sub delete_async {
my $self = shift;
$self->_assert_context_active;
return $self->_track_statement(
$self->{storage}->_run_crud_pinned('delete', $self->{pipeline_conn}, @_)
);
}
# No blocking counterparts. The sync form of every other DBIO async surface is
# "call the *_async twin and ->get it" -- which cannot work here: a batched
# statement's result arrives at _pipeline_sync, and the sync only runs once the
# batch coderef has returned. ->get inside the batch would therefore wait for a
# result whose delivery is waiting on the caller it is blocking. Refusing with
# the reason beats hanging the process (and beats "Can't locate object method",
# which reads like the context is simply incomplete).
sub select { $_[0]->_no_blocking('select') }
sub select_single { $_[0]->_no_blocking('select_single') }
sub insert { $_[0]->_no_blocking('insert') }
sub update { $_[0]->_no_blocking('update') }
sub delete { $_[0]->_no_blocking('delete') }
sub _no_blocking {
my ($self, $op) = @_;
croak "Blocking ->$op is not available inside a pipeline batch: a batched "
. "statement resolves at the pipeline sync, which runs after the batch "
. "coderef returns -- blocking on it here would deadlock. Call "
. "->${op}_async and return an aggregate Future from the batch.";
}
# Delegations
sub sql_maker { my $self = shift; $self->{storage}->sql_maker(@_) }
sub debug { my $self = shift; $self->{storage}->debug(@_) }
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
DBIO::Storage::Async::PipelineContext - Generic pinned-connection context for async pipeline batches
=head1 VERSION
version 0.900005
=head1 DESCRIPTION
The batch context handed to the coderef of
L<DBIO::Storage::Async/pipeline>. Pipeline mode is B<per-connection> state:
a statement is only batched if it is sent on the very connection the
scaffold put into pipeline mode. This context is what makes that structural
instead of advisory -- it carries the bracketed connection and routes every
CRUD call through the storage's shared C<_run_crud_pinned> builder onto it,
exactly as L<DBIO::Storage::Async::TransactionContext> does for the pinned
transaction connection (dbio-async ADR 0002 / ADR 0003).
Without it, an C<*_async> call inside the batch coderef would go through the
pooled path and take a B<different> connection out of the pool -- running
outside the bracket, unbatched, and at C<< pool_size => 1 >> only after the
pipeline has released. The statements would still execute; the round-trip
economy the batch was written for would silently not happen.
The context is only valid for the synchronous run of the batch coderef: once
the coderef returns, the scaffold invalidates the context, syncs, exits
pipeline mode and releases the connection. A query issued on the context past
that point throws a L<DBIO::Exception> (C<pipeline context is no longer
active>) instead of reaching a connection that is back in the pool.
=head1 ATTRIBUTES
=head2 storage
The parent L<DBIO::Storage::Async> instance.
( run in 0.620 second using v1.01-cache-2.11-cpan-145b269f2f3 )