Hyperman

 view release on metacpan or  search on metacpan

t/70-pool-edges.t  view on Meta::CPAN

#!perl
use strict;
use warnings;
use lib 't/lib';
use Test::More;
use HMTest qw(free_ports quiet_child server_reap);
use IO::Socket::INET;
use Time::HiRes ();
use File::Temp ();
use Hyperman;

# The pool's edges: a server without the arena printing its own USR1 line, a
# reuseport pool whose workers bind for themselves with an arena too small for
# a scoreboard, USR1 at a supervisor with a board, and a supervisor whose
# caller's alarm comes due.

my ($pa, $pb, $pc) = free_ports(3);
plan skip_all => 'no free loopback ports' unless $pa && $pb && $pc;
plan skip_all => 'signals and fork are not Windows' if $^O eq 'MSWin32';
my $dir = File::Temp::tempdir(CLEANUP => 1);

sub connect_to {
    my $port = shift;
    for (1 .. 60) {
        my $s = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port");
        return $s if $s;
        Time::HiRes::sleep(0.1);
    }
    return;
}
sub get {
    my ($port, $path) = @_;
    my $s = connect_to($port) or return '';
    $s->syswrite("GET $path HTTP/1.1\r\nHost: a\r\nConnection: close\r\n\r\n");
    my $buf = '';
    eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm 5; while (my $n = $s->sysread(my $c, 4096)) { $buf .= $c } alarm 0 };
    return $buf;
}
sub slurp { my $f = shift; -f $f ? do { local (@ARGV, $/) = ($f); <> } // '' : '' }
sub wait_for { my ($file, $re) = @_; for (1 .. 30) { my $t = slurp($file); return $t if $t =~ $re; Time::HiRes::sleep(0.1) } return slurp($file) }

# --- a fresh perl without the arena: the worker prints its own USR1 line ---
{
    my $pid = fork // die "fork: $!";
    if (!$pid) {
        quiet_child();
        $ENV{HYPERMAN_NO_SA_ABI} = 1;
        exec $^X, '-Mblib', '-MHyperman', '-e',
            "Hyperman->run(app => sub { [200, [], ['x']] }, host => '127.0.0.1', port => $pa, workers => 1, error_log => '$dir/a.err')";
        exit 1;
    }
    like(get($pa, '/'), qr{^HTTP/1\.1 200}, 'the arena-less server answers');
    kill 'USR1', $pid;
    like(wait_for("$dir/a.err", qr/requests=/), qr/Hyperman worker \d+: requests=\d+ accepts=\d+ conns=\d+ bytes_out=\d+ backend=\w+/, 'USR1 without a scoreboard prints the worker line');
    kill 'TERM', $pid;
    server_reap($pid);
}

# --- a reuseport pool with an arena too small for its scoreboard ---
{
    my $pid = fork // die "fork: $!";
    if (!$pid) {
        quiet_child(stderr => "$dir/b.stderr");
        Hyperman->run(app => sub { [ 200, [], [ "w$$" ] ] }, host => '127.0.0.1', port => $pb, workers => 2,
            reuseport => 1, arena_size => 4096, error_log => "$dir/b.err");
        exit 0;
    }
    my %seen;
    for (1 .. 40) {
        my $r = get($pb, '/');
        $seen{$1}++ if $r =~ /w(\d+)\z/;
        last if keys %seen >= 2;
    }
    ok(scalar(keys %seen) >= 1, 'the reuseport workers bound their own listeners and answer');
    like(wait_for("$dir/b.stderr", qr/could not carve/), qr/could not carve the "deny" map in the arena/, 'an arena too small warns of what it cannot carve');
    kill 'USR1', $pid;
    Time::HiRes::sleep(0.5);
    kill 'TERM', $pid;
    is(server_reap($pid) >> 8, 0, 'the pool stops cleanly');
}

# --- USR1 at a supervisor with a scoreboard prints every row ---
{
    my $pid = fork // die "fork: $!";
    if (!$pid) {
        quiet_child(stderr => "$dir/c.stderr");
        Hyperman->run(app => sub { [ 200, [], ['x'] ] }, host => '127.0.0.1', port => $pc, workers => 2, error_log => "$dir/c.err");
        exit 0;
    }
    like(get($pc, '/'), qr{^HTTP/1\.1 200}, 'the pool answers');
    Time::HiRes::sleep(0.5);
    kill 'USR1', $pid;
    like(wait_for("$dir/c.stderr", qr/\S/), qr/\d/, 'the supervisor prints the scoreboard on USR1');
    kill 'TERM', $pid;
    server_reap($pid);
}

# --- the caller's own alarm ends the pool and is then delivered ---
{



( run in 1.800 second using v1.01-cache-2.11-cpan-145b269f2f3 )