3

I browsed around and read a few other questions about getting a command to stop running after a period of time but everything i tried doesn't work.

The command will act like it wants to stop after the specified amount of time (It returns the terminal prompt for a brief second) but just keeps going and wont stop.

Here is my code:

#!/usr/bin/perl

use strict;
use warnings;


use Try::Tiny;

try {
        local $SIG{ALRM} = sub { die "alarm\n" };
        alarm 5;
        system("ping 192.168.1.1");
        alarm 0;
}
catch {
        die $_ unless $_ eq "alarm\n";
        print "timed out\n";
};
exit;

The output in the terminal looks like this:

PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.98 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=3.13 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=3.20 ms
64 bytes from 192.168.1.1: icmp_seq=4 ttl=64 time=3.17 ms
64 bytes from 192.168.1.1: icmp_seq=5 ttl=64 time=3.16 ms
64 bytes from 192.168.1.1: icmp_seq=6 ttl=64 time=3.11 ms
64 bytes from 192.168.1.1: icmp_seq=7 ttl=64 time=3.18 ms
64 bytes from 192.168.1.1: icmp_seq=8 ttl=64 time=3.20 ms
64 bytes from 192.168.1.1: icmp_seq=9 ttl=64 time=3.22 ms
64 bytes from 192.168.1.1: icmp_seq=10 ttl=64 time=3.20 ms
skilo@hp-xubuntu:~/perlstuff$ 64 bytes from 192.168.1.1: icmp_seq=11 ttl=64 time=3.06 ms
64 bytes from 192.168.1.1: icmp_seq=12 ttl=64 time=3.19 ms
64 bytes from 192.168.1.1: icmp_seq=13 ttl=64 time=3.21 ms
64 bytes from 192.168.1.1: icmp_seq=14 ttl=64 time=3.21 ms

Notice how it tries to stop but can't.

Skilo Skilo
  • 526
  • 4
  • 11
  • 23

1 Answers1

4

The perl 'system' builtin function forks your process, and in the child process created by the fork, it executes the 'ping' program. Then, the parent process waits on the child process to finish.

Your alarm timer and signal handler interrupts the waiting process, and then terminates the parent, leaving the child process running in the background.

What you will need to do is:

  1. After the alarm times out, kill the child process by sending it a signal
  2. You should still (manually) wait on the process, to avoid getting a zombie.

Try this:

#!/usr/bin/perl

use strict;
use warnings;



eval {
        local $SIG{ALRM} = sub { die "alarm\n" };
        alarm 5;
        system("ping -v 192.168.1.1");
        alarm 0;
};
if ($@) {
        die $@ unless $@ eq "alarm\n";
        print "timed out\n";
        kill 2, -$$;
        wait;
};
exit;

Note: I did not have Try::Tiny on my system so I replaced that with old-school eval blocks. But it should work the same.

This 'kill' command takes a signal number (I used 2 for SIGINT, which is equivalent to pressing ctrl-c) and one or more processes to kill. $$ is the pid of the current process; a negative value has the effect of killing the whole process group associated with the current process (which effectively means all child processes). Note that this is not completely portable - if you find that it does not work on your system then you are going to need to be able to find the actual PID of the child process. To be able to do that you should replace system with calls to fork and then exec, like this:

#!/usr/bin/perl

use strict;
use warnings;


my $childPid;
eval {
        local $SIG{ALRM} = sub { die "alarm\n" };
        alarm 5;
        if ($childPid = fork()) {
                wait();
        } else {
                exec("ping -v 192.168.1.1");
        }
        alarm 0;
};
if ($@) {
        die $@ unless $@ eq "alarm\n";
        print "timed out\n";
        kill 2, $childPid;
        wait;
};
exit;
harmic
  • 28,606
  • 5
  • 67
  • 91
  • This did not work for me on Windows cmd, just try "timeout 10" as a command. It prints "Terminating" and gives you back the prompt, but the child process keeps running and printing, messing up the cmd. Any idea why? – Agostino Dec 06 '21 at 16:01