http://www.perlmonks.org?node_id=828917

BrowserUk has asked for the wisdom of the Perl Monks concerning the following question:

I attempted to extend the sample code I posted in Re: Transfer a hash using client and server in perl to make it do and handle concurrent clients. I tried this:

#! perl -slw use strict; use threads; use IO::Socket; use Storable qw[ freeze thaw ]; use Data::Dump qw[ pp ]; $|++; my %hash; @hash{ 'a'..'h' } = 1 .. 8; my $lsn = new IO::Socket::INET( Listen => 5, LocalPort => '12345' ) or die "Failed to open listening port: $!\n"; async{ print 'accept loop started'; while( my $c = $lsn->accept ) { print "Connect from $c"; binmode $c; async { while( my $cmd = <$c> ) { print "Got '$cmd'"; last if $cmd =~ m[^quit]; printf $c "%s", pack "N/a*", freeze \%hash; } print "Got quit from $c"; close $c; }->detach; } }->detach; sleep 2; for my $client ( 1 .. 2 ) { # async { print "Client $client started"; my $s = new IO::Socket::INET( 'localhost:12345' ) or die "Failed to connect to server: $!"; print "client $client connected"; binmode $s; for ( 1 .. 2 ) { print $s 'givemeit'; print 'Sent givemeit'; my $len; read( $s, $len, 4 ) or die "Read failed: $!"; $len = unpack 'N', $len; print "read length: $len"; my $hashStr; read( $s, $hashStr, $len ) or die "Read failed: $!"; print "Read hashstr length: ", length $hashStr; my %hash = %{ thaw $hashStr }; pp \%hash; } print $s 'quit'; close $s; print "client; $client disconnected"; # }->detach; } sleep 1 while 1;

Which works perfectly. It serially connects to the server (twice), transmits a couple of hashes and disconnects. The server then awaits further connection until ^C.

But if you uncomment the #async{ & }->detach; lines in order to allow the clients to run concurrently, nothing happens. The listener starts and awaits a connection. The clients loop is entered and the async() is dispatched, but the thread never starts and async doesn't return. No errors. Just nothing.

But the weird part is, if I connect to the listener from an external process (telnet), the client threads start, run concurrently and finish as designed.

So the question is, what is it about connecting from an external process that causes the in-process client threads to start?


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

Replies are listed 'Best First'.
Re: threads + sockets stalling question
by ikegami (Patriarch) on Mar 16, 2010 at 14:04 UTC

    I'm guessing there's a lock preventing something from being cloned at thread creation.

    • WinXP ActivePerl 5.8.8, 5.10.0, 5.10.1: Blocks.
    • WinXP ActivePerl 5.8.0: Doesn't block, but eventually gives an access violation.
    • Linux 5.8.8: Doesn't block

      You're right! I tracked as far as entering the perl_clone() call in threads.xs. It goes in and doesn't come out until I connect to the socket from an external process:

      C:\test>828831.pl paused # threads.xs(695) # threads.xs(700) # threads.xs(722) # threads.xs(736) # threads.xs(765) # threads.xs(774) # threads.xs(788) # threads.xs(796) # threads.xs(824) # threads.xs(834) # threads.xs(904) # threads.xs(906) # threads.xs(929) accept loop started paused # threads.xs(695) # threads.xs(700) # threads.xs(722) # threads.xs(736) # threads.xs(765) ### hangs here until Connect from IO::Socket::INET=GLOB(0x544e2a0) ### I connect with tel +net # threads.xs(774) ### then everything r +uns on # threads.xs(788) # threads.xs(796) # threads.xs(824) # threads.xs(834) # threads.xs(904) # threads.xs(906) # threads.xs(929) Client 1 started ### and the internal c +lients connect

      So the question becomes, what could perl_clone() be locking and not freeing, that gets freed when accept() gets a connection from an external process?


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

      Hm. The problem comes down to the fact that PerlIO calls the crt fstat() in order to determine if the file descriptor it is trying to clone, is attached to a "regular file".

      As the file descriptor in question is currently in an accept state in another thread and the CRT serialises access, the fstat blocks. The entire Perl_clone() blocks, and the client threads are never started.

      When I connect to the listener from an external process, the accept completes, which unlocks the file descriptor. Thus the fstat() returns allowing the Perl_clone to complete and the client threads get created.

      The real problem here seems to be the hookey methods used inside PerlIO. There has to be a better way than to try to fstat() a socket, to determine if it is socket.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

        Comments in win32sck.c led me to GetFileType(), which might be all that's needed, at least to find out whether a file handle might be a socket.

        As the code is Win32-specific, and Windows 9x isn't supported anymore, this call could maybe used instead of fstat.

Re: threads + sockets stalling question
by almut (Canon) on Mar 16, 2010 at 13:36 UTC

    Just tried it (on Linux, though), and it doesn't hang for me (with async uncommented...).

    (5.8.8 with both threads-1.07 (the one that shipped with 5.8.8) and the current threads-1.76 — my 5.10.1 build here is without thread support, so I can't try)