From 89a396a1adc7fbdbbf6b82f1ead02ed7bffa446f Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@4701ed2b-b243-491d-98bb-9a9ce286683a> Date: Fri, 20 Feb 2009 14:16:38 +0000 Subject: --- CHANGES | 57 ++ ChangeLog | 84 +++ Cron.pm | 1592 ++++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST | 15 + META.yml | 11 + Makefile.PL | 12 + README | 150 +++++ svn-commit.tmp | 4 + t/callbackreschedule.t | 41 ++ t/entry.t | 71 +++ t/execution_time.t | 146 +++++ t/load_crontab.t | 51 ++ t/nofork.t | 99 +++ t/sighandler.t | 31 + t/startup.t | 34 ++ t/test.crontab | 69 +++ 16 files changed, 2467 insertions(+) create mode 100644 CHANGES create mode 100644 ChangeLog create mode 100644 Cron.pm create mode 100644 MANIFEST create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100644 svn-commit.tmp create mode 100644 t/callbackreschedule.t create mode 100644 t/entry.t create mode 100644 t/execution_time.t create mode 100644 t/load_crontab.t create mode 100644 t/nofork.t create mode 100644 t/sighandler.t create mode 100644 t/startup.t create mode 100644 t/test.crontab diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..64ceb41 --- /dev/null +++ b/CHANGES @@ -0,0 +1,57 @@ +0.97 + +- Use POSIX only where available, otherwise fallback to an emulation of waipid. +- Fixed bug when previous SIGCHLD handler was not a coderef, but a String value like + "DEFAULT" or "IGNORE" +- Added tests +- Made test more robust so they work now also on system without alarm() functionality + (Win32) + +0.96 + +- Applied patch for #4917 in order to be smarter to existing SIGCHLD handler and to + reap only own childs. + + +0.95 + +- If a scheduled method in 'nofork' manipulates the execution queue with + add_entry or delete_entry, it will be picked up during the next run. + +- Clarified different behaviour of global variables within the fork/norfork + mode in the documentation + +- Fixed warning about non-numeric arguments when sorting. + +- Fixed bug which called to an undefined methods in a die-message + +- Pretty print a hashref in $0 if provided as argument and the + prefix for the name to be shown in the process list can be configured + with the option "processprefix" + +0.9 + +- Logging: It is now possible to add a reference to a custom logging + subroutine to the constructor which will be used for logging certain + events. +- NoFork: The option 'nofork' prevents Cron.pm from creating a new + child process. Instead, the job is run within the current + process. You can use the 'skip' and 'catch' options to tune the + behaviour. +- Seconds granularity: Cron times can now be specified up to the + second. +- Bugfix: Thinks like "*/5" now work like expected + +0.05 + +- Other bugfixes for parsedate problem with single digit hours/minutes + and warnings if argumentlist of command to execute is empty + +0.03 + +- Minor bug fixes (reaping of child processes improved, + fixed regexp in get_next_execution_time()) + +0.01 + +- Initial Release diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..0cfc50c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,84 @@ +2006-11-27 Roland Huss + + * Cron.pm (REAPER): Added support for plattforms where POSIX is + not available. + (run): Call previous childhandler only if it is a coderef + +2006-11-08 Roland Huss + + * Cron.pm (run): Removed leading space when no process prefix is + used (for backwards compatibility) + +2006-11-05 Roland Huss + + * CHANGES: added long forgotten patch for child process handling + +2006-11-05 Roland Huss + + * CHANGES: added patches and suggestions from + - Andrew Danforth + - Frank Mayer + - Jamie McCarthy + - Andy Ford + + Thanks ! + +2006-11-04 Roland Huss + + * Cron.pm: Worked on: + - Reexamination of crontabs entry in 'nofork' mode if someone + has added a new entry + +2004-01-30 Roland Huss + + * t/entry.t (Module): added and extended tests + +2004-01-29 Roland Huss + + * Cron.pm (add_entry): added heuristic for parsing crontab with 6 + time columns + +2004-01-28 Roland Huss + + * Cron.pm (new): added new options 'nofork', 'skip', 'catch' and + 'log' + (get_next_execution_time): allow a sixth column for specifing the + second to start up + +2002-08-09 Roland Huss + + * Cron.pm (get_next_execution_time): added recognition of "*/5" + notations (thanks to Loic Paillotin for spotting this problem) + +2002-04-02 Roland Huss + + * Released 0.0.5 + +2000-07-05 Roland Huss + + * Cron.pm: added patch from Lars Holokowo for working around a bug + in parsedate, which has trouble in parsing times in the form "3:1 + 2000/6/30". Added tests to check for those dates + +2000-06-14 Roland Huss + + * Cron.pm: added additional check for arguments to avoid warnings + as suggested by David Parker + + * Makefile.PL: added check for Time::ParseDate as suggested by + Philippe Verdret + +2000-06-12 Roland Huss + + * Cron.pm: Fixed bug in regexp splitting the crontab entry in + get_next_execution_time() report by Peter Vary + +2000-03-23 Roland Huss + * Cron.pm: fixed problem when reaping childs: Now SIGCHLD handler + can handle more than one finished child at once (thanx to Bray + Jones for discovering this bug) + +2000-01-02 Roland Huss + + * Initial release 0.001 + diff --git a/Cron.pm b/Cron.pm new file mode 100644 index 0000000..21ae607 --- /dev/null +++ b/Cron.pm @@ -0,0 +1,1592 @@ +#!/usr/bin/perl -w + +# $Id: Cron.pm,v 1.16 2006/11/27 13:42:52 roland Exp $ + +=head1 NAME + +Cron - cron-like scheduler for Perl subroutines + +=head1 SYNOPSIS + + use Schedule::Cron; + + # Subroutines to be called + sub dispatcher { + print "ID: ",shift,"\n"; + print "Args: ","@_","\n"; + } + + sub check_links { + # do something... + } + + # Create new object with default dispatcher + my $cron = new Schedule::Cron(\&dispatcher); + + # Load a crontab file + $cron->load_crontab("/var/spool/cron/perl"); + + # Add dynamically crontab entries + $cron->add_entry("3 4 * * *",ROTATE => "apache","sendmail"); + $cron->add_entry("0 11 * * Mon-Fri",\&check_links); + + # Run scheduler + $cron->run(detach=>1); + + +=head1 DESCRIPTION + +This module provides a simple but complete cron like scheduler. I.e this +modules can be used for periodically executing Perl subroutines. The dates and +parameters for the subroutines to be called are specified with a format known +as crontab entry (L<"METHODS">, C and L) + +The philosophy behind C is to call subroutines periodically +from within one single Perl program instead of letting C trigger several +(possibly different) perl scripts. Everything under one roof. Furthermore +C provides mechanism to create crontab entries dynamically, +which isn't that easy with C. + +C knows about all extensions (well, at least all extensions I'm +aware of, i.e those of the so called "Vixie" cron) for crontab entries like +ranges including 'steps', specification of month and days of the week by name +or coexistence of lists and ranges in the same field. And even a bit more +(like lists and ranges with symbolic names). + +=head1 METHODS + +=over 4 + +=cut + +#' + +package Schedule::Cron; + +use Time::ParseDate; +use Data::Dumper; + +use strict; +use vars qw($VERSION $DEBUG); +use subs qw(dbg); + +my $HAS_POSIX; + +BEGIN { + eval { + require POSIX; + import POSIX ":sys_wait_h"; + }; + $HAS_POSIX = $@ ? 0 : 1; +} + + +$VERSION = 0.97; + +our $DEBUG = 0; +my %STARTEDCHILD = (); + +my @WDAYS = qw( + Sunday + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday + ); + +my @ALPHACONV = ( + { }, + { }, + { }, + { qw(jan 1 feb 2 mar 3 apr 4 may 5 jun 6 jul 7 aug 8 + sep 9 oct 10 nov 11 dec 12) }, + { qw(sun 0 mon 1 tue 2 wed 3 thu 4 fri 5 sat 6)}, + { } + ); +my @RANGES = ( + [ 0,59 ], + [ 0,23 ], + [ 0,31 ], + [ 0,12 ], + [ 0,7 ], + [ 0,60 ] + ); + +my @LOWMAP = ( + {}, + {}, + { 0 => 1}, + { 0 => 1}, + { 7 => 0}, + {}, + ); + +sub REAPER { + if ($HAS_POSIX) + { + # Only on platforms supporting POSIX semantisc + foreach my $pid (keys %STARTEDCHILD) { + my $res = $HAS_POSIX ? waitpid($pid, WNOHANG) : waitpid($pid,0); + if ($res > 0) { + # We reaped a truly running process + delete $STARTEDCHILD{$pid}; + } + } + } + else + { + my $waitedpid = 0; + while($waitedpid != -1) { + $waitedpid = wait; + } + } +} + +=item $cron = new Schedule::Cron($dispatcher,[extra args]) + +Creates a new C object. C<$dispatcher> is a reference to a subroutine, +which will be called by default. C<$dispatcher> will be invoked with the +arguments parameter provided in the crontab entry if no other subroutine is +specified. This can be either a single argument containing the argument +parameter literally has string (default behavior) or a list of arguments when +using the C option described below. + +The date specifications must be either provided via a crontab like file or +added explicitly with C (L<"add_entry">). + +I can be a hash or hash reference for additional arguments. The +following parameters are recognized: + +=over + +=item file => + + +Load the crontab entries from + +=item eval => 1 + +Eval the argument parameter in a crontab entry before calling the subroutine +(instead of literally calling the dispatcher with the argument parameter as +string) + +=item nofork => 1 + +Don't fork when starting the scheduler. Instead, the jobs are executed within +current process. In your executed jobs, you have full access to the global +variables of your script and hence might influence other jobs running at a +different time. This behaviour is fundamentally different to the 'fork' mode, +where each jobs gets its own process and hence a B of the process space, +independent of each other job and the main process. This is due to the nature +of the C system call. + +=item skip => 1 + +Skip any pending jobs whose time has passed. This option is only useful in +combination with C where a job might block the execution of the +following jobs for quite some time. By default, any pending job is executed +even if its scheduled execution time has already passed. With this option set +to true all pending which would have been started in the meantime are skipped. + +=item catch => 1 + +Catch any exception raised by a job. This is especially useful in combination with +the C option to avoid stopping the main process when a job raises an +exception (dies). + +=item log => \&log_sub + +Install a logging subroutine. The given subroutine is called for several events +during the lifetime of a job. This method is called with two arguments: A log +level of 0 (info),1 (warning) or 2 (error) depending on the importance of the +message and the message itself. + +For example, you could use I (L) for logging +purposes for example like in the following code snippet: + + use Log::Log4perl; + use Log::Log4perl::Level; + + my $log_method = sub { + my ($level,$msg) = @_; + my $DBG_MAP = { 0 => $INFO, 1 => $WARN, 2 => $ERROR }; + + my $logger = Log::Log4Perl->get_logger("My::Package"); + $logger->log($DBG_MAP->{$level},$msg); + } + + my $cron = new Schedule::Cron(.... , log => $log_method); + +=item processprefix => + +Cron::Schedule sets the process' name (i.e. C<$0>) to contain some informative +messages like when the next job executes or with which arguments a job is +called. By default, the prefix for this labels is C. With this +option you can set it to something different. You can e.g. use C<$0> to include +the original process name. + +=back + +=cut + +sub new +{ + my $class = shift; + my $dispatcher = shift || die "No dispatching sub provided"; + die "Dispatcher not a ref to a subroutine" unless ref($dispatcher) eq "CODE"; + my $cfg = ref($_[0]) eq "HASH" ? $_[0] : { @_ }; + $cfg->{processprefix} = "Schedule::Cron" unless $cfg->{processprefix}; + my $self = { + cfg => $cfg, + dispatcher => $dispatcher, + queue => [ ], + map => { } + }; + bless $self,(ref($class) || $class); + + $self->load_crontab if $cfg->{file}; + $self; +} + +=item $cron->load_crontab($file) + +=item $cron->load_crontab(file=>$file,[eval=>1]) + +Loads and parses the crontab file C<$file>. The entries found in this file will +be B to the current time table with C<$cron-Eadd_entry>. + +The format of the file consists of cron commands containing of lines with at +least 5 columns, whereas the first 5 columns specify the date. The rest of the +line (i.e columns 6 and greater) contains the argument with which the +dispatcher subroutine will be called. By default, the dispatcher will be +called with one single string argument containing the rest of the line +literally. Alternatively, if you call this method with the optional argument +C1> (you must then use the second format shown above), the rest of +the line will be evaled before used as argument for the dispatcher. + +For the format of the first 5 columns, please see L<"add_entry">. + +Blank lines and lines starting with a C<#> will be ignored. + +There's no way to specify another subroutine within the crontab file. All +calls will be made to the dispatcher provided at construction time. + +If you want to start up fresh, you should call +C<$cron-Eclean_timetable()> before. + +Example of a crontab fiqw(le:) + + # The following line runs on every Monday at 2:34 am + 34 2 * * Mon "make_stats" + # The next line should be best read in with an eval=>1 argument + * * 1 1 * { NEW_YEAR => '1',HEADACHE => 'on' } + +=cut + +#' + +sub load_crontab +{ + my $self = shift; + my $cfg = shift; + + if ($cfg) + { + if (@_) + { + $cfg = ref($cfg) eq "HASH" ? $cfg : { $cfg,@_ }; + } + elsif (!ref($cfg)) + { + my $new_cfg = { }; + $new_cfg->{file} = $cfg; + $cfg = $new_cfg; + } + } + + my $file = $cfg->{file} || $self->{cfg}->{file} || die "No filename provided"; + my $eval = $cfg->{eval} || $self->{cfg}->{eval}; + + open(F,$file) || die "Cannot open schedule $file : $!"; + my $line = 0; + while () + { + $line++; + next if /^$/; + next if /^\s*#/; + chomp; + s/\s*(.*)\s*$/$1/; + my ($min,$hour,$dmon,$month,$dweek,$rest) = split (/\s+/,$_,6); + + my $time = [ $min,$hour,$dmon,$month,$dweek ]; + + # Try to check, whether an optional 6th column specifying seconds + # exists: + my $args; + if ($rest) + { + my ($col6,$more_args) = split(/\s+/,$rest,2); + if ($col6 =~ /^[\d\-\*\,\/]+$/) + { + push @$time,$col6; + dbg "M: $more_args"; + $args = $more_args; + } + else + { + $args = $rest; + } + } + $self->add_entry($time,{ 'args' => $args, 'eval' => $eval}); + } + close F; +} + +=item $cron->add_entry($timespec,[arguments]) + +Adds a new entry to the list of scheduled cron jobs. + +B