Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multiple sysreads causing a following syswrite to fail in Perl

Tags:

sockets

perl

I have a daemon process that accepts connections, reads a JSON request via a single sysread, processes it, and sends back a JSON response via syswrite. This works fine. But I wanted to make the reading more robust in case of partial reads. So I used the common technique I see folks use of looping until 0 bytes are read, while specifying to append the input to the end of the buffer. When I do this simple loop, the following syswrite claims it wrote out X bytes, but the caller thinks it read back 0 bytes. It appears that doing one than 1 sysread on a socket will cause a future syswrite on that socket to fail.

ie: I had changed my single sysread to do this instead:

my $done = 0 ;
while ( ! $done ) {
    $bytes = sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;
    $done++ if ( $bytes == 0 ) ;
}

I have verified that in each case, the request that I receive is the same, and the response that I am sending is the same. The only thing that changed is the additional sysread that read 0 bytes to indicate that I had read everything available.

I have attached a simple stripped down program that illustrates this. Can someone explain to me what is happening here? I've also tried specifically telling syswrite the number of bytes to write and the starting position (0) with the same effect.

#!/usr/bin/env perl

use Socket ;
use JSON ;
use constant BUFSIZ => 1024 ;

my $protocol = getprotobyname( 'tcp' ) ;

my $server ;
if ( ! socket( $server, AF_INET, SOCK_STREAM, $protocol )) {
    die( "socket() failed: $!" ) ;
}
if ( ! setsockopt( $server, SOL_SOCKET, SO_REUSEADDR, 1 )) {
    die( "setsockopt(): failed: $!" ) ;
}

my $my_addr = sockaddr_in( 2007, INADDR_ANY ) ;
bind( $server, $my_addr )     or die( "bind(): failed: $!" ) ;
listen( $server, SOMAXCONN )  or die( "listen(): failed: $!" ) ;

my ( $remote_addr, $client ) ;
$remote_addr = accept( $client, $server ) ;
$_ = select( $client ); $| = 1; select $_ ;       # autoflushing
my $json_text = "" ;
my $bytes     = 0 ;

# a later syswrite() doesn't work when sysread() called more than once
my $done = 0 ;
while ( ! $done ) {
    $bytes = sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;
    $done++ if ( $bytes == 0 ) ;
}

# calling sysread once works ok with following syswrite
# sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;

print "Got a JSON request: $json_text\n" ;

# just make up some dummy response to whatever we received
my $out_struct = {
    'id'        => 'server6-alive-test',
    'output'    => [ 'FOOBAR' ],
    'errors'    => [],
    'status'    => 0,
    'served-by' => 'server6',
};

$json_text = to_json( $out_struct, { utf8 => 1, pretty => 1 } ) ;
my $num = syswrite( $client, $json_text ) ;
print "wrote $num bytes to server\n" ;

and this debug output is from the callers perspective:

# NO while loop
debug: main::set_up_systems: testing if server6 is alive
debug: main::send_cmd_to_daemon: sending command 'alive' to remote
debug: main::check_if_system_alive: Got: 'FOOBAR'

# with while loop version
debug: main::set_up_systems: testing if server6 is alive
debug: main::send_cmd_to_daemon: sending command 'alive' to remote
debug: main::send_cmd_to_daemon: empty JSON string returned
debug: main::process_cmd: server6: No output sent back.
like image 615
RJ White Avatar asked Sep 11 '25 04:09

RJ White


1 Answers

You say the syswrite in the deamon failed, but you also say it returned a positive number. This would indicate it didn't fail. syswrite returns undef on error.

The sysread loop in the daemon reads until it encounters EOF, which is when sysread returns 0. I am guessing the client signaled EOF by closing the entire socket, causing the sysread in the client to fail (return undef with $! set to EBADF (Bad file descriptor)). Instead, only shut down the writer half using

use Socket qw( SHUT_WR );

shutdown($sock, SHUT_WR)
   or die($!);
like image 123
ikegami Avatar answered Sep 12 '25 22:09

ikegami