How to Kill a Child Process After a Given Timeout in Bash

How to kill a child process after a given timeout in Bash?

(As seen in:
BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")

If you don't mind downloading something, use timeout (sudo apt-get install timeout) and use it like: (most Systems have it already installed otherwise use sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

If you don't want to download something, do what timeout does internally:

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

In case that you want to do a timeout for longer bash code, use the second option as such:

( cmdpid=$BASHPID; 
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )

Does the Unix command Timeout also kill any children of the process?

The GNU coreutils version of timeout can have its implementation viewed here.

Whether a process group is created (and killed as a whole) does indeed depend on whether --foreground is passed, as you have inferred from the documentation.


To be a bit more explicit:

  • When --foreground is not used, we call setpgid() to create a new process group, putting both the timeout command itself and the invoked command inside that group. When the timeout later occurs, kill() is passed 0 as the PID to kill, specifying that the entire process group should be targeted.
  • When --foreground is used, only the immediately forked PID is signaled, and not other members of the process group.

How to kill a process after a given real running time in Bash?

On Bash+Linux, you can use ulimit -t. Here's from help ulimit:

 -t        the maximum amount of cpu time in seconds

Here's an example:

$ time bash -c 'ulimit -t 5; while true; do true; done'
Killed

real 0m8.549s
user 0m4.983s
sys 0m0.008s

The infinite loop process was scheduled (i.e. actually ran) for a total of 5 seconds before it was killed. Due to other processes competing for the CPU at the same time, this took 8.5 seconds of wall time.

A command like sleep 3600 would never be killed, since it doesn't use any CPU time.

kill process using sigterm and escalate to sigkill after timeout

There's the timeout command, which allows you to cap a process' execution time and escalate to a SIGKILL if it doesn't respond promptly to the initial signal (SIGTERM by default). This isn't quite what you're asking for, but it might be sufficient.

To do what you're actually describing (send a signal, briefly await, then send a kill) you may have to do a bit of bookkeeping yourself, as this question details.

One option would be to use Upstart (or I imagine other service managers), which provides a kill timeout n command that does what you want.


As an aside, many systems would treat 30 minutes as much too long to wait for SIGTERM. Linux does something akin to what you're describing on shutdown, for instance, but gives processes barely a few seconds to clean up and exit before SIGKILLing them. For other use cases you certainly can have a long-lived termination like you describe (e.g. with Upstart), but YMMV.

How to stop a child process if not completed after a timeout

In order to kill the child, you need to know its pid. You can get it if you start the program with fork and exec instead of system.

In addition to a signal handler for SIGALRM, set up one for SIGCHLD (received when a child process finishes) as well. After calling alarm to set the timer, call pause. This function will return when you get either of the two signals.

In the signal handlers you should only set a global flag. Calling printf from within a signal handler can lead to undefined behavior.

After pause returns, check each of the two flags. If the timeout flag is set, you can terminate the child with kill.

In either case, call wait to reap the child process's pid.

#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

#define TIME_LIMIT 1 //determine time limit

void alarm_handler(int);
void child_handler(int);

int timeout = 0;
int child_done = 0;

int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
// child process
execl("./r.out","r.out", NULL);
perror("exec failed");
_exit(1);
}

// set up the signal handlers after forking so the child doesn't inherit them

signal(SIGALRM, alarm_handler);
signal(SIGCHLD, child_handler);

alarm(TIME_LIMIT); // install an alarm to be fired after TIME_LIMIT
pause();

if (timeout) {
printf("alarm triggered\n");
int result = waitpid(pid, NULL, WNOHANG);
if (result == 0) {
// child still running, so kill it
printf("killing child\n");
kill(pid, 9);
wait(NULL);
} else {
printf("alarm triggered, but child finished normally\n");
}
} else if (child_done) {
printf("child finished normally\n");
wait(NULL);
}

return 0;
}


void child_handler(int sig)
{
child_done = 1;
}

void alarm_handler(int sig)
{
timeout = 1;
}


Related Topics



Leave a reply



Submit