3 # jobd - perform Girocco maintenance jobs
5 # Run with --help for details
22 my $lockfile = "/tmp/jobd.lock";
30 my $p = $job->{'project'};
31 check_project_exists
($job) || return;
32 (-e
"$Girocco::Config::reporoot/$p.git/.nofetch") && do {
36 exec_job_command
($job, ["$Girocco::Config::basedir/jobd/update.sh", $p], $quiet);
41 my $p = $job->{'project'};
42 check_project_exists
($job) || return;
43 exec_job_command
($job, ["$Girocco::Config::basedir/jobd/gc.sh", $p], $quiet);
49 project
=> $job->{'project'},
51 command
=> \
&gc_project
,
55 sub check_project_exists
{
57 my $p = $job->{'project'};
58 if (!-d
"$Girocco::Config::reporoot/$p.git") {
59 error
("Warning: skipping non-existent project: $job->{project}")
72 command
=> \
&update_project
,
73 on_success
=> \
&setup_gc
,
74 on_error
=> \
&setup_gc
,
79 queue_one
($_) for (Girocco
::Project
->get_full_list());
82 ######### Daemon operation {{{1
92 error
("Waiting for outstanding jobs to finish... ".
93 "^C again to exit immediately");
96 $SIG{'INT'} = \
&handle_exit
;
100 error
("Killing outstanding jobs...");
101 $SIG{'CHLD'} = 'IGNORE';
102 $SIG{'TERM'} = 'IGNORE';
104 kill 'KILL', $_->{'pid'};
106 unlink $lockfile if ($locked);
110 sub handle_childgone
{
113 my $child = grep { $_->{'pid'} == $pid } @running;
115 # XXX- we currently don't care
117 $child->{'finished'} = 2;
121 $SIG{'CHLD'} = \
&handle_childgone
;
126 $opts{'queued_at'} = time;
134 $job->{'command'}->($job);
139 "[".$job->{'type'}."::".$job->{'project'}."]";
142 # Only one of those per job!
143 sub exec_job_command
{
144 my ($job, $command, $err_only) = @_;
147 if (!defined($pid = fork)) {
148 error
(_job_name
($job) ." Can't fork job: $!");
149 $job->{'finished'} = 1;
154 open STDOUT
, '>/dev/null' || do {
155 error
(_job_name
($job) ." Can't write to /dev/null: $!");
156 $job->{'finished'} = 1;
163 $job->{'pid'} = $pid;
164 $job->{'finished'} = 0;
165 $job->{'started_at'} = time;
170 exec_job_command
($job, ['/bin/false']);
173 sub reap_hanging_jobs
{
175 if ((time - $_->{'started_at'}) > $kill_after) {
176 $_->{'finished'} = 1;
177 kill 'KILL', $_->{'pid'};
178 print STDERR _job_name
($_) ." KILLED due to timeout\n";
179 push @jobs_killed, _job_name
($_);
184 sub reap_finished_jobs
{
186 my $status = $_->{'finished'};
187 if ($status == 0) { next; }
188 elsif ($status == 1 && defined($_->{'on_error'})) {
189 $_->{'on_error'}->($_);
190 } elsif ($status == 2 && defined($_->{'on_success'})) {
191 $_->{'on_success'}->($_);
194 @running = grep { $_->{'finished'} == 0 } @running;
202 printf STDERR
"--- Processing %d queued jobs\n", scalar(@queue);
204 $SIG{'CHLD'} = \
&handle_childgone
;
205 while (@queue || @running) {
207 reap_finished_jobs
();
208 # Back off if we're too busy
209 if (@running >= $max_par) {
212 unless (($quiet && !$progress) || ($queue_steps % 10)) {
213 printf STDERR
"STATUS: %d queued, %d running, %d finished, %d killed\n", scalar(@queue), scalar(@running), $jobs_executed, scalar(@jobs_killed);
218 run_job
(shift(@queue)) if @queue;
221 printf STDERR
"--- Queue processed. %d jobs executed, %d killed due to timeouts. Now restarting.\n", $jobs_executed, scalar(@jobs_killed);
225 sub run_perpetually
{
227 die "Lockfile exists. Please make sure no other instance of jobd is running.";
229 open LOCK
, '>', $lockfile || die "Cannot create lockfile $lockfile: $!";
241 ######### Helpers {{{1
244 print STDERR
shift()."\n";
254 Getopt
::Long
::Configure
('bundling');
255 my $parse_res = GetOptions
(
256 'help|?' => sub { pod2usage
(-verbose
=> 1, -exitval
=> 0); },
257 'quiet|q' => \
$quiet,
258 'progress|P' => \
$progress,
259 'kill-after|k=i' => \
$kill_after,
260 'max-parallel|p=i' => \
$max_par,
261 'lockfile|l=s' => \
$lockfile,
262 'all-once|a' => \
$all_once,
265 fatal
("Error: can only use one out of --all-once and --one")
266 if ($all_once && $one);
269 $ENV{'show_progress'} = '1';
286 ########## Documentation {{{1
292 jobd - Perform Girocco maintenance jobs
299 -h | --help detailed instructions
300 -q | --quiet run quietly
301 -P | --progress show occasional status updates
302 -k SECONDS | --kill-after=SECONDS how long to wait before killing jobs
303 -p NUM | --max-parallel=NUM how many jobs to run at the same time
304 -l FILE | --lockfile=FILE create a lockfile in the given location
305 -a | --all-once process the list only once
306 -o PRJNAME | --one=PRJNAME process only one project
314 Print the full description of jobd's options.
318 Suppress non-error messages, e.g. for use when running this task as a cronjob.
322 Show information about the current status of the job queue occasionally. This
323 is automatically enabled if --quiet is not given.
325 =item B<--kill-after=SECONDS>
327 Kill supervised jobs after a certain time to avoid hanging the daemon.
329 =item B<--max-parallel=NUM>
331 Run no more than that many jobs at the same time.
333 =item B<--lockfile=FILE>
335 For perpetual operation, create a lockfile in that place and clean it up after
340 Instead of perpetuously processing all projects over and over again, process
341 them just once and then exit.
343 =item B<--one=PRJNAME>
345 Process only the given project (given as just the project name without C<.git>
346 suffix) and then exit.
352 jobd is Girocco's repositories maintenance servant; it periodically checks all
353 the repositories and updates mirrored repositories and repacks push-mode
354 repositories when needed.