#! /usr/bin/perl use strict; use warnings; use Socket; use Time::HiRes qw(gettimeofday tv_interval); my $verbose = undef; # The number of connections seems to affect the chance of hitting # the race. So might need tuning on different machines. my $num_connected = 20; my $port = 2345; my $proto = getprotobyname("tcp"); my $remote = "127.0.0.1"; my $iaddr = inet_aton($remote) || die "no host: $remote"; my $paddr = sockaddr_in($port, $iaddr); print <[$i], PF_INET, SOCK_STREAM, $proto) or die "socket: $!"; connect($socks->[$i], $paddr) or die "connect: $!"; vec($rin, fileno($socks->[$i]), 1) = 1; sysread($socks->[$i], my $dummy2, 1); #print "Connected! sock=", fileno($socks->[$i]), "\n"; } $ein = $rin; print "select() vector: ", unpack('H*', $rin), "\n" if $verbose; socket(my $extra_sock, PF_INET, SOCK_STREAM, $proto) or die "extra_sock: $!"; # Wait for the server to die, which causes connection sockets to close and notify us. my $t0 = [gettimeofday]; my $res = select(my $rout = $rin, my $wout = $win, my $eout = $ein, undef); # Try to get in an extra connection while the server process is still getting # killed. This is the connection that can show the race/bug, where the # connection gets established, but there is never any notification that # the connection was closed at the other end. my $t1 = [gettimeofday]; my $res2 = connect($extra_sock, $paddr); my $t2 = [gettimeofday]; print "select() returns: $res out=", unpack('H*', $rout), " err=", unpack('H*', $eout), "\n" if $verbose; my $elapsed1 = tv_interval($t0, $t1); my $elapsed2 = tv_interval($t1, $t2); print "Oops, select() took $elapsed1 seconds! (found=$res)\n" if $elapsed1 > 0.5; # Curiously, the extra connection sometimes takes almost exactly 1 second # to fail. print "Oops, connect() took $elapsed2 seconds! (connect=", ($res2 ? 'Yes' : 'No'), ")\n" if $elapsed2 > 0.5; if ($res2) { print "Oh! Got an extra connection: fd=", fileno($extra_sock), "\n"; my $rin2 = my $win2 = my $ein2 = ''; vec($rin2, fileno($extra_sock), 1) = 1; $ein2 = $rin2; my $res3 = select(my $rout2 = $rin2, my $wout2 = $win2, my $eout2 = $ein2, 10); print "extra select() returns: $res3 out=", unpack('H*', $rout2), " err=", unpack('H*', $eout2), "\n" if $verbose; if (!$res3) { print STDERR "AHA! select() on extra connection timed out on iteration $iter!\n"; my ($extra_port, $extra_host) = sockaddr_in(getsockname($extra_sock)); print STDERR "Extra connection fd=", fileno($extra_sock), " port=", $extra_port, "\n"; exit(1); } } else { print "Bummer, no extra connection this time...\n" if $verbose; } close $child; close $socks->[$_] for (1..$N); close $extra_sock; } # Server. sub server { my ($N) = @_; socket(my $server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; setsockopt($server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!"; bind($server, sockaddr_in($port, INADDR_ANY)) || die "bind: $!"; listen($server, SOMAXCONN) || die "listen: $!"; # Signal parent that socket is ready! syswrite(STDOUT, '.', 1); # First accept a bunch of connections. my $clis = []; for my $i (1..$N) { my $res = accept($clis->[$i], $server); die unless $res; syswrite($clis->[$i], '!', 1) or die; } # Then SIGKILL ourselves, see if we can trigger a client managing to get a # connection in during the close-of-sockets. select(undef, undef, undef, 0.02); print STDERR "SIGKILL self\n" if $verbose; kill 9, $$; sleep(10) while (1); }