EV-cares

 view release on metacpan or  search on metacpan

lib/EV/cares.pm  view on Meta::CPAN

    for my $node (@unique) {
        $self->getaddrinfo($node, $service, $hints, sub {
            my ($status, @addrs) = @_;
            $res{$node} = { status => $status, addrs => \@addrs };
            $cb->(\%res) if --$pending == 0;
        });
    }
    return;
}

# is_busy: true iff there are pending queries on this resolver.  Cheap
# wrapper for the most common active_queries comparison.
sub is_busy { $_[0]->active_queries > 0 }

# wait_idle($timeout_seconds): pump the EV loop until either all of this
# resolver's pending queries complete or the timeout elapses.  Returns
# true if the channel drained, false on timeout.  Useful in mostly-
# synchronous scripts that want to ensure callbacks have run before
# proceeding.  Returns immediately if the resolver is already idle.
#
# Picks up a custom EV::Loop passed to new(loop => $loop): the timer and
# the run() call are dispatched on the same loop the resolver's watchers
# are armed on.  Without this, custom-loop resolvers would hang because

lib/EV/cares.pm  view on Meta::CPAN

=head2 active_queries

    my $n = $r->active_queries;

Returns the number of outstanding queries.  Remains callable after
C<destroy>; returns C<0> in that case (during interpreter global
destruction the count may reflect whatever was pending, since
C<ares_destroy> is intentionally skipped on the global-destruction
path).

=head2 is_busy

    if ($r->is_busy) { ... }

Convenience wrapper for C<< $r->active_queries > 0 >>.

=head2 wait_idle

    my $drained = $r->wait_idle;          # default 30s timeout
    my $drained = $r->wait_idle($seconds);

Pumps the EV loop until every pending query on this resolver has fired
its callback, or until the timeout elapses.  Returns true if the

t/17_wait_idle.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use EV;
use EV::cares qw(:all);

# --- is_busy: idle resolver ---
{
    my $r = EV::cares->new;
    ok(!$r->is_busy, 'is_busy false on fresh resolver');
    is($r->active_queries, 0, 'active_queries matches');
}

# --- is_busy: with pending query ---
{
    my $r = EV::cares->new(timeout => 5, tries => 1);
    $r->resolve('unlikely-host-xyz.invalid', sub { });
    ok($r->is_busy, 'is_busy true while query pending');
    $r->cancel;
    # drain the cancellation callback
    EV::run(EV::RUN_NOWAIT) for 1..3;
    ok(!$r->is_busy, 'is_busy false after cancel drains');
}

# --- wait_idle: already idle returns immediately ---
{
    my $r = EV::cares->new;
    my $t0 = EV::time;
    my $drained = $r->wait_idle(5);
    ok($drained, 'wait_idle on idle resolver returns true');
    cmp_ok(EV::time - $t0, '<', 0.1, 'wait_idle on idle is immediate');
}

# --- wait_idle: drains pending queries (file lookup, fast) ---
{
    my $r = EV::cares->new(lookups => 'f');
    my @completed;
    for (1..3) {
        $r->resolve('localhost', sub { push @completed, $_[0] });
    }
    ok($r->is_busy || @completed == 3,
        'queries queued (or completed synchronously)');
    my $drained = $r->wait_idle(5);
    ok($drained, 'wait_idle returns true when channel drains');
    is(scalar @completed, 3, 'all 3 callbacks fired');
    ok(!$r->is_busy, 'resolver is idle after wait_idle');
}

# --- wait_idle: timeout when a query is genuinely in-flight ---
# 192.0.2.1 is RFC 5737 TEST-NET-1 — a guaranteed black-hole address that
# routes nowhere on the public Internet.  c-ares will sit on it until its
# own timeout fires.  If the platform refuses outbound traffic to TEST-NET
# instantly (some hardened firewalls), the query may complete fast — skip
# the timing assertions in that case.
{
    my $r = EV::cares->new(timeout => 10, tries => 1);
    $r->set_servers('192.0.2.1');
    my $done;
    $r->resolve('unlikely-name.invalid', sub { $done = 1 });
    my $t0 = EV::time;
    my $drained = $r->wait_idle(1);
    my $elapsed = EV::time - $t0;
    SKIP: {
        # If the query drained within the 1s window, the OS rejected the
        # TEST-NET packet immediately (no route / hardened firewall) rather
        # than black-holing it, so the timeout path can't be exercised.
        # This must be detected AFTER wait_idle: a synchronous is_busy check
        # before the loop runs still shows the query as in-flight.
        skip 'TEST-NET query did not block on this platform', 2 if $drained;
        ok(!$drained, 'wait_idle returns false when timeout elapses');
        cmp_ok($elapsed, '>=', 0.9, "elapsed >=~1s (actual: ${elapsed}s)");
    }
    # clean up — query may still be in flight
    $r->cancel if $r->is_busy;
    $r->wait_idle(2);
}

# --- wait_idle: croaks on destroyed resolver ---
{
    my $r = EV::cares->new;
    $r->destroy;
    eval { $r->wait_idle(1) };
    like($@, qr/destroyed/, 'wait_idle croaks on destroyed resolver');
}

t/17_wait_idle.t  view on Meta::CPAN

{
    my $custom = EV::Loop->new;
    my $r = EV::cares->new(loop => $custom, lookups => 'f');
    is(ref $r->loop, 'EV::Loop', 'loop() returns the custom EV::Loop');

    my $r2 = EV::cares->new(lookups => 'f');
    ok(!defined $r2->loop, 'loop() is undef on default-loop resolver');

    my $fired;
    $r->resolve('localhost', sub { $fired = 1 });
    ok($r->is_busy || $fired, 'query pending or fired synchronously');
    my $drained = $r->wait_idle(3);
    ok($drained, 'wait_idle drains on a custom EV::Loop');
    ok($fired, 'callback ran during wait_idle on custom loop');
}

done_testing;



( run in 0.432 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )