File Coverage

blib/lib/Ukigumo/Agent/Manager.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Ukigumo::Agent::Manager;
2 5     5   435029 use strict;
  5         8  
  5         198  
3 5     5   26 use warnings;
  5         11  
  5         142  
4 5     5   25 use utf8;
  5         8  
  5         32  
5 5     5   2679 use Ukigumo::Agent::Cleaner qw/cleanup_old_branch_dir/;
  5         47  
  5         316  
6 5     5   3795 use Ukigumo::Client;
  5         697552  
  5         224  
7 5     5   4055 use Ukigumo::Client::VC::Git;
  5         37728  
  5         158  
8 5     5   4255 use Ukigumo::Client::Executor::Perl;
  5         3888  
  5         204  
9 5     5   31 use Ukigumo::Helper qw/normalize_path/;
  5         39  
  5         247  
10 5     5   28 use Ukigumo::Logger;
  5         10  
  5         106  
11 5     5   518740 use Coro;
  0            
  0            
12             use Coro::AnyEvent;
13             use POSIX qw/SIGTERM SIGKILL/;
14             use File::Spec;
15             use Carp ();
16              
17             use Mouse;
18              
19             has 'config' => (
20             is => 'rw',
21             isa => 'HashRef',
22             lazy => 1,
23             default => sub { +{} },
24             );
25              
26             has 'work_dir' => (
27             is => 'ro',
28             isa => 'Str',
29             lazy => 1,
30             default => sub { shift->config->{work_dir} // File::Spec->tmpdir },
31             );
32              
33             has 'server_url' => (
34             is => 'ro',
35             isa => 'Str',
36             lazy => 1,
37             default => sub { shift->config->{server_url} },
38             );
39              
40             has 'timeout' => (
41             is => 'ro',
42             isa => 'Int',
43             lazy => 1,
44             default => sub { shift->config->{timeout} // 0 },
45             );
46              
47             has 'ignore_github_tags' => (
48             is => 'ro',
49             isa => 'Bool',
50             lazy => 1,
51             default => sub { shift->config->{ignore_github_tags} // 0 },
52             );
53              
54             has 'force_git_url' => (
55             is => 'ro',
56             isa => 'Bool',
57             lazy => 1,
58             default => sub { shift->config->{force_git_url} // 0 },
59             );
60              
61             has 'max_children' => (
62             is => 'ro',
63             isa => 'Int',
64             lazy => 1,
65             default => sub { shift->config->{max_children} // 1 },
66             );
67              
68             has 'cleanup_cycle' => (
69             is => 'ro',
70             isa => 'Int',
71             lazy => 1,
72             default => sub { shift->config->{cleanup_cycle} || 0 },
73             );
74              
75             has 'job_queue' => (
76             is => 'ro',
77             isa => 'ArrayRef',
78             default => sub { +[] },
79             );
80              
81             has 'children' => (
82             is => 'rw',
83             isa => 'HashRef',
84             default => sub { +{} },
85             );
86              
87             has 'logger' => (
88             is => 'ro',
89             isa => 'Ukigumo::Logger',
90             lazy => 1,
91             default => sub { Ukigumo::Logger->new },
92             );
93              
94             no Mouse;
95              
96             sub count_children {
97             my $self = shift;
98             0+(keys %{$self->children});
99             }
100              
101             sub push_job {
102             my ($self, $job) = @_;
103             push @{$self->{job_queue}}, $job;
104             }
105              
106             sub pop_job {
107             my ($self, $job) = @_;
108             pop @{$self->{job_queue}};
109             }
110              
111             sub run_job {
112             my ($self, $args) = @_;
113             Carp::croak("Missing args") unless $args;
114              
115             my $repository = $args->{repository} || die;
116             my $branch = $args->{branch} || die;
117              
118             my $vc = Ukigumo::Client::VC::Git->new(
119             branch => $branch,
120             repository => $repository,
121             );
122             my $client = Ukigumo::Client->new(
123             workdir => $self->work_dir,
124             vc => $vc,
125             executor => Ukigumo::Client::Executor::Perl->new(),
126             server_url => $self->server_url,
127             compare_url => $args->{compare_url} || '',
128             repository_owner => $args->{repository_owner} || '',
129             repository_name => $args->{repository_name} || '',
130             );
131              
132             my $client_log_filename = $client->logfh->filename;
133              
134             my $timeout_timer;
135              
136             my $pid = fork();
137             if (!defined $pid) {
138             die "Cannot fork: $!";
139             }
140              
141             if ($pid) {
142             $self->logger->infof("Spawned $pid");
143             $self->{children}->{$pid} = +{
144             child => AE::child($pid, unblock_sub {
145             my ($pid, $status) = @_;
146              
147             undef $timeout_timer;
148              
149             # Process has terminated because it was timeout
150             if ($status == SIGTERM) {
151             Coro::AnyEvent::sleep 5;
152             if (kill 0, $pid) {
153             # Process is still alive
154             kill SIGTERM, $pid;
155             Coro::AnyEvent::sleep 5;
156             if (kill 0, $pid) {
157             # The last resort
158             kill SIGKILL, $pid;
159             }
160             }
161             $self->logger->warnf("[child] timeout");
162             eval { $client->report_timeout($client_log_filename) };
163             if ($@) {
164             $self->logger->warnf("[child] fail on sending timeout report: $@");
165             }
166             }
167              
168             $self->logger->infof("[child exit] pid: $pid, status: $status");
169             delete $self->{children}->{$pid};
170              
171             if ($self->count_children < $self->max_children && @{$self->job_queue} > 0) {
172             $self->logger->infof("[child exit] run new job");
173             $self->run_job($self->pop_job);
174             } else {
175             $self->_take_a_break();
176             }
177             }),
178             job => $args,
179             start => time(),
180             };
181             my $timeout = $self->timeout;
182             if ($timeout > 0) {
183             $timeout_timer = AE::timer $timeout, 0, sub {
184             kill SIGTERM, $pid;
185             };
186             }
187             } else {
188             eval { $client->run() };
189             $self->logger->warnf("[child] error: $@") if $@;
190              
191             if (my $cleanup_cycle = $self->cleanup_cycle) {
192             my $project_dir = File::Spec->catfile($client->workdir, normalize_path($client->project));
193             cleanup_old_branch_dir($project_dir, $cleanup_cycle);
194             }
195              
196             $self->logger->infof("[child] finished to work");
197             exit;
198             }
199             }
200              
201             sub register_job {
202             my ($self, $params) = @_;
203              
204             if ($self->count_children < $self->max_children) {
205             # run job.
206             $self->run_job($params);
207             } else {
208             $self->push_job($params);
209             }
210             }
211              
212             sub _take_a_break {
213             my ($self) = @_;
214             $self->logger->infof("[child exit] There is no jobs. sleep...");
215             }
216              
217             1;