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

Hi,

I am trying to do the following:

Step through a foreach loop and call a funtion.
The function may take a very long time to complete but I only want to wait a finite period of time and if the function has not completed, go to the next iteration of the loop.

I am using an alarm() with a call to a signal handler in order to acomplish this.

My problem is that when I try to use a goto withing the signal handler, I get the following error:

Can't find label LABEL at ./t21.pl line 18.

I should mention that I running this in a Win32 (Win 2K) machine and I am aware of all the caveats that apply to signal handling on this OS.

Aparently the alarm() call is now supported on Win32 for Active State Perl 5.8.0. which I am using(See article here) and it appears to work fine.

Here is my code:

sub callfc() { # just keep printing until the signal goes off for ($i = 0; $i < 1000; $i++) { print "$i \n"; sleep(1); } } for ($j = 0; $j < 1000000; $j++) { # set signal time out alarm(2); # set up signal handler $SIG{ALRM} = sub { goto "LABEL"; }; # call the function &callfc(); LABEL: # hopefully skip to the next iteration of the loop } print "I am continuing with the code\n"; sleep(1); print "I am continuing with the code\n";

for the test code above, I've used a simple for loop but the same principle applies.

Does anyone have any suggestions on how to acomplish what I am trying to do, either with the (much frowned upon goto) or any other way.

Thanks for your help!

Kakaze

Replies are listed 'Best First'.
Re: GOTO, Signals and Win32
by broquaint (Abbot) on Nov 14, 2003 at 11:48 UTC
    It can't find the label because it's not in the same scope when the callback is invoked. A more effective solution would be to use an eval BLOCK e.g
    { alarm 2; ## don't set this globally! local $SIG{ALRM} = sub { die "timed out" }; eval { callfc() }; ## continue to next iteration if timed out next if $@ and $@ =~ /^timed out$/; }
    See. the eval docs for more info.
    HTH

    _________
    broquaint

      I played about with the eval code and some of the code from the docs (which I had previously rejected) and for some reason, the following actually works:

      foreach $j (@j) { print "Next Loop $j \n"; eval { local $SIG{ALRM} = sub { die "Alarm\n"; }; alarm(2); &callfc(); }; if ($@) { die unless $@ eq "Alarm\n"; # timed out } else { # didn't print "Didnt\n"; } } print "I am continuing with the code\n";

      Does anyone have an explanation as to why this works and the next if above does not. Logically it should.

      Kakaze

        Hi. I know this thread is almost 12 years old. I still want to clarify the presented problem for any possible new visitors searching for an answer on the subject.

        Your previous code, the one that quits "unexpetedly", has issues, which I comment inline:

        foreach $j (@j) { alarm 2; # ^ You shouldn't set an alarm out of the scope where you eval the + time out and localize $SIG{ALRM}; local $SIG{ALRM} = sub { die "timed out" }; # ^ This localization should be inside an eval block so you can ca +tch the alarm. eval { callfc() }; # ^ Here, you are only catching errors from your function, not fro +m a timeout next if $@ and $@ =~ /^timed out$/; # ^ You are checking for the exact string "timed out", but you sho +uld have /^timed out/ (without $ at the end), or, "timed out\n" in bo +th places (including the new-line character) because otherwise the me +ssage will include traces information from "die" }
        Now, for your "working" code, which still has issues, I commend inline too:
        foreach $j (@j) { print "Next Loop $j \n"; eval { local $SIG{ALRM} = sub { die "Alarm\n"; }; alarm(2); &callfc(); }; # ^ This is much better, you localize $SIG{ALRM} inside eval, and yo +u are also using # the new-line character in the message (and that's why it works (pa +rtially)) # In here, you should have called alarm(0) to cancel the alarm. # It doesn't matter if you are out of the eval block, the alarm cont +inues to run # and you eventually can have an uncatched "timeout" while being on +a different # part of your flow. You must call alarm(0) after your eval block (n +ot from inside # because it can die for a different cause too and never get to the +alarm(0) line). # So, I added it here: alarm 0; if ($@) { die unless $@ eq "Alarm\n"; # ^ You should send the error to the die too so you know what went + wrong, # like this: die $@ unless $@ eq "Alarm\n"; # But yes, you would catch a timeout here (if alarm 0 was present +too above). } else { print "Didnt\n"; } } print "I am continuing with the code\n"; # ^ Yeah... except if you didn't set alarm(0) above, and your script c +ontinues # to run and take longer than the alarm to complete, you'll eventually + have an # error stating "Terminating on signal SIGALRM(14)" and wonder why tha +t happened.
        So, to summarize, this is what a timeout block should have, explained:
        # First, the thing you want to timeout must be inside of an eval block +: eval { # Second, localize the signal handling and set alarm to the desired +timeout limit: $SIG{ALRM} = sub { die "timeout\n" }; alarm 10; # Third, add the lines of code you specifically want to timeout: do_something_here_that_can_timeout(); }; # Fourth: CANCEL the alarm immediately or you may have problems later! # This should be AFTER the eval block, not inside the eval block: alarm 0; # Fifth: Check for errors to see if you had a timeout or something els +e: if ($@ eq "timeout\n") { # Do whaever you need if a timeout happened handle_timeout(); } else { # You had an actual error and not a timeout, so handle it: handle_error($@); # or just: die $@ }
        I hope this helps anyone trying to understand it.

        - Zarabozo

      Trying the following code

      foreach $j (@j) { alarm 2; ## don't set this globally! local $SIG{ALARM} = sub { die "timed out" }; eval { callfc() }; ## continue to next iteration if timed out next if $@ and $@ =~ /^timed out$/; }

      simply causes the program to quit:

      $ t21.pl 0 1 Terminating on signal SIGALRM(14)

      I had tried something similar from the signal and eval Docs but it also terminated which is why I had switched to using the goto.

      from what you have written, it would appear that it should iterate to the next iteration of the loop but it does not.

      kakaze>

      I tried adding the alarm(0) immediately after the eval and it still exited.

      Thanks anyway!