tags:

views:

234

answers:

3

Is there any way to build some time counter that enable parts of a script to run as long it ticks? For example, I have the following code:

for my $i (0 .. $QUOTA-1) {
    build_dyna_file($i);
    comp_simu_exe;
    bin2txt2errormap($i);
}

Theoretically I want to run this loop for 3 minutes, even if the loop instructions haven't finished yet, it should still break out of the loop after exactly 3 minutes.

Actually the program opens a time counter window that works in parallel to part of the script (each time I call it).

Additionally, the sub call 'comp_simu_exe' run an outside simulator (in the shell) that when time out ends - this process must also killed (not suppose to return after a while).

sub comp_simu_exe{

system("simulator --shell");
}

Is there any connection between the dead coming problem to the system function call ?

+11  A: 

You can set an alarm that will break out of your code after a specified amount of seconds:

eval {
    local $SIG{ ALRM } = sub { die "TIMEOUT" };
    alarm 3 * 60;
    for (my $i = 0 ; $i <$QUOTA ; $i++) {
        build_dyna_file($i);
        comp_simu_exe;
        bin2txt2errormap($i);
    }
    alarm 0;
};

if ( $@ && $@ =~ m/TIMEOUT/ ) {
    warn "operation timed out";
}
else {
    # somebody else died
    alarm 0;
    die $@;
}

Or, if you really need the loop to run at least three times, no matter how long this might take:

eval {
    my $t0 = time;
    local $SIG{ ALRM } = sub { die "TIMEOUT" };

    for (my $i = 0 ; $i <$QUOTA ; $i++) {
        build_dyna_file($i);
        comp_simu_exe;
        bin2txt2errormap($i);
        if ( $i == 3 ) {
            my $time_remaining = 3 * 60 - time - $t0;
            alarm $time_remaining if $time_remaining > 0;
        }
    }
    alarm 0;
};
innaM
+1 beat me to it (and a better example)
Nifle
remember to run `alarm 0;` if `$@` does not match `"TIMEOUT"`. The code could die inside the block eval and the `alarm 0;` inside would never run. This would lead to spurious `die`s with the message "Alarm clock\n".
Chas. Owens
thank you for the fast answer. What i meant to do is to run a script and kill the process if it run more then 3 minuets. I tried your suggestion and it actually stops after 10 seconds(I tried 10 seconds) but then, after another 10 seconds it continue from the breaking point.
YoDar
Chas: Good point. Code adjusted.
innaM
Yohad: How are you starting the script that must be finished after x seconds?
innaM
As you wrote in your first answer (but without the else..)
YoDar
As _I_ wrote? Sorry, but I don't get it.
innaM
The start of my script is lots of subs but in the main program I wrote some functions and then the 'eval' declaration, then I wrote my loop between the two alarms.
YoDar
It is probably better to use a scope guard to do the "alarm 0", that way the code is not repeated. "use Guard; eval { my $guard = scope_guard { alarm 0 }; <code> }" Now you always reset the alarm no matter how the eval block is exited.
jrockway
Yohad: So what is missing now?
innaM
the rest of the script ended like you wrote before. I don't know what is missing now, but when I run it in Unix, it kill the process after x sec, return to the terminal and then after x sec it conduct the rest of the script.
YoDar
Are you forking before setting the alarm?
innaM
The alarm set before the forking
YoDar
So you are forking in one of the subs called from within the for-loop?
innaM
yes, if I understand you correctly.
YoDar
In that case you will have to move the timeout-code to the code that handles the forking. If you need to kill one of your child processes you will have to do that someplace where you know the child's pid.
innaM
Sorry, didn't catch you... but I'll try to figure out it tomorrow.Thanks a lot for your attention, Manni!
YoDar
The problem is only in the sub call comp_simu_exe that return to work after x sec. this sub run a simulation task in the shell. How can I have a clean kill of it's process with the alarm ?
YoDar
Now I get it. Sorry. See my new answer.
innaM
+3  A: 

The way you deal with timeouts like this in Perl is the alarm function. It will send the signal ALRM to your process after the number of seconds you pass into it. Be careful if the code you are trying to setup the timeout for calls sleep, as they do not mix well on many platforms. The basic structure looks like this:

#start a block eval to stop the die below from ending the program
eval {
    #set the signal handler for the ALRM signal to die if it is run
    #note, the local makes this signal handler local to this block
    #eval only
    local $SIG{ALRM} = sub { die "timeout\n" };

    alarm $wait; #wait $wait seconds and then send the ALRM signal

    #thing that could take a long time

    alarm 0; #turn off the alarm (don't send the signal)

    #any true value will do, if this line is reached the or do below won't run
    1; 
} or do {
    #if we are in this block something bad happened, so we need to find out
    #what it was by looking at the $@ variable which holds the reason the
    #the code above died
    if ($@ eq "timeout\n") {
        #if $@ is the timeout message then we timed out, take the
        #proper clean up steps here
    } else {
        #we died for some other reason, possibly a syntax error or the code
        #issued its own die.  We should look at $@ more carefully and determine
        #the right course of action.  For the purposes of this example I will
        #assume that a message of "resource not available\n" is being sent by
        #the thing that takes a long time and it is safe to continue the program.
        #Any other message is unexpected.

        #since we didn't timeout, but the code died, alarm 0 was never called
        #so we need to call it now to prevent the default ALRM signal handler
        #from running when the timeout is up
        alarm 0;

        if ($@ eq "resource not available\n") {
            warn $@;
        } else {
            die $@;
        }
 }

or written more compactly:

eval {
    local $SIG{ALRM} = sub { die "timeout\n" };

    alarm $wait; #wait $wait seconds and then send the ALRM signal

    #thing that could take a long time

    alarm 0;

    1;
} or do {
    die $@ unless $@ eq "timeout\n" or $@ eq "resource not available\n";
    alarm 0;
    warn $@;
}
Chas. Owens
It happens also with your suggestion. After x sec the program exit out back to the terminal - OK. but then after x sec at the terminal - the program comes from nowhere and continue from where it stops and end its task. What do you think could be the dead coming problem ?
YoDar
+3  A: 

Here's a second answer that deals with the case of timing-out a second process. Use this case to start your external program and make sure that it doesn't take too long:

my $timeout = 180;
my $pid = fork;

if ( defined $pid ) {
    if ( $pid ) {
        # this is the parent process
        local $SIG{ALRM} = sub { die "TIMEOUT" };
        alarm 180;
        # wait until child returns or timeout occurs
        eval {
            waitpid( $pid, 0 );
        };
        alarm 0;

        if ( $@ && $@ =~ m/TIMEOUT/ ) {
            # timeout, kill the child process
            kill 9, $pid;
        }
    }
    else {
        # this is the child process
        # this call will never return. Note the use of exec instead of system
        exec "simulator --shell";
    }
}
else {
    die "Could not fork.";
}
innaM
Thanks it seems to work fine but when I use EXEC it exit PERL script, and I need to continue the for-loop section. Can I use somehow with the system call instead of the exec call ?
YoDar
If you do the fork each time through your loop, you should be fine.
innaM
Manni, thank you very much for your help! it done the work perfectly!
YoDar