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

Hi,

I would like to achieve the following:

Run a command and capture its output. While waiting for output, run a subroutine every 10 seconds. So, I do not want to escape from the while loop, like in most other examples.

In this example, the timer is set off, but it ends the program. I tried positioning the eval block on various places (e.g. outside the while loop), but to no avail. Perhaps it cannot be done and I need to dive into working with threads.

The sleep 120 in this code is only to simulate no response from command.

#!/usr/bin/perl use strict; use warnings; use Time::Local; use Time::HiRes qw[ time ]; use POSIX ":sys_wait_h"; my $ReturnValue = 0; sub MainSub() { my $Sub = "MainSub"; my $SubReturnValue = 0; my $CommandLine; my $Command = qq(echo 'Hallo, sleeping for 120 seconds ...'; sleep 120 + 2>&1); open (COMMAND, '-|', "$Command"); while (<COMMAND>) { eval { local $SIG{ALRM} = sub { die("TimerTriggerStatus\n"); }; alarm(10); printf "%s : %s\n", scalar localtime(), "$Sub - timer set to 1 +0 seconds"; chomp; $CommandLine = $_; printf "%s : %s\n", scalar localtime(), "$Sub - CommandLine re +ceived:\n$CommandLine"; }; if ( $@ eq "TimerTriggerStatus\n" ) { printf "%s : %s\n", scalar localtime(), "$Sub - timer triggere +d"; &TimerHandler; }; } close (COMMAND); return ($SubReturnValue); } # end of sub MainSub sub TimerHandler() { my $Sub = "TimerHandler"; my $SubReturnValue = 0; printf "%s : %s\n", scalar localtime(), "$Sub - timer expired"; alarm(10); printf "%s : %s\n", scalar localtime(), "$Sub - timer set to 10 second +s"; return ($SubReturnValue); } # end of sub TimerHandler sub TimerSub() { my $Sub = "TimerSub"; my $SubReturnValue = 0; printf "%s : %s\n", scalar localtime(), "$Sub - run after timer interr +upt"; alarm(10); printf "%s : %s\n", scalar localtime(), "$Sub - timer set to 10 second +s"; return ($SubReturnValue); } # end of sub TimerSub #### MAIN #### printf "%s : %s\n", scalar localtime(), "BEGIN - MAIN"; ($ReturnValue) = &MainSub; printf "%s : %s\n", scalar localtime(), "END - MAIN";

Output when run:

# time ./TestSigalrm.pl Wed Jul 24 08:38:20 2019 : BEGIN - MAIN Wed Jul 24 08:38:20 2019 : MainSub - timer set to 10 seconds Wed Jul 24 08:38:20 2019 : MainSub - CommandLine received: Hallo, sleeping for 120 seconds ... Alarm clock real 0m10.018s user 0m0.014s sys 0m0.002s

Replies are listed 'Best First'.
Re: Using SIG{ALRM} within while loop.
by hippo (Archbishop) on Jul 24, 2019 at 09:54 UTC

    Your script dies because you call alarm() where there is no handler in scope. The code as it stands is rather hard to follow for me because you have used modules you don't then refer to, you have a sub TimerSub which you never call, you have a load of printfs which almost all do the same thing, the subs aren't indented and you are using prototypes with no comments as to why.

    Removing/replacing all of this we get:

    #!/usr/bin/perl use strict; use warnings; sub TimerHandler() { debug ("In the handler"); } debug ('BEGIN - MAIN'); my $Command = qq(echo 'Hallo, sleeping for 120 seconds ...'; sleep 120 + 2>&1); open (COMMAND, '-|', $Command); $SIG{ALRM} = sub { die("TimerTriggerStatus\n"); }; while (<COMMAND>) { eval { alarm(10); debug ("timer set to 10 seconds"); chomp; debug ("CommandLine received:\n$_"); }; if ( $@ eq "TimerTriggerStatus\n" ) { debug ("timer triggered"); TimerHandler (); }; } close (COMMAND); debug ('END - MAIN'); sub debug { printf "%s : %s - %s\n", scalar localtime(), (caller(1))[3], shift +; }

    which still doesn't do what you want but is at least easier to trace through and hopefully you can see the problem which is that your eval does not cover the blocking read and therefore the script dies but at least it does so with your handler.

    Perhaps it cannot be done and I need to dive into working with threads.

    It's probably feasible without threads or forks or a framework around them but it will be hackish. Why not use this as an excellent opportunity to try a threaded or multiprocess approach with a small, manageable task? It probably won't be as tough as you think.

    Good luck.

      Yes, you are right about unused subs. In my attempt post readable code I forgot the part where TimerHandler() calls TimerSub().

      In the meantime I started to tryout threads and it indeed is not too hard. Thanks for pointing out that is would be 'an excellent opportunity'

Re: Using SIG{ALRM} within while loop.
by haukex (Archbishop) on Jul 24, 2019 at 17:38 UTC
    I would like to achieve the following: Run a command and capture its output. While waiting for output, run a subroutine every 10 seconds.

    Sounds like exactly the kind of job IPC::Run can do:

    use warnings; use strict; use IPC::Run qw/ harness timer /; my @cmd = ( 'perl','-le','sleep 60; print "Done!"' ); my $h = harness \@cmd, \undef, \my $out, \my $err, ( my $t = timer 10 ); my $startt = time; $h->start; printf "%2d started\n", time-$startt; while ( $h->pumpable ) { $t->start; $h->pump; printf "%2d pumped\n", time-$startt; } $h->finish; printf "%2d finished\n", time-$startt; print "stdout: <<$out>>\nstderr: <<$err>>\n";

    You can also capture the "live" output of the command, The $out and $err variables should be updated as data from the external program comes in.

    Update: See IPC::Run::Timer for details on the timings (on my system the delay in the above ends up being about 11 seconds).

Re: Using SIG{ALRM} within while loop.
by bliako (Abbot) on Jul 24, 2019 at 13:33 UTC

    and here is a much cleaner solution with IO::Select and a timeout:

    #!/usr/bin/perl use strict; use warnings; use Time::Local; use Time::HiRes qw[ time ]; use POSIX ":sys_wait_h"; use IO::Select; # if command ended, do not keep it because that makes it zombie # this or reap it using waitpid below #$SIG{CHLD} = 'IGNORE'; my $timeout = 3; my $st=time; my $Command = qq(echo 'Hallo, sleeping for 12 seconds ...'; sleep 12); my $COMMANDFH; my $pid = open ($COMMANDFH, '-|', "$Command"); my $select = IO::Select->new(); $select->add($COMMANDFH); print "ran command with pid=$pid\n"; my $iters = 0; WH: while (++$iters) { last WH if waitpid $pid, WNOHANG; my @ready = $select->can_read($timeout); print "no output for the last $timeout seconds ...\n" unless scala +r @ready; foreach(@ready){ last WH if waitpid $pid, WNOHANG; print "command got: ".<$_>."\n"; } sleep 1; } print "command has ended after ".(time-$st)." seconds.\n";

    bw, bliako

Re: Using SIG{ALRM} within while loop.
by Fletch (Bishop) on Jul 24, 2019 at 13:14 UTC

    Not that others haven't addressed your stated problem already, but you might consider using an off the shelf event loop like POE or AnyEvent rather than trying to roll your own.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Using SIG{ALRM} within while loop.
by bliako (Abbot) on Jul 24, 2019 at 12:28 UTC

    I do not understand why eval() does not trap alarm's die() but I think you have 2 logical errors in your code, in addition to what others spotted:

    open (COMMAND, '-|', "$Command"); while (<COMMAND>) { eval { local $SIG{ALRM} = sub { die("TimerTriggerStatus\n"); }; alarm(10); ...

    The first is that the while loop may block on the very first time (i.e. before setting the alarm) if your command does not say something at the beginning. In your example it does echo, so this is not happening. The second is inside the loop, when your command has said something, the while condition unblocked and alarm was set. That sets for just once and will expire in 10s but what happens if command blocks again and when the alarm expired it is never re-set?

    The code below may have logical errors but it does what I think you want to do and alarm's die() is trapped by eval:

    #!/usr/bin/perl use strict; use warnings; use Time::Local; use Time::HiRes qw[ time ]; use POSIX ":sys_wait_h"; my $timeout = 3; my $Command = qq(echo 'Hallo, sleeping for 120 seconds ...'; sleep 120 + 2>&1); my $pid = open (COMMAND, '-|', "$Command"); print "ran command with pid=$pid\n"; my $iters = 0; while (kill 0, $pid) { eval { local $SIG{ALRM} = sub { print "resetting alarm $timeout\n"; a +larm($timeout); die("TimerTriggerStatus\n"); }; if( ! $iters++ ){ print "setting alarm for the first time. +..\n"; alarm($timeout); } print "sleeping for $timeout and then will check command's out +put...\n"; sleep $timeout; print "now I may block for a long time waiting for command's o +utput...\n"; print "command: ".<COMMAND>."\n"; }; print "eval exited with this: ".$@."\n"; }

    EDIT: caveat: I am using sleep() but here https://docstore.mik.ua/orelly/weblinux2/modperl/ch06_10.htm states that:

    It is usually a mistake to intermix alarm( ) and sleep( ) calls. sleep +( ) may be internally implemented in your system with alarm( ), which + will break your original alarm( )settings, since every new alarm( ) +call cancels the previous one.

    bw, bliako

      It would be nice to know
      # some operation that might take a long time to complete
      without a potential usage of SIG(alrm), like sleep. I mean a simple one, for testing purposes.
      The article makes sense.

        see my other post with select

Re: Using SIG{ALRM} within while loop.
by Xliff (Initiate) on Jul 24, 2019 at 10:54 UTC
    Here's a quick and dirty solution in Perl6:
    perl6 -e 'my $a = Proc::Async.new: "bash", "-c", "echo \"Hallo, sleepi +ng for 120 seconds ...\"; sleep 10"; $*SCHEDULER.cue(-> { say "In han +dler..." }, in => 1, every => 2); await $a.start'
    If you want some idea as to more complex options, see this page: https://docs.perl6.org/type/Proc::Async
      Another, more in-depth version of the above, that gives you more control over script output:
      my $proc = Proc::Async.new: "bash", "-c", "echo \"Hallo, sleeping for 120 seconds ...\"; sleep 120"; react { whenever $proc.stdout.lines { # Act on output from command. In this case, we just print it. say ‘line: ’, $_ } whenever Supply.interval(10) { # Handler. Occurs every 10 seconds. say "In handler..." } whenever $proc.start { say ‘Proc finished: exitcode=’, .exitcode, ‘ signal=’, .signal; # Gracefully jump from the react block. # Also stops the handler! done; } whenever Promise.in(300) { # Optional time-out code that fires in 300 seconds [5 minutes] # Handle as you see fit, but it's probably best to end with: done; } }

        Thanks for the code and trying to help me out. I will first tryout threads. I already got it working; now busy with ending threads in a controlled way. For any questions I might have for which I cannot find an answer, I will create a new PerlMonks thread (how appropiate :-) ).