Exit perl script automatically every 2 hours

387 Views Asked by At

I have a perl scipt that I need it to end every two hours on a Linux machine. I was going to do a separate script and add it on cron.d to accomplish this, but I wanted an easier way. It has to do a graceful exit because after doing a CTRL+C, it writes a log file and killing it won't write the file.

3

There are 3 best solutions below

0
On

A wrapper keeps things simple.

#!/usr/bin/perl

# usage:
#    restarter program [arg [...]]

use strict;
use warnings;

use IPC::Open3 qw( open3 );
use POSIX      qw( WNOHANG );


use constant RESTART_AFTER  => 2*60*60;
use constant KILL_INT_WAIT  => 30;
use constant KILL_TERM_WAIT => 30;
use constant WAIT_POLL      => 15;


sub start_it {
   open(local *NULL, '<', '/dev/null')
      or die($!);

   return open3('<&NULL', '>&STDOUT', '>&STDERR', @_);
}


sub wait_for_it {
   my ($pid, $max_wait) = @_;

   my $end_time = time + $max_wait;

   while (1) {
      if (waitpid($pid, WNOHANG) > 0) {
         return 1;
      }

      my $time = time;
      if ($end_time >= $time) {
         return 0;
      }

      sleep(1);
   }
}


sub end_it {
   my ($pid) = @_;
   kill(INT => $pid)
      or die($!);

   return if wait_for_it($pid, KILL_INT_WAIT);

   kill(TERM => $pid)
      or die($!);

   return if wait_for_it($pid, KILL_TERM_WAIT);

   kill(KILL => $pid)
      or die($!);

   waitpid($pid, 0);
}


sub run_it {
   my $end_time = time + RESTART_AFTER;

   my $pid = start_it(@_);

   while (1) {
      if (waitpid($pid, WNOHANG) > 0) {
         last;
      }

      my $time = time;
      if ($end_time >= $time) {
         end_it($pid);
         last;
      }

      my $sleep_time = $end_time - $time;
      $sleep_time = WAIT_POLL if $sleep_time > WAIT_POLL;  # Workaround for race condition.
      sleep($sleep_time);
   }

   my $status = $?;

   if    ($? & 0x7F) { warn("Child killed by signal ".($? & 0x7F)."\n"); }
   elsif ($? >> 8)   { warn("Child exited with error ".($? >> 8)."\n"); }
   else              { warn("Child exited with succcesfully.\n"); }
}


run_it(@ARGV) while 1;

You might want to forward signals sent to the handler to the child.

2
On

You can catch the signal sent by Ctrl-C by setting a subroutine in $SIG{INT}:

$ perl -e '$SIG{INT} = sub { print "Caught signal, cleaning up\n"; exit 0 }; while(1) {}'

Do your cleanup within the sub, and there you go.

3
On

You can set up an alarm at the beginning of the script, and provide a handler for it:

alarm 60 * 60 * 2;
local $SIG{ALRM} = sub {
    warn "Time over!\n";
    # Do the logging here...
    exit
};

The question is how you would restart the script again.