Acme-Parataxis
view release on metacpan or search on metacpan
t/030_soak.t view on Meta::CPAN
W => $scale * ( 1 + $rand->(10) ),
A => $rand->(2) ? $scale * ( 4 + $rand->(10) ) : 0
);
my $fut = Acme::Parataxis::Future->new;
my $sem = Acme::Parataxis::Semaphore->new( count => 2 );
my $sig = Acme::Parataxis::Signal->new;
my $chan = Acme::Parataxis::Channel->new( capacity => 1 );
my %inv;
my @fibers;
for ( 1 .. $plan{S} ) {
push @fibers, fiber { await_sleep( 1 + $rand->(5) ) }
}
for ( 1 .. $plan{G} ) {
push @fibers, fiber {
my $g = $sem->guard;
yield for 1 .. 2;
undef $g;
$inv{guards}++;
};
}
my $put_seq = 0;
for ( 1 .. $plan{P} ) {
push @fibers, fiber { $chan->put( ++$put_seq ) }
}
for ( 1 .. $plan{P} ) {
push @fibers, fiber {
my $v = $chan->get;
push @{ $inv{tokens} }, $v;
};
}
for ( 1 .. $plan{W} ) {
push @fibers, fiber { $sig->wait; $inv{signalled}++ }
}
for ( 1 .. $plan{A} ) {
push @fibers, fiber { my $v = $fut->await; $inv{awaited}++; $inv{fut_value} = $v }
}
undef $fibers[$_] for grep { $rand->(5) == 0 } 0 .. $#fibers;
undef @fibers;
my $need_ups = $plan{G};
my $need_sig = $plan{W};
my ( $ups_done, $fut_set ) = ( 0, 0 );
for ( 1 .. ( 50 + 20 * $scale + $rand->(40) ) ) {
my $op = $rand->(8);
if ( $op == 0 && $need_ups ) { $sem->up; $need_ups-- }
elsif ( $op == 1 && $need_sig ) { $sig->send; $need_sig-- }
elsif ( $op == 2 ) { $sem->up }
elsif ( $op == 3 ) { $sig->send }
elsif ( $op == 4 && !$fut_set && $plan{A} ) { $fut->set_result('v'); $fut_set = 1 }
elsif ( $op == 5 ) { await_sleep(1) }
elsif ( $op == 6 ) {yield}
else {maybe_yield}
}
$sem->up for 1 .. $need_ups;
$sig->send for 1 .. $need_sig;
$fut->set_result('v') if $plan{A} && !$fut_set;
maybe_yield for 1 .. 3;
return { plan => \%plan, inv => \%inv };
}
# Prologue: abandon a run mid-flight so a parked fiber and its job are left over; the
# first wave has to reclaim them before it can reach a clean boundary.
async {
my $leaker = fiber { await_sleep(4) };
yield for 1 .. 2;
stop;
};
my $t0 = time;
my ( $waves, $fails ) = ( 0, 0 );
my ( $peak_live, $peak_jobs, $max_scale ) = ( 0, 0, 1 );
my @problems_out;
while ( time - $t0 < $SECONDS && $waves < $MAX_WAVES ) {
$waves++;
my $seed = $BASE_SEED + $waves;
my $scale = 1 + $waves % 3;
$max_scale = $scale if $scale > $max_scale;
my $out;
eval {
$out = async { one_wave( $seed, $scale ) }
};
if ($@) { @problems_out = ( @problems_out, "wave $waves (seed $seed, scale $scale) croak escaped: $@" ); $fails++; last }
if ( ref $out ne 'HASH' ) { @problems_out = ( @problems_out, "wave $waves (seed $seed) async returned nothing" ); $fails++; last }
my $plan = $out->{plan};
my $inv = $out->{inv};
my @p;
push @p, 'fibers left behind (waves must end at a clean boundary)' if live_count() != 0;
push @p, 'jobs left outstanding after wave' if outstanding() != 0;
push @p, "lost semaphore wake: $inv->{guards} guards ran (expected $plan->{G})" if $inv->{guards} != $plan->{G};
push @p, "lost future wake: $inv->{awaited} awaiters (expected $plan->{A})" if $plan->{A} && $inv->{awaited} != $plan->{A};
push @p, "future misdelivered value $inv->{fut_value} (expected v)" if $plan->{A} && ( $inv->{fut_value} // '' ) ne 'v';
push @p, "lost signal: $inv->{signalled} waiters woken (expected $plan->{W})" if $inv->{signalled} != $plan->{W};
my $tokens = join ',', sort { $a <=> $b } @{ $inv->{tokens} // [] };
push @p, "channel tokens [$tokens] != 1..$plan->{P}" unless $tokens eq join ',', 1 .. $plan->{P};
if (@p) {
$fails++;
@problems_out = (
@problems_out,
'wave ' .
$waves .
" (seed $seed; scale $scale; " .
join( ' ', map {"$_=$plan->{$_}"} sort keys %{ $plan // {} } ) .
"):\n " .
join "\n ",
@p
);
last;
}
$peak_live = live_count() if live_count() > $peak_live;
$peak_jobs = outstanding() if outstanding() > $peak_jobs;
}
my $elapsed = sprintf '%.1fs', time - $t0;
ok $fails == 0, "soak: $waves waves in $elapsed (peak scale $max_scale)";
note 'peak live fibers: ' . $peak_live . " (limit ~" . 1024 . ')' if $peak_live;
note 'peak outstanding jobs: ' . $peak_jobs if $peak_jobs;
note join "\n", @problems_out if @problems_out;
#
done_testing;
( run in 1.125 second using v1.01-cache-2.11-cpan-eca53f54451 )