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

Hello,
Im trying to write a server which accepts any number of strings which will processed and an answer will be sent.
the string I get has a header which contains the length of the string so I should know how much data I will get.
now Id like to have a timeout if no data was sent in a specific time.....eg when the length is higher than the sent string.
thats how I tried it. but the string could be like 10MB in the worst case and it doesnt make sense to increase the timeout just to be able to read the biggest possible string.
I also tried to part data_length in 10kb parts and read it part for part......but for some reason it stops reading after a few loops....no idea why. all I need is a timeout which starts counting as soon as no data is sent anymore. :) btw Im using Net::Server::PreFork and this snipplet is part of my process_request{} hook
eval { local $SIG{ALRM} = sub { die "timeout" }; my $prev_alarm = alarm($prop->{timeout}); while( $data_length = readheader()){ $data = ""; alarm($prop->{timeout}); read STDIN, $data, $data_length, 0; alarm($prop->{timeout}); $modul_result = $modul_handle->process_xml($data); send_response($modul_result); } alarm($prev_alarm); }; if( $@ =~ /^timeout/ ){ $self->log(4, "Connection timed out.\n"); }
Thanks! :)
  • Comment on how can I timeout a function like "read" when reading from a socket
  • Download Code

Replies are listed 'Best First'.
Re: how can I timeout a function like "read" when reading from a socket
by Abigail-II (Bishop) on Mar 08, 2004 at 10:09 UTC
    I would use select(). The fourth argument of select() indicates the timeout.

    Abigail

Re: how can I timeout a function like "read" when reading from a socket
by UnderMine (Friar) on Mar 08, 2004 at 10:30 UTC
    Either select or use an alarm signal.
    my $line; eval { local $SIG{ALRM} = sub { die "alarm clock restart" }; alarm 10; $line = <$child>; alarm 0; }; if ($@) { if ($@ =~ /alarm clock restart/) { print "$$ alarm triggered\n"; next; } else { die "$$ Other error $@\n"; } }
    As always there are multiple solutions but select is nicer.
    UnderMine

      There is a potential for a race condition here. This is described in the Camel book (3rd Ed. - page 417). In brief, you need to do something like the following to protect against this.

      eval { local $SIG{ALRM} = sub { ... }; alarm $timeout; eval { # operation you're waiting on }; alarm 0; }; alarm 0; die $@ if $@;

      PN5

        As great as the Camel book is, I think that the code and discussion on page 417 are both a bit off. The code has a rather nasty bug, and the race condition described isn't the race condition defended against.

        First off, check the Camel book's errata page; that die line will, with the code you posted, never get called! (except in the case of an alarm timeout, which is the one time the OP doesn't want to die)

        Using the fix suggested in the errata, you get something like this:

        eval { local $SIG{ALRM} = sub { die "alarm timeout" }; alarm $timeout; eval { # operation you're waiting on which might die() }; alarm 0; # cancel the alarm die $@ if $@; }; alarm 0; # race help die $@ if $@ && $@ !~ /alarm timeout/; if ($@) { # Whatever you want on an alarm timeout }
        For those of you playing along at home, the race condition in the original code that the Camel book talks about is that the alarm signal could arrive in between completing the code-that-might-timeout (in this case, $line = <$child>) and the alarm 0 call. But if it does, that's OK - alarm doesn't set up signals to be delivered at regular intervals, so this case is handled exactly as if the alarm had been delivered in the middle of whatever code might be timing out.

        The one thing you really don't want is to have the code leave the outer nested eval without either calling alarm 0 or having the alarm signal delivered. In that case, you run the risk of having the alarm be delivered with no alarm handler in place.

        The way that can happen is if some signal (other than SIGALARM) comes in right before the alarm 0 call, and that signal causes things to die. We then exit the outer eval with the alarm still possible.

        The astute of you may notice that even with the code from the Camel book there's still a race condition present, though it requires the code-that-might-timeout to almost timeout, and then have another signal delivered right before the first alarm 0 call. This is now verging on the truly ridiculous, though it might still be possible to trigger, depending on how your OS queues signals and on how slow the perl interpreter could become in moving from one statement's actions to the next.

        My fix for that race condition is this, which I think now handles all the possible race conditions, and is (IMO) a slight bit prettier than the double-alarm-0 method:

        eval { local $SIG{ALRM} = sub { die "alarm timeout" }; local $SIG{__DIE__} = sub { alarm 0; die @_ }; alarm $timeout; # operation you're waiting on which might die() # in the grandparent post, this was # $line=<$child> alarm 0; # cancel the alarm }; die $@ if $@ && $@ !~ /alarm timeout/; if ($@) { # Whatever you want on an alarm timeout }
        No more nested eval, and it is easy to see that alarm 0 is called for every exit path from the eval block. (Unless you do something crazy like re-assign to $SIG{__DIE__} inside your SIGHUP handler. If you do something like that, you're on your own.)

        There's also not a reason to discuss why you set up the $SIG{__DIE__} handler, whereas with the nested-eval-double-alarm-0 you might later have to explain to coworkers the obscure race condition you're avoiding with that second alarm 0 statement. Here, the alarm 0 inside the __DIE__ handler isn't just a race condition guard - it guards all sorts of things. Perhaps race conditions are fun lunch-time conversation at your office, but I find it easier to just code so that the races don't come up in the first place.

Re: how can I timeout a function like "read" when reading from a socket
by Crian (Curate) on Mar 08, 2004 at 11:26 UTC
    You could calculate your own timeout time depending on the length of the input after receiving the header like

    my $timeout = 120 + int($size/1024)*60;

    or something simular. Perhaps you have to play around with the "120" and "60" to get values fitting with your environment. Other posts pointed out how to set this timeout-time after calculating it.
      thanks for the help......its finally running now:)
      good idea with the timout calculation....was thinking about different timeouts but thats a much better solution
      with a basic timeout and an additional time depending on length :)