App-Koyomi

 view release on metacpan or  search on metacpan

lib/App/Koyomi/Worker.pm  view on Meta::CPAN

package App::Koyomi::Worker;

use strict;
use warnings;
use 5.010_001;
use Class::Accessor::Lite (
    ro => [qw/ctx config schedule/],
);
use DateTime;
use Getopt::Long qw(:config posix_default no_ignore_case no_ignore_case_always);
use Log::Minimal env_debug => 'KOYOMI_LOG_DEBUG';
use Proc::Wait3;
use Smart::Args;
use Time::Piece;

use App::Koyomi::Context;
use App::Koyomi::Schedule;

use version; our $VERSION = 'v0.6.1';

sub new {
    args(
        my $class,
        my $config => +{ isa => 'Str',  optional => 1 },
        my $debug  => +{ isa => 'Bool', optional => 1 },
    );
    $ENV{KOYOMI_CONFIG_PATH} = $config if $config;
    $ENV{KOYOMI_LOG_DEBUG}   = 1       if $debug;

    my $ctx   = App::Koyomi::Context->instance;
    return bless +{
        ctx      => $ctx,
        config   => $ctx->config,
        schedule => App::Koyomi::Schedule->instance(ctx => $ctx),
    }, $class;
}

sub parse_args {
    my $class  = shift;
    my @args   = @_;

    Getopt::Long::GetOptionsFromArray(
        \@args, \my %opt, 'config|c=s',
        'debug|d', 'help|h', 'man',
    );
    return \%opt;
}

sub run {
    my $self = shift;

    my $now = $self->_now;

    ## main loop
    while (1) {
        $self->schedule->update($now);
        my @jobs = $self->schedule->get_jobs($now);

        for my $job (@jobs) {
            my $pid = fork();
            if ($pid == 0) { # child
                $job->proceed($now);
                exit;
            } elsif ($pid) { # parent
                # nothing to do
            } else {
                die "Can't fork: $!";
            }
        }

        my $prev_epoch = $now->epoch;
        $now->add(
            minutes => $self->config->{worker}{interval_minutes}
        )->truncate(to => 'minute');

        # Sleep to next tick
        my $min_seconds = $self->config->{worker}{minimum_interval_seconds};
        my $seconds = $now->epoch - $prev_epoch;
        if ($seconds < $min_seconds) {
            my $dsec = $min_seconds - $seconds;
            $seconds = $min_seconds;
            $now->add(seconds => $dsec);
        }
        if ($self->ctx->is_debug) {
            $seconds = $self->config->{debug}{worker}{sleep_seconds} // $seconds;
        }
        sleep($seconds);
        while (my @child_proc_info = wait3(0)) {
            debugf('Exit %d', $child_proc_info[0]);
        }
    }
}

sub _now {
    my $self = shift;

    my $debug_datestr = sub {
        return unless $self->ctx->is_debug;
        return $self->config->{debug}{now} // undef;
    }->();
    if ($debug_datestr) {
        my $t = Time::Piece->strptime($debug_datestr, '%Y-%m-%dT%H:%M');
        return DateTime->from_epoch(epoch => $t->epoch);
    }
    return $self->ctx->now;
}

1;

__END__

=encoding utf8

=head1 NAME

B<App::Koyomi::Worker> - koyomi worker module

=head1 SYNOPSIS

    use App::Koyomi::Worker;
    my $props = App::Koyomi::Worker->parse_args(@ARGV);
    App::Koyomi::Worker->new(%$props)->run;

=head1 DESCRIPTION

I<Koyomi> worker module.

=head1 METHODS

=over 4

=item B<new>

Construction.

=item B<run>

Runs worker.

=item B<parse_args> : HashRef

Parse command-line arguments.
Return option stash to construct the worker object or to show help.

=back

=head1 SEE ALSO

L<koyomi>,
L<App::Koyomi::Context>

=head1 AUTHORS

IKEDA Kiyoshi E<lt>progrhyme@gmail.comE<gt>

=head1 LICENSE

Copyright (C) 2015-2017 IKEDA Kiyoshi.

This library is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.  That means either (a) the GNU General Public
License or (b) the Artistic License.



( run in 0.845 second using v1.01-cache-2.11-cpan-ceb78f64989 )