Acme-Parataxis
view release on metacpan or search on metacpan
t/354_transparent.t view on Meta::CPAN
is $rc, 3, "$builtin at the top level returned the byte count" if $builtin eq 'sysread';
is $buf, $builtin eq 'read' ? 'hihi' : 'yo!', "$builtin at the top level filled the caller buffer";
}
};
subtest 'read/sysread inside a fiber frame until the peer writes' => sub {
for my $builtin (qw[read sysread]) {
my ( $a, $b ) = socket_pair();
my ( $buf, $rc );
my $t0 = time;
async {
my $reader = fiber {
if ( $builtin eq 'read' ) { $rc = read( $a, $buf, 7 ) }
else { $rc = sysread( $a, $buf, 7 ) }
};
my $writer = fiber { await_sleep(30); syswrite $b, 'framed!' };
$reader->await;
$writer->await;
};
my $ms = ( time - $t0 ) * 1000;
is $rc, 7, "$builtin framed the payload written after the read parked";
is $buf, 'framed!', "$builtin wrote the result back into the caller buffer";
ok $ms < 4000, "the $builtin returned promptly ($ms ms) once the peer wrote";
}
};
subtest 'a read asked for more than the peer sends returns the short count' => sub {
# Every socket case above asks for exactly the number of bytes the peer sends, which is the one shape a blocking
# read cannot get wrong. Ask for one more than arrives and the old override froze the entire run: await_read only
# promises that *something* is readable, but a raw read on a blocking stream handle waits for the whole requested
# count, so the fiber sat in a syscall where no deadline, no token and no sibling could reach it.
for my $builtin (qw[read sysread]) {
my ( $a, $b ) = socket_pair();
syswrite $b, 'hello'; # five bytes down the wire to $a, then silence
my ( $buf, $rc, $ticked );
my $t0 = time;
async {
# The deadline below does NOT rescue this one if the override regresses: a fiber wedged in the read
# syscall cannot be interrupted, because the timer fiber that would raise the deadline never gets
# scheduled. It is here to catch a *spin* regression, which a deadline can stop. A return to plain
# blocking reads stalls the run outright, so that failure mode is a stall to be read, not a red test.
with_timeout(
5000,
sub {
my $ticks = fiber { await_sleep(30); $ticked = 1; 1 };
if ( $builtin eq 'read' ) { $rc = read( $a, $buf, 10 ) }
else { $rc = sysread( $a, $buf, 10 ) }
$ticks->await;
}
);
};
my $ms = ( time - $t0 ) * 1000;
is $rc, 5, "$builtin asked for 10, got 5, and returned the short count instead of waiting";
is $buf, 'hello', "$builtin filled the caller buffer with the bytes that had arrived";
ok $ms < 2000, "the $builtin returned promptly ($ms ms) instead of parking the OS thread";
ok $ticked, "a sibling fiber ran during the $builtin, so the rest of the run kept going";
}
};
subtest 'the caller handle is handed back in the mode it arrived in' => sub {
# The override makes the handle non-blocking only for the duration of its own read, then puts the mode back.
# Anything else would break the gevent-style contract in reverse: code compiled *before* installation still gets a
# raw blocking read from that same handle, and would start failing with EAGAIN if we left it flipped.
#
# Asked through IO::Handle->blocking, which is the interface the override itself uses and the only portable one -
# Fcntl has no F_GETFL or F_SETFL on Win32 at all. It is not an interface that works *everywhere* either: Winsock's
# FIONBIO can set a socket's mode but has no way to report one, so the getter answers undef there. The capability
# is probed rather than assumed, so a platform that cannot answer is a skip and not a false pass.
my ( $a, $b ) = socket_pair();
plan skip_all => 'this platform cannot report a handle mode through IO::Handle' unless defined IO::Handle::blocking($a);
for my $want ( 1, 0 ) { # 1 = the socket default (blocking), 0 = the caller set non-blocking itself
IO::Handle::blocking( $a, $want );
my $before = IO::Handle::blocking($a) ? 1 : 0;
syswrite $b, 'ok';
my ( $buf, $rc );
async { $rc = read( $a, $buf, 2 ) };
my $after = IO::Handle::blocking($a) ? 1 : 0;
my $was = $before ? 'non-blocking' : 'blocking';
my $now = $after ? 'still non-blocking' : 'still blocking';
is $rc, 2, "an exact read on a handle the caller left $was worked";
is $after, $before, "...and the handle is $now afterwards";
}
};
subtest 'a pipe parks the fiber instead of freezing the thread' => sub {
# Only a platform whose select() accepts a pipe can park here, and that is a question about the platform rather
# than about the OS by name, so it is asked: select() is the mechanism the worker itself uses, and it answers
# immediately either way. Win32's takes sockets only and reports ENOTSOCK for a pipe, which makes a pipe there
# unwatchable - nothing for await_read to wait on, so the override's only remaining move is to read it, and a
# read of a pipe with no writer yet parks the OS thread until the writer runs. The writer is usually a fiber, and
# it cannot run, because the thread that would run it is the one now inside the read. That is a platform limit
# rather than something this module can paper over; the regular-file subtest below still covers the unwatchable
# fallback for the case it actually exists for, where the data is already there.
pipe( my $r, my $w ) or die "pipe: $!";
plan skip_all => 'select() cannot watch a pipe on this platform, so a fiber cannot wait on one' unless can_watch($r);
my ( $buf, $rc, $ticked );
my $t0 = time;
async {
with_timeout(
5000,
sub {
my $ticks = fiber { await_sleep(30); $ticked = 1; 1 };
my $late = fiber { await_sleep(150); syswrite $w, 'piped' };
$rc = read( $r, $buf, 5 );
$late->await;
$ticks->await;
}
);
};
my $ms = ( time - $t0 ) * 1000;
is $rc, 5, 'the read on a pipe returned the byte count once the writer got there';
is $buf, 'piped', '...and filled the caller buffer';
ok $ms >= 140, "the read waited for the late writer ($ms ms) rather than spinning or returning early";
ok $ms < 3000, "...and returned promptly once the data arrived ($ms ms)";
ok $ticked, 'a sibling fiber ran while the pipe read was parked';
};
subtest 'a regular file read inside a fiber falls back to the raw builtin' => sub {
my $dir = File::Temp->newdir( CLEANUP => 0 );
my $path = File::Spec->catfile( $dir->dirname, 'x.txt' );
open my $wr, '>', $path or die "write $path: $!";
print $wr 'file read works'; # length 15
close $wr;
( run in 0.627 second using v1.01-cache-2.11-cpan-b554fb43c41 )