DBIx-Loop

 view release on metacpan or  search on metacpan

examples/ioasync.pl  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;

# DBIx::Loop on IO::Async: three CPU-heavy queries overlap on the worker pool
# while a loop timer keeps ticking - the loop is never blocked.
#
#   perl examples/ioasync.pl

use File::Temp ();
use DBIx::Loop;
use DBIx::Loop::Loop::IOAsync;

my $dir = File::Temp->newdir;
my $ad  = DBIx::Loop::Loop::IOAsync->new;
my $db  = DBIx::Loop->connect(

lib/DBIx/Loop.pm  view on Meta::CPAN


Either way the result is the same shape: C<query> resolves to
C<< { rows => [ [...], ... ], columns => [ ... ] } >> (arrayref rows - they
benchmarked ~4x faster to build than hashrefs) and C<do> to
C<< { rows_affected => $n, insert_id => $id } >> (insert_id best-effort via
C<last_insert_id>).

A note on B<SQLite and more than one worker>: each worker is another process
with its own connection, and SQLite takes one writer at a time for the whole
file. Reads run concurrently; writes serialise, and a writer that keeps losing
the race gets C<database is locked> back once DBD::SQLite's busy timeout (30
seconds by default) is spent - which on a loaded machine a deep burst of
concurrent writes can genuinely do. For write-heavy SQLite either use
C<workers =E<gt> 1>, which costs no responsiveness (the queue still keeps the
loop free) and removes the contention outright, or raise
C<sqlite_busy_timeout>. Client/server engines have no such limit.

=head1 CONSTRUCTORS

=head2 connect

t/04-native-pg.t  view on Meta::CPAN

}

# ---- a genuinely async wait: the loop is free while Pg sleeps -------------------
{
    my $ticks = 0;
    my $timer_done = 0;
    $ad->timer(0.05, sub { $ticks++; $timer_done = 1 });
    my $f = $db->query("SELECT pg_sleep(0.3), 42 AS answer");
    $ad->await($f);
    is(($f->get)[0]{rows}[0][1], 42, 'slow query resolves');
    ok($timer_done, 'a loop timer fired while the query was in flight (loop not blocked)');
}

# ---- errors fail only their future ----------------------------------------------
{
    my $bad = $db->query("SELECT * FROM table_that_does_not_exist");
    my $good = $db->query("SELECT 7 AS ok");
    $ad->await($bad); $ad->await($good);
    ok($bad->is_failed, 'bad SQL -> failed future');
    like($bad->failure, qr/does not exist|relation/i, 'failure carries the Pg error');
    is(($good->get)[0]{rows}[0][0], 7, 'the queued query after it still succeeds');

t/lib/AdapterConformance.pm  view on Meta::CPAN

        my $res = ($q->get)[0];
        is(scalar @{ $res->{rows} }, 4, 'four rows back');
        is_deeply($res->{columns}, ['id', 'v'], 'columns named');

        # -- concurrency: more in flight than workers -------------------------
        my @f = map { $db->query("SELECT COUNT(*) FROM t") } 1 .. 5;
        $ad->await($_) for @f;
        is(scalar(grep { $_->is_done && ($_->get)[0]{rows}[0][0] == 4 } @f), 5,
            '5 concurrent queries across 2 workers all resolve');

        # -- a timer fires while a query is in flight (loop not blocked) ------
        SKIP: {
            skip 'adapter has no timer', 1 unless $ad->can('timer');
            my $fired = 0;
            $ad->timer(0.02, sub { $fired = 1 });
            my $slow = $db->query(
                "WITH RECURSIVE c(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM c WHERE x < 200000) SELECT COUNT(*) FROM c");
            $ad->await($slow);
            # give the timer a chance if the query won the race
            $ad->await(do { my $f = DBIx::Loop::Future->new;
                            $ad->timer(0.03, sub { $f->done(1) }); $f });

t/lib/BackendParity.pm  view on Meta::CPAN

            "SELECT id, name, n, id+1, id+2, id+3, id+4, id+5, id+6, id+7 "
          . "FROM p WHERE id = 1"));
        is(scalar @{ $wide->{rows}[0] }, 10, 'ten columns cross intact');

        # -- a larger result set --------------------------------------------------
        #
        # The seed writes go one at a time, on purpose. Two pool workers are
        # two processes writing one SQLite file and SQLite serialises writers,
        # so fired off as a 500-deep burst they fight for the write lock; on a
        # loaded machine a starved writer sits out its whole busy timeout and
        # comes back "database is locked". Nothing here ever looked at the
        # write futures, so that arrived as a silent short row count (a CPAN
        # smoker reported 497). Nothing about crossing 500 rows needs
        # concurrent writers - the concurrent-work path is what the read burst
        # below, AdapterConformance and t/02-pool.t are for - and a failed
        # write is now a named failure rather than a missing row.
        {
            my ($written, $err) = (0, undef);
            for my $i (100 .. 599) {
                my $f = $db->do("INSERT INTO p (id, name, n) VALUES (?,?,?)",
                                $i, "row$i", $i);



( run in 1.282 second using v1.01-cache-2.11-cpan-800906f7e73 )