[backend] define armvXl architecture but keep armvXel and hl for compatibility
[opensuse:build-service.git] / src / backend / bs_dispatch
1 #!/usr/bin/perl -w
2 #
3 # Copyright (c) 2006, 2007 Michael Schroeder, Novell Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program (see the file COPYING); if not, write to the
16 # Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 #
19 ################################################################
20 #
21 # The Job Dispatcher
22 #
23
24 BEGIN {
25   my ($wd) = $0 =~ m-(.*)/- ;
26   $wd ||= '.';
27   # FIXME: currently the bs_dispatcher makes assumptions on being in a
28   # properly set up working dir, e.g. with subdirs 'worker' and
29   # 'build'.  Either that is cleaned up or this stays in, for the sake
30   # of startproc and others being able to start a bs_srcserver without
31   # knowing that it has to be started in the right directory....
32
33   chdir "$wd";
34   unshift @INC,  "build";
35   unshift @INC,  ".";
36 }
37
38 use POSIX;
39 use Data::Dumper;
40 use Digest::MD5 ();
41 use List::Util;
42 use Fcntl qw(:DEFAULT :flock);
43 use XML::Structured ':bytes';
44 use Storable;
45
46 use BSConfig;
47 use BSRPC;
48 use BSUtil;
49 use BSXML;
50
51 use strict;
52
53 my $nosrcchangescale = 3;       # -4.77
54
55 my %powerpkgs;
56
57 if ($BSConfig::powerpkgs) {
58   my $i = 1;
59   for (@{$BSConfig::powerpkgs || []}) {
60     $powerpkgs{$_} = $i++;
61   }
62 }
63
64 my $bsdir = $BSConfig::bsdir || "/srv/obs";
65
66 BSUtil::mkdir_p_chown($bsdir, $BSConfig::bsuser, $BSConfig::bsgroup) || die("unable to create $bsdir\n");
67 BSUtil::drop_privs_to($BSConfig::bsuser, $BSConfig::bsgroup);
68
69 my $port = 5252;        #'RR'
70 $port = $1 if $BSConfig::reposerver =~ /:(\d+)$/;
71
72 my %cando = (
73 # this code needs to be handled via a worker/dispatcher capability system in future
74   'i586'    => [          'i586',         'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'mips', 'mipsel',       'sh4'],
75   'i686'    => [          'i586', 'i686', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'mips', 'mipsel',       'sh4'],
76   'x86_64'  => ['x86_64', 'i586', 'i686', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el', 'mips', 'mipsel',       'sh4'],
77 #
78   'ppc'     => [                                                                                        'ppc'                ],
79   'ppc64'   => [                                                                                        'ppc', 'ppc64',      ],
80   'armv4l'  => [                          'armv4l'                                                                           ],
81   'armv5l'  => [                          'armv4l', 'armv5l', 'armv5el',                                                               ],
82   'armv6l'  => [                          'armv4l', 'armv5l', 'armv6l', 'armv5el', 'armv6el'                                                     ],
83   'armv7l'  => [                          'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el'                                          ],
84   'sh4'     => [                                                                                                        'sh4'],
85   'parisc'  => ['hppa', 'hppa64:linux64'],
86   'parisc64'=> ['hppa64', 'hppa:linux32'],
87   'ia64'    => ['ia64'],
88   's390'    => ['s390'],
89   's390x'   => ['s390x', 's390'],
90   'sparc'   => ['sparcv8', 'sparc'],
91   'sparc64' => ['sparc64v', 'sparc64', 'sparcv9v', 'sparcv9', 'sparcv8:linux32' , 'sparc:linux32'],
92   'mips'    => ['mips'],
93   'mips64'  => ['mips64', 'mips'],
94   'local'   => ['local'],
95 );
96
97 # 4h build will add .5 to the load
98 # 4h idle will half the load
99 my $decay = log(.5)/(4*3600);
100
101 my $rundir = $BSConfig::rundir || "$BSConfig::bsdir/run";
102 my $workersdir = "$BSConfig::bsdir/workers";
103 my $jobsdir = "$BSConfig::bsdir/jobs";
104 my $eventdir = "$BSConfig::bsdir/events";
105
106 my $reporoot = "$BSConfig::bsdir/build";
107
108 sub getcodemd5 {
109   my ($dir, $cache) = @_;
110   my $md5 = '';
111   my %new;
112   my $doclean;
113   my @files = grep {!/^\./} ls($dir);
114   my @bfiles = grep {!/^\./} ls("$dir/Build");
115   my %bfiles = map {$_ => 1} @bfiles;
116   @files = sort(@files, @bfiles);
117   $cache ||= {};
118   for my $file (@files) {
119     my $f = $bfiles{$file} ? "$dir/Build/$file" : "$dir/$file";
120     next unless -f $f;
121     my @s = stat _;
122     my $id = "$s[9]/$s[7]/$s[1]";
123     $new{$id} = 1; 
124     if ($cache->{$id}) {
125       $md5 .= "$cache->{$id}  $file\n";
126       next;
127     }    
128     $cache->{$id} = Digest::MD5::md5_hex(readstr($f));
129     $md5 .= "$cache->{$id}  $file\n";
130     $doclean = 1; 
131   }
132   if ($doclean) {
133     for (keys %$cache) {
134       delete $cache->{$_} unless $new{$_};
135     }    
136   }
137   return Digest::MD5::md5_hex($md5);
138 }
139
140 my $workerdircache = {};
141 my $builddircache = {};
142
143 my %badhost;
144 my %newestsrcchange;
145 my %infocache;
146
147 my %lastbuild;  # last time a job was build in that prpa
148
149 sub assignjob {
150   my ($job, $idlename, $arch) = @_;
151   local *F;
152
153   print "assignjob $arch/$job -> $idlename\n";
154   my $jobstatus = {
155     'code' => 'dispatching',
156   };
157   if (!BSUtil::lockcreatexml(\*F, "$jobsdir/$arch/.dispatch.$$", "$jobsdir/$arch/$job:status", $jobstatus, $BSXML::jobstatus)) {
158     print "job lock failed!\n";
159     return undef;
160   }
161
162   # got the lock, re-check if job is still there
163   if (! -e "$jobsdir/$arch/$job") {
164     unlink("$jobsdir/$arch/$job:status");
165     close F;
166     print "job disappered!\n";
167     return undef;
168   }
169
170   # prepare job data
171   my $infoxml = readstr("$jobsdir/$arch/$job");
172   my $jobid = Digest::MD5::md5_hex($infoxml);
173   my $info = XMLin($BSXML::buildinfo, $infoxml);
174
175   my $workercode = getcodemd5('worker', $workerdircache);
176   my $buildcode = getcodemd5('build', $builddircache);
177
178   # get the worker data
179   my $worker = readxml("$workersdir/idle/$idlename", $BSXML::worker, 1);
180   if (!$worker) {
181     unlink("$jobsdir/$arch/$job:status");
182     close F;
183     print "worker is gone!\n";
184     return undef;
185   }
186
187   eval {
188     BSRPC::rpc({
189       'uri'     => "http://$worker->{'ip'}:$worker->{'port'}/build",
190       'timeout' => 10,
191       'request' => "PUT",
192       'headers' => [ "Content-Type: text/xml" ],
193       'data'    => $infoxml,
194     }, undef, "port=$port", "workercode=$workercode", "buildcode=$buildcode");
195   };
196   if ($@) {
197     my $err = $@;
198     print "rpc error: $@";
199     unlink("$jobsdir/$arch/$job:status");
200     close F;
201     if ($err =~ /cannot build this package/) {
202       $badhost{"$info->{'project'}/$info->{'package'}/$info->{'arch'}/@{[(split(':', $idlename, 2))[1]]}"} = time();
203       return 'badhost';
204     }
205     unlink("$workersdir/idle/$idlename");       # broken client
206     return undef;
207   }
208   unlink("$workersdir/idle/$idlename"); # no longer idle
209   $jobstatus->{'code'} = 'building';
210   $jobstatus->{'uri'} = "http://$worker->{'ip'}:$worker->{'port'}";
211   $jobstatus->{'workerid'} = $worker->{'workerid'} if defined $worker->{'workerid'};
212   $jobstatus->{'starttime'} = time();
213   $jobstatus->{'hostarch'} = $worker->{'hostarch'};
214   $jobstatus->{'jobid'} = $jobid;
215
216   # put worker into building list
217   $worker->{'job'} = $job;
218   $worker->{'arch'} = $arch;
219   mkdir_p("$workersdir/building");
220   writexml("$workersdir/building/.$idlename", "$workersdir/building/$idlename", $worker, $BSXML::worker);
221
222   # write new status and release lock
223   writexml("$jobsdir/$arch/.$job:status", "$jobsdir/$arch/$job:status", $jobstatus, $BSXML::jobstatus);
224   close F;
225   return 'assigned';
226 }
227
228 sub sendeventtosrcserver {
229   my ($ev) = @_;
230   my @args;
231   for ('type', 'project', 'package', 'repository', 'arch', 'job') {
232     push @args, "$_=$ev->{$_}" if defined $ev->{$_};
233   }
234   my $param = {
235     'uri' => "$BSConfig::srcserver/event",
236     'request' => 'POST',
237     'timeout' => 10,
238   };
239   BSRPC::rpc($param, undef, @args);
240 }
241
242 sub staleness {
243   my ($prpa, $now, $ic, $jobs) = @_;
244
245   my $projid = (split('/', $prpa))[0];
246   my $lb = $lastbuild{$prpa};
247   return 0 unless $lb;
248   $lb = $now if $lb > $now;
249   my $newestsrcchange = $newestsrcchange{$projid};
250   if (!defined $newestsrcchange) {
251     $newestsrcchange = 0;
252     for (@$jobs) {
253       my $job = $ic->{$_};
254       $newestsrcchange = $job->{'revtime'} if $job && $job->{'revtime'} && $job->{'revtime'} > $newestsrcchange;
255     }
256     $newestsrcchange ||= $lb;
257     $newestsrcchange{$projid} = $newestsrcchange;
258   }
259   my $ret = ($lb - $newestsrcchange) / (($now - $lb) * 40 + 5000000);
260   $ret = 0 if $ret < 0;
261   #print "staleness $prpa: $ret\n";
262   return $ret;
263 }
264
265 $| = 1;
266 $SIG{'PIPE'} = 'IGNORE';
267 BSUtil::restartexit($ARGV[0], 'dispatcher', "$rundir/bs_dispatch");
268 print "starting build service dispatcher\n";
269
270 # get lock
271 mkdir_p($rundir);
272 open(RUNLOCK, '>>', "$rundir/bs_dispatch.lock") || die("$rundir/bs_dispatch.lock: $!\n");
273 flock(RUNLOCK, LOCK_EX | LOCK_NB) || die("dispatcher is already running!\n");
274 utime undef, undef, "$rundir/bs_dispatch.lock";
275
276 my $dispatchprios;
277 my $dispatchprios_project;
278 my $dispatchprios_id = '';
279
280 if (-s "$rundir/bs_dispatch.state") {
281   print "reading old state...\n";
282   my $state = BSUtil::retrieve("$rundir/bs_dispatch.state", 2);
283   unlink("$rundir/bs_dispatch.state");
284   %infocache = %{$state->{'infocache'}} if $state && $state->{'infocache'};
285   %badhost = %{$state->{'badhost'}} if $state && $state->{'badhost'};
286   %newestsrcchange = %{$state->{'newestsrcchange'}} if $state && $state->{'newestsrcchange'};
287 }
288
289 while (1) {
290
291   if (-s "$jobsdir/finished") {
292     local *F;
293     if (open(F, '<', "$jobsdir/finished")) {
294       unlink("$jobsdir/finished");
295       my $load = BSUtil::retrieve("$jobsdir/load", 1) || {};
296       while (<F>) {
297         next unless /\n$/s;
298         my @s = split('\|', $_);
299         s/%([a-fA-F0-9]{2})/chr(hex($1))/ge for @s;
300         my ($projid, $repoid, $arch, $packid, $start, $end, $result, $workerid, $hostarch) = @s;
301         next unless $start =~ /^[0-9]+$/s;
302         next unless $end=~ /^[0-9]+$/s;
303         next if $end <= $start;
304         my $prpa = "$projid/$repoid/$arch";
305         $load->{$prpa} = [0, 0] unless $load->{$prpa};
306         my $l = $load->{$prpa};
307         if ($l->[0] < $end) {
308           my $d = $end - $l->[0];
309           $l->[1] *= exp($decay * $d);
310           $l->[1] += (1 - exp($decay * ($end - $start)));
311           $l->[0] = $end;
312         } else {
313           my $d = $l->[0] - $end;
314           $l->[1] += (1 - exp($decay * ($end - $start))) * exp($decay * $d);
315         }
316       }
317       close F;
318       BSUtil::store("$jobsdir/load.new", "$jobsdir/load", $load);
319     }
320   }
321
322   my @dispatchprios_s = stat("$jobsdir/dispatchprios");
323   if (!@dispatchprios_s) {
324     $dispatchprios = undef;
325     $dispatchprios_project = undef;
326     $dispatchprios_id = '';
327   } elsif ($dispatchprios_id ne "$dispatchprios_s[9]/$dispatchprios_s[7]/$dispatchprios_s[1]") {
328     $dispatchprios_id = "$dispatchprios_s[9]/$dispatchprios_s[7]/$dispatchprios_s[1]";
329     $dispatchprios = BSUtil::retrieve("$jobsdir/dispatchprios", 1);
330     $dispatchprios_project = undef;
331     if ($dispatchprios) {
332       # create dispatchprios_project hash
333       $dispatchprios_project = {};
334       for (@{$dispatchprios->{'prio'} || []}) {
335         $dispatchprios_project->{$_->{'project'}} ||= [] if defined $_->{'project'};
336       }
337       my @p = keys %$dispatchprios_project;
338       push @p, ':all:';
339       for (@{$dispatchprios->{'prio'} || []}) {
340         if (defined($_->{'project'})) {
341           push @{$dispatchprios_project->{$_->{'project'}}}, $_;
342         } else {
343           for my $p (@p) {
344             push @{$dispatchprios_project->{$p}}, $_;
345           }
346         }
347       }
348     }
349   }
350
351   my $load = BSUtil::retrieve("$jobsdir/load", 1) || {};
352   my $now = time();
353   for my $prpa (sort keys %$load) {
354     my $l = $load->{$prpa};
355     my $ll = $l->[1];
356     $ll *= exp($decay * ($now - $l->[0])) if $now > $l->[0];
357     $load->{$prpa} = $ll;
358     $lastbuild{$prpa} = $l->[0];
359   }
360   
361   my @idle = grep {!/^\./} ls("$workersdir/idle");
362   my %idlearch;
363   for my $idle (@idle) {
364     my $harch = (split(':', $idle, 2))[0];
365     for (@{$cando{$harch} || []}) {
366       push @{$idlearch{$_}}, $idle;
367     }
368   }
369   #print "finding jobs\n";
370   my %jobs;
371   my %maybesrcchange;
372   for my $arch (sort keys %idlearch) {
373     my $ic = $infocache{$arch} || {};
374     my @b = grep {!/^\./} ls("$jobsdir/$arch");
375     my %locked = map {$_ => 1} grep {/:status$/} @b;
376     my %notlocked = map {$_ => 1} grep {!$locked{$_}} @b;
377     for (grep {!$notlocked{$_}} keys (%{$infocache{$arch} || {}})) {
378       delete $infocache{$arch}->{$_};
379     }
380     # adapt load
381     for my $job (keys %locked) {
382       my $jn = $job;
383       $jn =~ s/:status$//;
384       next unless $notlocked{$jn};
385       $jn =~ s/-[0-9a-f]{32}$//s;
386       my ($projid, $repoid, $packid) = split('::', $jn);
387       next unless defined $packid;
388       my $prpa = "$projid/$repoid/$arch";
389       $load->{$prpa} ||= 0;
390       $load->{$prpa} += 1;
391       $lastbuild{$prpa} = $now;
392     }
393     @b = grep {!/:(?:dir|status|new)$/} @b;
394     @b = grep {!$locked{"$_:status"}} @b;
395     for my $job (@b) {
396       my $info;
397       if (0) {
398         $info = readxml("$jobsdir/$arch/$job", $BSXML::buildinfo, 1);
399       } else {
400         my $jn = $job;
401         $jn =~ s/-[0-9a-f]{32}$//s;
402         my ($projid, $repoid, $packid) = split('::', $jn);
403         $info = {'project' => $projid, 'repository' => $repoid, 'package' => $packid, 'arch' => $arch};
404       }
405       my $prpa = "$info->{'project'}/$info->{'repository'}/$info->{'arch'}";
406       push @{$jobs{$prpa}}, $job;
407       $info = $ic->{$job};
408       if (!$info) {
409         $maybesrcchange{$prpa} = 1;
410       } elsif ($info->{'reason'} && ($info->{'reason'} eq 'new build' || $info->{'reason'} eq 'source change')) {
411         # only count direct changes as source change, not changes because of
412         # a change in a linked package
413         if ($info->{'reason'} eq 'new build' || !$info->{'revtime'} || $info->{'readytime'} - $info->{'revtime'} < 24 * 3600) {
414           $maybesrcchange{$prpa} = 1;
415         }
416       }
417     }
418   }
419
420   # calculate and distribute project load
421   if (%$load) {
422     my %praload;
423     for my $prpa (keys %$load) {
424       my $pra = $prpa;
425       $pra =~ s/\/.*\//\//s;
426       $praload{$pra} += $load->{$prpa};
427     }
428     for my $prpa (keys %jobs) {
429       my $pra = $prpa;
430       $pra =~ s/\/.*\//\//s;
431       next unless $praload{$pra};
432       $load->{$prpa} = rand(.01) unless $load->{$prpa};
433       $load->{$prpa} = ($load->{$prpa} + $praload{$pra}) / 2;
434     }
435   }
436
437   #print "calculating scales\n";
438   my %scales;
439   my @jobprpas = keys %jobs;
440   for my $prpa (@jobprpas) {
441     $load->{$prpa} = rand(.01) unless $load->{$prpa};
442     $scales{$prpa} = 0;
443     if ($BSConfig::dispatch_adjust) {
444       my @prios = @{$BSConfig::dispatch_adjust || []};
445       while (@prios) {
446         my ($match, $adj) = splice(@prios, 0, 2);
447         $scales{$prpa} += $adj if $prpa =~ /^$match/s;
448       }
449     }
450     if ($dispatchprios) {
451       my ($project, $repository, $arch) = split('/', $prpa, 3);
452       for (@{$dispatchprios_project->{$project} || $dispatchprios_project->{':all:'} || []}) {
453         next unless defined($_->{'adjust'});
454         next if defined($_->{'project'}) && $_->{'project'} ne $project;
455         next if defined($_->{'repository'}) && $_->{'repository'} ne $repository;
456         next if defined($_->{'arch'}) && $_->{'arch'} ne $arch;
457         $scales{$prpa} = 0 + $_->{'adjust'};
458       }
459     }
460     $scales{$prpa} = exp(-$scales{$prpa} * (log(10.)/10.));
461   }
462
463   if (1) {
464     #print "writing debug data\n";
465     # write debug data
466     if (@jobprpas) {
467       BSUtil::store("$rundir/.dispatch.data", "$rundir/dispatch.data", {
468         'load' => $load,
469         'scales' => \%scales,
470         'jobs' => \%jobs,
471         'powerpkgs' => \%powerpkgs,
472       });
473     }
474   }
475
476   my %didsrcchange;
477   my $assigned = 0;
478   my %extraload;
479
480   # the following helps a lot...
481   #print "fast src change load adapt\n";
482   for my $prpa (@jobprpas) {
483     next if $maybesrcchange{$prpa};
484     my $arch = (split('/', $prpa))[2];
485     my $ic = $infocache{$arch} || {};
486     $didsrcchange{$prpa} = 1;
487     $load->{$prpa} *= $nosrcchangescale;
488     $load->{$prpa} += staleness($prpa, $now, $ic, $jobs{$prpa} || []);
489   }
490
491   @jobprpas = sort {$scales{$a} * $load->{$a} <=> $scales{$b} * $load->{$b}} @jobprpas;
492
493   #print "assigning jobs\n";
494   while (@jobprpas) {
495     my $prpa = shift @jobprpas;
496     my $arch = (split('/', $prpa))[2];
497     next unless @{$idlearch{$arch} || []};
498     my @b = @{$jobs{$prpa} || []};
499     next unless @b;
500
501     #printf "%s %d %d\n", $prpa, $scales{$prpa} * $load->{$prpa}, scalar(@b);
502
503     my $nextload = @jobprpas ? $scales{$jobprpas[0]} * $load->{$jobprpas[0]} : undef;
504
505     # sort all jobs, src change jobs first
506     my @srcchange;
507     my $ic = $infocache{$arch};
508     $ic = $infocache{$arch} = {} unless $ic;
509     for my $job (@b) {
510       $ic->{$job} ||= readxml("$jobsdir/$arch/$job", $BSXML::buildinfo, 1) || {};
511       my $info = $ic->{$job};
512       # clean up job a bit
513       for (qw{bdep subpack imagetype}) {
514         delete $info->{$_};
515       }
516       if (!$info->{'readytime'}) {
517         my @s = stat("$jobsdir/$arch/$job");
518         $info->{'readytime'} = $s[9];
519       }
520       if ($info->{'reason'} && ($info->{'reason'} eq 'new build' || $info->{'reason'} eq 'source change')) {
521         # only count direct changes as source change, not changes because of
522         # a change in a linked package
523         if ($info->{'reason'} eq 'new build' || !$info->{'revtime'} || $info->{'readytime'} - $info->{'revtime'} < 24 * 3600) {
524           push @srcchange, $job;
525           $newestsrcchange{$info->{'project'}} = $info->{'readytime'} if ($newestsrcchange{$info->{'project'}} || 0) < $info->{'readytime'};
526         }
527       }
528     }
529     @b = List::Util::shuffle(@b);
530     @b = sort {($ic->{$b}->{'needed'} || 0) <=> ($ic->{$a}->{'needed'} || 0) || ($ic->{$a}->{'readytime'} || 0) <=> ($ic->{$b}->{'readytime'} || 0)} @b;
531     my %powerjobs;
532     if (%powerpkgs && $BSConfig::powerhosts) {
533       for my $job (@b) {
534         my $jn = $job;
535         $jn =~ s/-[0-9a-f]{32}$//s;
536         my ($projid, $repoid, $packid) = split('::', $jn);
537         $powerjobs{$job} = $powerpkgs{$packid} if $powerpkgs{$packid};
538       }
539       if (%powerjobs) {
540         # bring em to front!
541         my @nb = grep {!$powerjobs{$_}} @b;
542         @b = grep {$powerjobs{$_}} @b;
543         @b = sort {$powerjobs{$a} <=> $powerjobs{$b}} @b;
544         push @b, @nb;
545       }
546     }
547     my %srcchange = map {$_ => 1} @srcchange;
548     if (@srcchange) {
549       # bring em to front!
550       @b = ((grep {$srcchange{$_}} @b), (grep {!$srcchange{$_}} @b));
551     }
552
553     my $rerun;
554     for my $job (@b) {
555       if (!$srcchange{$job} && !$didsrcchange{$prpa}) {
556         $didsrcchange{$prpa} = 1;
557         $load->{$prpa} *= $nosrcchangescale;
558         $load->{$prpa} += staleness($prpa, $now, $ic, \@b);
559         if (defined($nextload) && $scales{$prpa} * $load->{$prpa} > $nextload) {
560           $rerun = 1;
561           last;
562         }
563       }
564       my @idle = List::Util::shuffle(@{$idlearch{$arch} || []});
565       last unless @idle;
566       my %poweridle;
567       if ($powerjobs{$job}) {
568         # reduce to powerhosts
569         for my $idle (splice @idle) {
570           my $idlehost = (split(':', $idle, 2))[1];
571           push @idle, $idle if grep {$idlehost =~ /^$_/} @$BSConfig::powerhosts;
572         }
573       }
574       my $tries = 0;
575       my $haveassigned;
576       my ($project, $repository, $arch) = split('/', $prpa, 3);
577       for my $idle (@idle) {
578         last unless -e "$jobsdir/$arch/$job";
579         next if $badhost{"$project/$ic->{$job}->{'package'}/$arch/@{[(split(':', $idle, 2))[1]]}"};
580         last if $assigned && $tries >= 5;
581         $tries++;
582         my $res = assignjob($job, $idle, $arch);
583         if (!$res) {
584           my $harch = (split(':', $idle, 2))[0];
585           for (@{$cando{$harch} || []}) {
586             $idlearch{$_} = [ grep {$_ ne $idle} @{$idlearch{$_}} ];
587           }
588           next;
589         }
590         next if $res eq 'badhost';
591         my $harch = (split(':', $idle, 2))[0];
592         for (@{$cando{$harch} || []}) {
593           $idlearch{$_} = [ grep {$_ ne $idle} @{$idlearch{$_}} ];
594         }
595         $assigned++;
596         $jobs{$prpa} = [ grep {$_ ne $job} @{$jobs{$prpa}} ];
597         $load->{$prpa} += 1;
598         $haveassigned = 1;
599         last;
600       }
601       # Tricky, still increase load so that we don't assign
602       # too many non-powerjobs. But only do that once for each powerjob.
603       if (!$haveassigned && $powerjobs{$job} && !$extraload{"$arch/$job"}) {
604         $load->{$prpa} += 1;
605         $extraload{"$arch/$job"} = 1;
606       }
607       # Check if load changes changed our order. If yes, re-sort and start over.
608       if (defined($nextload) && $scales{$prpa} * $load->{$prpa} > $nextload) {
609         $rerun = 1;
610         last;
611       }
612     }
613     if ($rerun) {
614       # our load was changed so much that the order was changed. put us back
615       # on the queue and re-sort.
616       unshift @jobprpas, $prpa;
617       @jobprpas = sort {$scales{$a} * $load->{$a} <=> $scales{$b} * $load->{$b}} @jobprpas;
618     }
619     last if $assigned >= 50;
620   }
621   for my $evname (ls("$eventdir/repository")) {
622     next if $evname =~ /^\./;
623     my $ev = readxml("$eventdir/repository/$evname", $BSXML::event, 1);
624     next unless $ev;
625     eval {
626       sendeventtosrcserver($ev);
627     };
628     if ($@) {
629       warn($@);
630     } else {
631       unlink("$eventdir/repository/$evname");
632     }
633   }
634   for my $evname (ls("$eventdir/dispatch")) {
635     next if $evname =~ /^\./;
636     my $ev = readxml("$eventdir/dispatch/$evname", $BSXML::event, 1);
637     next unless $ev;
638     next if $ev->{'due'} && time() < $ev->{'due'};
639     delete $ev->{'due'};
640     eval {
641       if ($ev->{'type'} eq 'built') {
642         # resend to rep server
643       } elsif ($ev->{'type'} eq 'badhost') {
644         print "badhost event: $ev->{'project'}/$ev->{'package'}/$ev->{'arch'}/$ev->{'job'}\n";
645         $badhost{"$ev->{'project'}/$ev->{'package'}/$ev->{'arch'}/$ev->{'job'}"} = time();
646       } else {
647         sendeventtosrcserver($ev);
648       }
649     };
650     if ($@) {
651       warn($@);
652     } else {
653       unlink("$eventdir/dispatch/$evname");
654     }
655   }
656   sleep(1) unless $assigned;
657   printf("assigned $assigned jobs\n") if $assigned;
658   if (%badhost) {
659     my $now = time();
660     for (keys %badhost) {
661       if ($badhost{$_} + 24*3600 < $now) {
662         print "deleting badhost $_\n";
663         delete $badhost{$_};
664       }
665     }
666   }
667
668   if (-e "$rundir/bs_dispatch.exit") {
669     my $state = {
670       'infocache' => \%infocache,
671       'badhost' => \%badhost,
672       'newestsrcchange' => \%newestsrcchange,
673     };
674     BSUtil::store("$rundir/bs_dispatch.state.new", "$rundir/bs_dispatch.state", $state);
675     unlink("$rundir/bs_dispatch.exit");
676     print "exiting...\n";
677     exit(0);
678   }
679   if (-e "$rundir/bs_dispatch.restart") {
680     my $state = {
681       'infocache' => \%infocache,
682       'badhost' => \%badhost,
683       'newestsrcchange' => \%newestsrcchange,
684     };
685     BSUtil::store("$rundir/bs_dispatch.state.new", "$rundir/bs_dispatch.state", $state);
686     unlink("$rundir/bs_dispatch.restart");
687     print "restarting...\n";
688     exec($0);
689     die("$0: $!\n");
690   }
691 }