A single-threaded schedule executor who does not miss scheduled task due to employment.
I wrote it for DBdumper, Network distributed dump/restore system
#!/usr/bin/env perl package aCron; use strict; use warnings; use POSIX qw(strftime); use Mojo::Util qw(dumper); sub new { my $class = shift; my %quantums; my %minutes; my $self = { quantums => \%quantums, minutes => \%minutes, quantum_wide => 2, minute_wide => 60, horizon => 7, }; bless $self, $class; return $self; } my %quantums; my %minutes; my $quantums = \%quantums; my $minutes = \%minutes; sub quantum_wide { my ($self, $wide) = @_; return $self->{quantum_wide} unless $wide; $self->{quantum_wide} = $wide; $self; } sub minute_wide { my ($self, $wide) = @_; return $self->{minute_wide} unless $wide; $self->{minute_wide} = $wide; $self; } sub horizon { my ($self, $wide) = @_; return $self->{horizon} unless $wide; $self->{horizon} = $wide; $self; } sub quantum { my ($self, $quantum, $status) = @_; return $self->{quantums}->{$quantum} unless $status; $self->{quantums}->{$quantum} = $status; $self; } sub minutes { my ($self, $min, $status) = @_; return $self->{minutes}->{$min} unless $status; $self->{minutes}->{$min} = $status; $self; } sub get_current_quantum { my $self = shift; int(time/$self->quantum_wide); } sub min { my ($self, $time) = @_; strftime("%M", localtime($time)); } sub hour { my ($self, $time) = @_; strftime("%H", localtime($time)); } sub wday { my ($self, $time) = @_; strftime("%u", localtime($time)); } sub mday { my ($self, $time) = @_; strftime("%d", localtime($time)); } sub expand { my ($self, $def, $start, $limit) = @_; $limit = 100 unless defined $limit; $start = 1 unless defined $start; $def =~ s/\s/,/g; $def =~ s/,,/,/g; my @list = split ',', $def; my @out; my %n; foreach my $sub (@list) { if (my ($num) = $sub =~ m/^(\d+)$/ ) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $n{$num} = 1; } elsif (my ($begin, $end) = $sub =~ /^(\d+)[-. ]+(\d+)$/) { foreach my $num ($begin..$end) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $n{$num} = 1; } } elsif (my ($inc) = $sub =~ /^[*]\/(\d+)$/) { my $num = $start-1; while ($num <= $limit - $inc) { $num += $inc; next if $num < $start; push @out, $num unless $n{$num}; $n{$num} = 1; } } elsif ($sub =~ /^[*]$/) { my $num = $start; my $inc = 1; while ($num <= $limit) { next if $num < $start; next if $num > $limit; push @out, $num unless $n{$num}; $n{$num} = 1; $num += $inc; } } } @out = sort {$a <=> $b} @out; return \@out } sub compact { my ($self, $list) = @_; my %n; my $out; foreach my $num (@{$list}) { $n{$num} = 1; } foreach my $num (sort { $a <=> $b } (keys %n)) { $out .= $num."-" if $n{$num + 1} && not $n{$num - 1}; $out .= $num."," unless $n{$num+1}; } $out =~ s/,$//; return $out; } sub match { my ($self, $num, $spec, $start, $end) = @_; my $list = $self->expand($spec, $start, $end); foreach my $rec (@$list) { return $rec if $num == $rec; } return undef; } 1; use strict; use Mojo::Util qw(dumper); use Mojo::IOLoop; my $cr = aCron->new; sub do_quantum { my $current_quantum = $cr->get_current_quantum; my $quantum_status = $cr->quantum($current_quantum) || 'undef'; return 1 if $quantum_status eq 'done'; # some do for this quantum my $quantum_sec = $current_quantum * $cr->quantum_wide; my $current_min = int($quantum_sec / $cr->minute_wide); # the time machine foreach my $old_min (($current_min - $cr->horizon)..$current_min) { my $minute_status = $cr->minutes($old_min) || 'undef'; next if ($minute_status eq 'done'); my $time = $old_min * $cr->minute_wide; my $min = $cr->min($time); my $hour = $cr->hour($time); my $wday = $cr->wday($time); my $mday = $cr->mday($time); # some do for the past minute sleep int(rand 5); print "old_min=$old_min $hour:$min\n"; $cr->minutes($old_min, 'done'); } $cr->quantum($current_quantum, 'done'); } my $loop = Mojo::IOLoop->singleton; my $id = $loop->recurring( 1 => \&do_quantum ); $loop->start unless $loop->is_running; #EOF
Horizon (retro look) set to 7 minutes.
# date Fri 12 Jan 2018 13:58:27 EET # ./cron2.pl old_min=25262631 13:51 old_min=25262632 13:52 old_min=25262633 13:53 old_min=25262634 13:54 old_min=25262635 13:55 old_min=25262636 13:56 old_min=25262637 13:57 old_min=25262638 13:58