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

Dear Perl Monks,
I'm trying to make a threaded TCP-server with Perl. It must run on Linux and MS-Windows, so I use threads. After some starting problems I have a server that runs without errors, but I grows with each new connect. I do no understand what is wrong. Can anyone tell my what to do to get it right?
Thanks, momo
#!/usr/bin/perl use strict; use threads; use threads::shared; use IO::Socket; use IO::Select; my ($thr,$tid); my $thrcnt : shared = 0; my $port = 7070; my $socket = IO::Socket::INET->new( LocalPort => $port, Type => SOCK_STREAM, Listen => SOMAXCONN, ReuseAddr => 1 ) or die "Can't create listen socket +: $!"; while ((!($tid = threads->tid()))&&(my $client = $socket->accept)) { $thr = threads->create(\&process_request,$client); $tid = threads->tid(); print "main: $tid\n"; $thr->detach; } print "done: exit\m"; ## ## process the request ## sub process_request { my $socket = $_[0]; local %ENV = (); $thrcnt++; my $line = <$socket>; # # #do my thing my $ltid = threads->tid(); print "loop: $thrcnt, $ltid\n"; # # close $socket; }

Replies are listed 'Best First'.
Re: threaded TCP server problem
by BrowserUk (Patriarch) on Mar 27, 2006 at 12:36 UTC

    I have no idea what you thought you were achieving by calling threads->tid() in your main loop?

    Try this version of your code.

    #!/usr/bin/perl use strict; use threads; use IO::Socket; use IO::Select; my $thrcnt : shared = 0; my $port = 7070; my $socket = IO::Socket::INET->new( LocalPort => $port, Type => SOCK_STREAM, Listen => SOMAXCONN, ReuseAddr => 1 ) or die "Can't create listen socket: $!"; while( my $client = $socket->accept ) { my $thr = threads->create( \&process_request, $client ); print "\rStarting ", $thr->tid; $thr->detach; } print "done: exit\n"; sub process_request { my $socket = $_[0]; local %ENV = (); ++$thrcnt; my $line = <$socket>; my $ltid = threads->tid(); print "loop: $thrcnt, $ltid : $line"; close $socket; } __END__ Starting 9999loop: 1, 9999 : Hello:3011 Starting 10000loop: 1, 10000 : Hello:1903 Starting 10001loop: 1, 10001 : Hello:3012 Starting 10002loop: 1, 10002 : Hello:1904

    I ran 4 clients that made connections, said hello and closed the socket in a tight loop until the server connections count reached 10,000. At that point the memory consumption by the server was just 7.5MB

    c:\test>tasklist /fi "pid eq 10624" Image Name PID Session Name Session# Mem Usag +e ========================= ====== ================ ======== =========== += tperl.exe 10624 0 7,676 +K

    That may represent a few 100k of growth over the starting position, it's a little hard to tell as obviously starting a new thread will use extra memory. The question is whether it all gets released. Past experience shows that it does not (as of AS 5.8.6/win32 which I'm using), but the growth is not heavy in this simple case. 200k / 10,000 ~= 20 bytes per thread. Given a average machine with 1 GB of memory, it will be approximately 53 million connections before you start swapping. YMMV on other builds and OSs.

    That's not to say that the leakage isn't a problem, and it can be much worse depending upon what your threads are doing, how many and what shared variables they use etc., but in order to address any serious problems of leakage, it is necessary to see the real code.

    I'd also say that unless your connections are pretty long lived, starting a new thread to handle each one and throwing it away is not an efficient way to use ithreads. Perl's threads are not, by design and necessity, lightweight threads and there is a substantial amount of energy required to spawn them.

    Depending upon the dynamics of your application, which is obviously not accurately represented by this test code, then there are probably better ways of structuring your application to make best use of threads.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Thank you. After making over a dozen versions I didn't know what the problem was. You removed all cruft and made it nice.
      I notice two problems:
      1: $thrcnt does not increment.
      2: on my system, it crashes on MS-Windows (ActivePerl-5.8.8.816)

      Considering my approach.
      The posted example is very lightweight, but in the real application the processing routine is timeconsuming and not lightweight. The number of connects are expected to be limited to <100/hour, and a restart every 24hours. Not a problem.
        1: $thrcnt does not increment.

        My mistake. I removed threads::shared to check something and forgot to put it back. Doing so will fix that and doesn't appear to cause much greater growth. With that put back it's ~ 7.7 MB after 10,000 connections.

        2: on my system, it crashes on MS-Windows (ActivePerl-5.8.8.816)

        I haven't tried AS 5.8.8 yet. I hadn't even noticed it was available. Maybe putting threads::shared back will help, though I don't see why it would. If you are on a multi-cpu system you should be locking the shared data before accesses, but that doesn't explain the crash without threads::shared.

        If the problem persists, I'd revert 5.8.6 which, (not yet having tried 5.8.8), is the most stable version I've used. Otherwise, you'll need the skills and internals knowledge of dave_the_m and the others on p5p to resolve the problem.

        One thing I have noticed is that if the client goes away without closing the connection, the thread will hang around and so consume memory. That may be curable by setting an appropriate timeout in IO::Socket somewhere?


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: threaded TCP server problem
by dave_the_m (Monsignor) on Mar 27, 2006 at 11:44 UTC
    my ($thr,$tid); ... while ((!($tid = threads->tid()))&&(my $client = $socket->accept)) { $thr = threads->create(\&process_request,$client);
    Hmm, looks like there's a leak with detach. A workaround is to narrow the scope of $tid, ie
    while ((!($tid = threads->tid()))&&(my $client = $socket->accept)) { my $thr = threads->create(\&process_request,$client);
    otherwise when you re-enter the loop, the old value in $thr is still there, and gets cloned into the newly-created thread.

    Dave.

      Dave,
      Thank you. You have a good point.
      In the old situation, the script took 6/3 MB (VSZ/RSS) at startup.
      After 35 connects, the programme took 61/37 MB.
      With you fix, this went down to 34/6 MB (after 35 connects).