The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Ukigumo::Agent::Manager;
use strict;
use warnings;
use utf8;
use Ukigumo::Client;
use Ukigumo::Client::VC::Git;
use Ukigumo::Client::Executor::Perl;

use Mouse;

has 'children' => ( is => 'rw', default => sub { +{ } } );
has 'work_dir' => ( is => 'rw', isa => 'Str', required => 1 );
has 'server_url' => ( is => 'rw', isa => 'Str', required => 1 );
has job_queue => (is => 'ro', default => sub { +[ ] });
has max_children => ( is => 'ro', default => 1 );
has timeout => (is => 'rw', isa => 'Int', default => 0);

no Mouse;

use constant SIGKILL => 9;

sub count_children {
    my $self = shift;
    0+(keys %{$self->children});
}

sub push_job {
    my ($self, $job) = @_;
    push @{$self->{job_queue}}, $job;
}

sub pop_job {
    my ($self, $job) = @_;
    pop @{$self->{job_queue}};
}

sub run_job {
    my ($self, $args) = @_;
    Carp::croak "Missing args" unless $args;

    my $pid = fork();
    if (!defined $pid) {
        die "Cannot fork: $!";
    }

    my $repository = $args->{repository} || die;
    my $branch     = $args->{branch} || die;

    my $timeout_timer;

    my $client;

    if ($pid) {
        print "Spawned $pid\n";
        $self->{children}->{$pid} = +{
            child => AE::child($pid, sub {
                my ($pid, $status) = @_;

                undef $timeout_timer;

                # Process has killed because it was timeout
                if ($status == SIGKILL) {
                    $client->report_timeout;
                    return;
                }

                print "[child exit] pid: $pid, status: $status\n";
                delete $self->{children}->{$pid};

                if ($self->count_children < $self->max_children && @{$self->job_queue} > 0) {
                    print "[child exit] run new job\n";
                    $self->run_job($self->pop_job);
                } else {
                    print "[child exit] There is no jobs. sleep...\n";
                }
            }),
            job => $args,
            start => time(),
        };
        my $timeout = $self->timeout;
        if ($timeout > 0) {
            $timeout_timer = AE::timer $timeout, 0, sub {
                kill SIGKILL, $pid;
            };
        }
    } else {
        eval {
            my $vc = Ukigumo::Client::VC::Git->new(
                branch => $branch,
                repository => $repository,
            );
            $client = Ukigumo::Client->new(
                workdir     => $self->work_dir,
                vc          => $vc,
                executor    => Ukigumo::Client::Executor::Perl->new(),
                server_url  => $self->server_url,
                compare_url => $args->{compare_url},
                repository_owner => $args->{repository_owner},
                repository_name  => $args->{repository_name},
            );
            $client->run();
        };
        print "[child] error: $@\n" if $@;
        print "[child] finished to work\n";
        exit;
    }
}

sub register_job {
    my ($self, $params) = @_;

    if ($self->count_children < $self->max_children) {
        # run job.
        $self->run_job($params);
    } else {
        $self->push_job($params);
    }
}

1;