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

Hi all,

I'm trying to write a daemon to emulate the cron scheduler for a collection of miscellaneous Perl scripts lying around our servers. Essentially, crontab entries will be stored in a database table and this daemon should poll the db every so often to refresh my psuedo crontab file. Once a minute, it will determine which scripts to run, and fork off a new process to exec() it.

There are two reasons we're not using cron here. First is that we don't want to allow people to edit crontabs willy-nilly as they create custom scripts they want run periodically. And secondly, as far as I know, cron doesn't have any sort of logging feature. We need to track what scripts ran, what kind of exit codes they produced when (if) they completed, etc. in the case of database or server outages. It'll make our lives a WHOLE lot easier if we could track down what needed to be rerun and why. Right now, no such mechanism is in place and system downtime is a disaster.

Finally, my question. This daemon wakes up once a minute and forks off processes as necessary. The problem are the incoming SIGCHLD signals the daemon receives during this time and, more critically, while its sleeping off the remainder of the minute before the next iteration. If I set $SIG{CHLD} to 'IGNORE' I don't have to worry about reaping the children as they die, but I also can't track exit statuses. However, if I set up any kind of handler, it can interrupt the process as it sleeps or at any other time, even with safe signals in 5.8.7.

So, is there a clean way to avoid this problem? Ideally, I'd like a system that can avoid defunct processes sitting around in the process table at all, but that's livable provided they're reaped and their exit statuses returned to me in a timely fashion.

There are two approaches that I've thought of, but can't seem to implement either. Any advice would be helpful:

1) A dedicated signal handling thread. I know of the Thread::Signal module for 5005threads, but is there a similar technique for Perl's ithreads? If the daemon process could redirect all incoming CHLD signals to a dedicated handling thread without interrupting its rest, that would be terrific. I don't actually need to go through the exit statuses immediately, but I'd like to reap the children as soon as possible. I can let the exit statuses sit in a hash until I get around to it in the next iteration.

-or-

2) Call a reaper function at the beginning of each minute's iteration to clean up any processes that have completed since the last harvest. Is there a way to defer any CHLD signals until after the daemon has forked off any necessary children and slept? This is the only way I could foresee this particular approach being successful, though again I'm open to suggestion. I've tried using sigprocmask, but it seems finicky at best as the signals I know have been generated are skipped for at least one additional iteration before they're received.

Clearly option 1 is the cleanest approach, but is it possible to accomplish?

Thanks for all of your help,
--overbyte

Replies are listed 'Best First'.
Re: SIGCHLD and sleep()
by dave_the_m (Monsignor) on Aug 09, 2005 at 18:33 UTC
    If your main concern is the sleep(60) getting interuppted too soon, just put it in a loop, eg (untested)
    my $start = time; while ( (my $diff = time - $start) < 60) { sleep(60-$diff) }

    Dave.

      Thanks, I was making the process of maintaining the actual time slept versus how much time had elapsed during a particular iteration of the loop entirely too complicated. The simple loop does ensure the daemon sleeps for the correct amount of time.

      However, this does leave defunct processes sitting in the process table until the daemon interates again and I reap the processes. Which I can live with, but ideally I'd like to know if it is possible to avoid.

      Is there a way to create a thread to handle the signals immediately and store the exit statuses in a shared hash? I don't know if I should be mixing ithreads and forking to begin with, but that would be the perfect solution to my problems.

      Thanks again,
      overbyte

        You should be able to the reaping in a SIGCHLD handler, which upon exit, returns you to the sleep loop. Or include the reaping within the timing loop, eg
        while (1) { while (remaining) { sleep(remaining); while (($pid = wait) != -1) { ... do reaping stuff } } ... check for new jobs and spawn them ... }

        Dave.