3 # taskd - Clone repositories on request
5 # taskd is Girocco mirroring servant; it processes requests for clones
6 # of given URLs received over its socket.
8 # When a request is received, new process is spawned that sets up
9 # the repository and reports further progress
10 # to .clonelog within the repository. In case the clone fails,
11 # .clone_failed is touched and .clone_in_progress is removed.
14 # Alice sets up repository and touches .cloning
15 # Alice opens connection to Bob
16 # Alice sends project name through the connection
17 # Bob opens the repository and sends error code if there is a problem
18 # Bob closes connection
19 # Alice polls .clonelog in case of success.
20 # If Alice reads "@OVER@" from .clonelog, it stops polling.
22 # Ref-change protocol:
23 # Alice opens connection to Bob
24 # Alice sends ref-change command for each changed ref
25 # Alice closes connection
26 # Bob sends out notifications
28 # Initially based on perlipc example.
30 use 5.008; # we need safe signals
39 use POSIX
qw(:sys_wait_h :fcntl_h);
44 use lib
"__BASEDIR__";
49 use Girocco
::Util
qw(noFatalsToBrowser get_git human_duration);
50 BEGIN {noFatalsToBrowser
}
51 use Girocco
::ExecUtil
;
53 use constant SOCKFDENV
=> "GIROCCO_TASKD_SOCKET_FD";
55 # Throttle Classes Defaults
56 # Note that any same-named classes in @Girocco::Config::throttle_classes
57 # will override (completely replacing the entire hash) these ones.
58 my @throttle_defaults = (
73 #maxproc => max(5, cpucount + maxjobs), # this is the default
74 #maxjobs => max(1, int(cpucount / 4)) , # this is the default
97 my $progname = basename
($0);
103 use Girocco
::Util
"online_cpus";
104 our $online_cpus_result;
105 $online_cpus_result = online_cpus
unless $online_cpus_result;
106 return $online_cpus_result;
110 my $hdr = "[@{[scalar localtime]}] $progname $$: ";
112 $OStream::only
= 2; # STDERR only
114 $OStream::only
= 1; # syslog only
116 $OStream::only
= 0; # back to default
123 return unless $progress;
124 my $hdr = "[@{[scalar localtime]}] $progname $$: ";
126 $OStream::only
= 2; # STDERR only
127 print STDERR
"$hdr@_\n";
128 $OStream::only
= 1; # syslog only
130 $OStream::only
= 0; # back to default
132 print STDERR
"$hdr@_\n";
138 return undef unless defined($fd) && $fd >= 0;
139 my $result = POSIX
::dup
($fd);
140 POSIX
::close($result) if defined($result);
146 fcntl($fd, F_SETFD
, 0) or die "fcntl failed: $!";
151 fcntl($fd, F_SETFD
, FD_CLOEXEC
) or die "fcntl failed: $!";
156 my $flags = fcntl($fd, F_GETFL
, 0);
157 defined($flags) or die "fcntl failed: $!";
158 fcntl($fd, F_SETFL
, $flags | O_NONBLOCK
) or die "fcntl failed: $!";
163 my $flags = fcntl($fd, F_GETFL
, 0);
164 defined($flags) or die "fcntl failed: $!";
165 fcntl($fd, F_SETFL
, $flags & ~O_NONBLOCK
) or die "fcntl failed: $!";
173 ## 1) Process needing throttle services acquire a control file descriptor
174 ## a) Either as a result of a fork + exec (the write end of a pipe)
175 ## b) Or by connecting to the taskd socket (not yet implemented)
177 ## 2) The process requesting throttle services will be referred to
178 ## as the supplicant or just "supp" for short.
180 ## 3) The supp first completes any needed setup which may include
181 ## gathering data it needs to perform the action -- if that fails
182 ## then there's no need for any throttling.
184 ## 4) The supp writes a throttle request to the control descriptor in
186 ## throttle <pid> <class>\n
187 ## for example if the supp's pid was 1234 and it was requesting throttle
188 ## control as a member of the mail class it would write this message:
189 ## throttle 1234 mail\n
190 ## Note that if the control descriptor happens to be a pipe rather than a
191 ## socket, the message should be preceded by another "\n" just be be safe.
192 ## If the control descriptor is a socket, not a pipe, the message may be
193 ## preceded by a "\n" but that's not recommended.
195 ## 5) For supplicants with a control descriptor that is a pipe
196 ## (getsockopt(SO_TYPE) returns ENOTSOCK) the (5a) protocol should be used.
197 ## If the control descriptor is a socket (getsockname succeeds) then
198 ## protocol (5b) should be used.
200 ## 5a) The supp now enters a "pause" loop awaiting either a SIGUSR1, SIGUSR2 or
201 ## SIGTERM. It should wake up periodically (SIGALRM works well) and attempt
202 ## to write a "keepalive\n" message to the control descriptor. If that
203 ## fails, the controller has gone away and it may make its own decision
204 ## whether or not to proceed at that point. If, on the other hand, it
205 ## receives a SIGTERM, the process limit for its class has been reached
206 ## and it should abort without performing its action. If it receives
207 ## SIGUSR1, it may proceed without writing anything more to the control
208 ## descriptor, any MAY even close the control descriptor. Finally, a
209 ## SIGUSR2 indicates rejection of the throttle request for some other reason
210 ## such as unrecognized class name or invalid pid in which case the supp may
211 ## make its own decision how to proceed.
213 ## 5b) The supp now enters a read wait on the socket -- it need accomodate no
214 ## more than 512 bytes and if a '\n' does not appear within that number of
215 ## bytes the read should be considered failed. Otherwise the read should
216 ## be retried until either a full line has been read or the socket is
217 ## closed from the other end. If the lone read is "proceed\n" then it may
218 ## proceed without reading or writing anything more to the control
219 ## descriptor, but MUST keep the control descriptor open and not call
220 ## shutdown on it either. Any other result (except EINTR or EAGAIN which
221 ## should be retried) constitutes failure. If a full line starting with at
222 ## least one alpha character was read but it was not "proceed" then it
223 ## should abort without performing its action. For any other failure it
224 ## may make its own decision whether or not to proceed as the controller has
227 ## 6) The supp now performs its throttled action.
229 ## 7) The supp now closes its control descriptor (if it hasn't already in the
230 ## case of (5a)) and exits -- in the case of a socket, the other end receives
231 ## notification that the socket has been closed (read EOF). In the case of
232 ## a pipe the other end receives a SIGCHLD (multiple processes have a hold
233 ## of the other end of the pipe, so it will not reaach EOF by the supp's
234 ## exit in that case).
237 # keys are class names, values are hash refs with these fields:
238 # 'maxproc' => integer; maximum number of allowed supplicants (the sum of how
239 # many may be queued waiting plus how many may be
240 # concurrently active) with 0 meaning no limit.
241 # 'maxjobs' => integer; how many supplicants may proceed simultaneously a value
242 # of 0 is unlimited but the number of concurrent
243 # supplicants will always be limited to no more than
244 # the 'maxproc' value (if > 0) no matter what the
245 # 'maxjobs' value is.
246 # 'total' -> integer; the total number of pids belonging to this class that
247 # can currently be found in %pid.
248 # 'active' -> integer; the number of currently active supplicants which should
249 # be the same as (the number of elements of %pid with a
250 # matching class name) - (number of my class in @queue).
251 # 'interval' -> integer; minimum number of seconds between 'proceed' responses
252 # or SIGUSR1 signals to members of this class.
253 # 'lastqueue' -> time; last time a supplicant was successfully queued.
254 # 'lastproceed' => time; last time a supplicant was allowed to proceed.
255 # 'lastthrottle' => time; last time a supplicant was throttled
256 # 'lastdied' => time; last time a supplicant in this class died/exited/etc.
259 # keys are pid numbers, values are array refs with these elements:
260 # [0] => name of class (key to classes hash)
261 # [1] => supplicant state (0 => queued, non-zero => time it started running)
262 # [2] => descriptive text (e.g. project name)
265 # minimum number of seconds between any two proceed responses no matter what
266 # class. this takes priority in that it can effectively increase the
267 # class's 'interval' value by delaying proceed notifications if the minimum
268 # interval has not yet elapsed.
271 # fifo of pids awaiting notification as soon as the next $interval elapses
272 # provided interval and maxjobs requirements are satisfied
273 # for the class of the pid that will next be triggered.
276 # time of most recent successful call to AddSupplicant
279 # time of most recent proceed notification
282 # time of most recent throttle
283 my $lastthrottle = 0;
285 # time of most recent removal
288 # lifetime count of how many have been queued
291 # lifetime count of how many have been allowed to proceed
292 my $totalproceed = 0;
294 # lifetime count of how many have been throttled
295 my $totalthrottle = 0;
297 # lifetime count of how many have died
298 # It should always be true that $totalqueued - $totaldied == $curentlyactive
301 # Returns an unordered list of currently registered class names
303 return keys(%classes);
307 return $_[0] if $_[0] >= $_[1];
312 my ($min, $val, $default) = @_;
314 if (defined($val) && $val =~ /^[+-]?\d+$/) {
319 return _max
($min, $ans);
322 # [0] => name of class to find
323 # [1] => if true, create class if it doesn't exist, if a hashref then
324 # it contains initial values for maxproc, maxjobs and interval.
325 # Otherwise maxjobs defaults to max(cpu cores/4, 1), maxprocs
326 # defaults to the max(5, number of cpu cores + maxjobs) and interval
328 # Returns a hash ref with info about the class on success
330 my ($classname, $init) = @_;
331 defined($classname) && $classname =~ /^[a-zA-Z][a-zA-Z0-9._+-]*$/
333 $classname = lc($classname);
335 if ($classes{$classname}) {
336 %info = %{$classes{$classname}};
341 ref($init) eq 'HASH' or $init = {};
342 $newclass{'maxjobs'} = _getnum
(0, $init->{'maxjobs'}, sub{_max
(1, int(::cpucount
() / 4))});
343 $newclass{'maxproc'} = _getnum
(0, $init->{'maxproc'}, sub{_max
(5, ::cpucount
() + $newclass{'maxjobs'})});
344 $newclass{'interval'} = _getnum
(0, $init->{'interval'}, sub{1});
345 $newclass{'total'} = 0;
346 $newclass{'active'} = 0;
347 $newclass{'lastqueue'} = 0;
348 $newclass{'lastproceed'} = 0;
349 $newclass{'lastthrottle'} = 0;
350 $newclass{'lastdied'} = 0;
351 $classes{$classname} = \
%newclass;
356 # [0] => pid to look up
357 # Returns () if not found otherwise ($classname, $timestarted, $description)
358 # Where $timestarted will be 0 if it's still queued otherwise a time() value
361 return () unless exists $pid{$pid};
362 return @
{$pid{$pid}};
365 # Returns array of pid numbers that are currently running sorted
366 # by time started (oldest to newest). Can return an empty array.
368 return sort({ ${$pid{$a}}[1] <=> ${$pid{$b}}[1] }
369 grep({ ${$pid{$_}}[1] } keys(%pid)));
372 # Returns a hash with various about the current state
373 # 'interval' => global minimum interval between proceeds
374 # 'active' => how many pids are currently queued + how many are running
375 # 'queue' => how many pids are currently queued
376 # 'lastqueue' => time (epoch seconds) of last queue
377 # 'lastproceed' => time (epoch seconds) of last proceed
378 # 'lastthrottle' => time (epoch seconds) of last throttle
379 # 'lastdied' => time (epoch seconds) of last removal
380 # 'totalqueue' => lifetime total number of processes queued
381 # 'totalproceed' => lifetime total number of processes proceeded
382 # 'totalthrottle' => lifetime total number of processes throttled
383 # 'totaldied' => lifetime total number of removed processes
386 interval
=> $interval,
387 active
=> scalar(keys(%pid)) - scalar(@queue),
388 queue
=> scalar(@queue),
389 lastqueue
=> $lastqueue,
390 lastproceed
=> $lastproceed,
391 lastthrottle
=> $lastthrottle,
392 lastdied
=> $lastdied,
393 totalqueue
=> $totalqueue,
394 totalproceed
=> $totalproceed,
395 totalthrottle
=> $totalthrottle,
396 totaldied
=> $totaldied
400 # with no args get the global interval
401 # with one arg set it, returns previous value if set
404 $interval = 0 + $_[0] if defined($_[0]) && $_[0] =~ /^\d+$/;
408 sub RemoveSupplicant
;
410 # Perform queue service (i.e. send SIGUSR1 to any eligible queued process)
411 # Returns minimum interval until next proceed is possible
412 # Returns undef if there's nothing waiting to proceed or
413 # the 'maxjobs' limits have been reached for all queued items (in which
414 # case it won't be possible to proceed until one of them exits, hence undef)
415 # This is called automatially by AddSupplicant and RemoveSupplicant
418 return undef unless @queue; # if there's nothing queued, nothing to do
420 my $min = _max
(0, $interval - ($now - $lastproceed));
421 my $classmin = undef;
422 my $classchecked = 0;
424 my $classcount = scalar(keys(%classes));
425 for (my $i=0; $i <= $#queue && $classchecked < $classcount; ++$i) {
426 my $pid = $queue[$i];
427 my $procinfo = $pid{$pid};
429 RemoveSupplicant
($pid, 1);
432 my $classinfo = $classes{$$procinfo[0]};
434 RemoveSupplicant
($pid, 1);
437 if (!$seenclass{$$procinfo[0]}) {
438 $seenclass{$$procinfo[0]} = 1;
440 if (!$classinfo->{'maxjobs'} || $classinfo->{'active'} < $classinfo->{'maxjobs'}) {
441 my $cmin = _max
(0, $classinfo->{'interval'} - ($now - $classinfo->{'lastproceed'}));
442 if (!$cmin && !$min) {
444 $$procinfo[1] = $now;
445 splice(@queue, $i, 1);
448 $classinfo->{'lastproceed'} = $now;
449 ++$classinfo->{'active'};
450 kill("USR1", $pid) or RemoveSupplicant
($pid, 1);
453 $classmin = $cmin unless defined($classmin) && $classmin < $cmin;
457 return defined($classmin) ? _max
($min, $classmin) : undef;
460 # $1 => pid to add (must not already be in %pids)
461 # $2 => class name (must exist)
462 # Returns -1 if no such class or pid already present or invalid
463 # Returns 0 if added successfully (and possibly already SIGUSR1'd)
464 # Return 1 if throttled and cannot be added
466 my ($pid, $classname, $text, $noservice) = @_;
467 return -1 unless $pid && $pid =~ /^[1-9][0-9]*$/;
469 kill(0, $pid) or return -1;
470 my $classinfo = $classes{$classname};
471 return -1 unless $classinfo;
472 return -1 if $pid{$pid};
473 $text = '' unless defined($text);
475 if ($classinfo->{'maxproc'} && $classinfo->{'total'} >= $classinfo->{'maxproc'}) {
477 $lastthrottle = $now;
478 $classinfo->{'lastthrottle'} = $now;
483 $pid{$pid} = [$classname, 0, $text];
484 ++$classinfo->{'total'};
485 $classinfo->{'lastqueue'} = $now;
487 ServiceQueue
unless $noservice;
491 # $1 => pid to remove (died, killed, exited normally, doesn't matter)
492 # Returns 0 if removed
493 # Returns -1 if unknown pid or other error during removal
494 sub RemoveSupplicant
{
495 my ($pid, $noservice) = @_;
496 return -1 unless defined($pid) && $pid =~ /^\d+$/;
498 my $pidinfo = $pid{$pid};
499 $pidinfo or return -1;
505 for (my $i=0; $i<=$#queue; ++$i) {
506 if ($queue[$i] == $pid) {
507 splice(@queue, $i, 1);
512 my $classinfo = $classes{$$pidinfo[0]};
513 ServiceQueue
, return -1 unless $classinfo;
514 --$classinfo->{'active'} if $$pidinfo[1];
515 --$classinfo->{'total'};
516 $classinfo->{'lastdied'} = $now;
517 ServiceQueue
unless $noservice;
533 # http://pubs.opengroup.org/onlinepubs/000095399/utilities/trap.html
546 while (($waitedpid = waitpid(-1, WNOHANG
)) > 0) {
547 my $code = $?
& 0xffff;
548 $idlestart = time if !--$children;
550 if (!($code & 0xff)) {
551 $codemsg = " with exit code ".($code >> 8) if $code;
552 } elsif ($code & 0x7f) {
553 my $signum = ($code & 0x7f);
554 $codemsg = " with signal ".
555 ($signame{$signum}?
$signame{$signum}:$signum);
557 logmsg
"reaped $waitedpid$codemsg";
558 push(@reapedpids, $waitedpid);
560 $SIG{CHLD
} = \
&REAPER
; # loathe sysV
563 sub set_sigchld_reaper
() {
564 $SIG{CHLD
} = \
&REAPER
; # Apollo 440
567 my ($piperead, $pipewrite);
572 if (not defined $pid) {
573 logmsg
"cannot fork: $!";
576 $idlestart = time if !++$children;
579 return; # I'm the parent
582 close(Server
) unless fileno(Server
) == 0;
584 $SIG{'CHLD'} = sub {};
586 open STDIN
, "+<&Client" or die "can't dup client to stdin";
595 sub request_throttle
{
596 use POSIX
qw(sigprocmask sigsuspend SIG_SETMASK);
597 my $classname = shift;
600 Throttle
::GetClassInfo
($classname)
601 or return -1; # no such throttle class
607 my $setempty = POSIX
::SigSet
->new;
608 my $setfull = POSIX
::SigSet
->new;
609 $setempty->emptyset();
611 $SIG{'TERM'} = sub {$throttled = 1};
612 $SIG{'USR1'} = sub {$proceed = 1};
613 $SIG{'USR2'} = sub {$error = 1};
614 $SIG{'PIPE'} = sub {$controldead = 1};
615 $SIG{'ALRM'} = sub {};
617 # After writing we can expect a SIGTERM, SIGUSR1 or SIGUSR2
618 print $pipewrite "\nthrottle $$ $classname $text\n";
619 my $old = POSIX
::SigSet
->new;
620 sigprocmask
(SIG_SETMASK
, $setfull, $old);
621 until ($controldead || $throttled || $proceed || $error) {
623 sigsuspend
($setempty);
625 sigprocmask
(SIG_SETMASK
, $setempty, $old);
626 print $pipewrite "\nkeepalive $$\n";
627 sigprocmask
(SIG_SETMASK
, $setfull, $old);
629 sigprocmask
(SIG_SETMASK
, $setempty, $old);
630 $SIG{'TERM'} = "DEFAULT";
631 $SIG{'USR1'} = "DEFAULT";
632 $SIG{'USR2'} = "DEFAULT";
633 $SIG{'ALRM'} = "DEFAULT";
634 $SIG{'PIPE'} = "DEFAULT";
647 Girocco
::Project
::does_exist
($name, 1) or die "no such project: $name";
649 eval {$proj = Girocco
::Project
->load($name)};
650 if (!$proj && Girocco
::Project
::does_exist
($name, 1)) {
651 # If the .clone_in_progress file exists, but the .clonelog does not
652 # and neither does the .clone_failed, be helpful and touch the
653 # .clone_failed file so that the mirror can be restarted
654 my $projdir = $Girocco::Config
::reporoot
."/$name.git";
655 if (-d
"$projdir" && -f
"$projdir/.clone_in_progress" && ! -f
"$projdir/.clonelog" && ! -f
"$projdir/.clone_failed") {
656 open X
, '>', "$projdir/.clone_failed" and close(X
);
659 $proj or die "failed to load project $name";
660 $proj->{clone_in_progress
} or die "project $name is not marked for cloning";
661 $proj->{clone_logged
} and die "project $name is already being cloned";
662 request_throttle
("clone", $name) <= 0 or die "cloning $name aborted (throttled)";
663 statmsg
"cloning $name";
664 my $devnullfd = POSIX
::open(File
::Spec
->devnull, O_RDWR
);
665 defined($devnullfd) && $devnullfd >= 0 or die "cannot open /dev/null: $!";
666 POSIX
::dup2
($devnullfd, 0) or
667 die "cannot dup2 STDIN_FILENO: $!";
668 POSIX
::close($devnullfd);
670 open $duperr, '>&2' or
671 die "cannot dup STDERR_FILENO: $!";
672 my $clonelogfd = POSIX
::open("$Girocco::Config::reporoot/$name.git/.clonelog", O_WRONLY
|O_TRUNC
|O_CREAT
, 0664);
673 defined($clonelogfd) && $clonelogfd >= 0 or die "cannot open clonelog for writing: $!";
674 POSIX
::dup2
($clonelogfd, 1) or
675 die "cannot dup2 STDOUT_FILENO: $!";
676 POSIX
::dup2
($clonelogfd, 2) or
677 POSIX
::dup2
(fileno($duperr), 2), die "cannot dup2 STDERR_FILENO: $!";
678 POSIX
::close($clonelogfd);
679 exec "$Girocco::Config::basedir/taskd/clone.sh", "$name.git" or
680 POSIX
::dup2
(fileno($duperr), 2), die "exec failed: $!";
684 return ' -> ' unless $showff && defined($_[0]);
685 my ($git_dir, $old, $new) = @_;
686 return '..' unless defined($old) && defined($new) && $old !~ /^0+$/ && $new !~ /^0+$/ && $old ne $new;
687 # In many cases `git merge-base` is slower than this even if using the
688 # `--is-ancestor` option available since Git 1.8.0, but it's never faster
689 my $ans = get_git
("--git-dir=$git_dir", "rev-list", "-n", "1", "^$new^0", "$old^0", "--") ?
'...' : '..';
690 return wantarray ?
($ans, 1) : $ans;
695 my ($username, $name, $oldrev, $newrev, $ref) = split(/\s+/, $arg);
696 $username && $name && $oldrev && $newrev && $ref or return 0;
697 $oldrev =~ /^[0-9a-f]{40}$/ && $newrev =~ /^[0-9a-f]{40}$/ && $ref =~ m{^refs/} or return 0;
698 $newrev ne $oldrev or return 0;
699 $Girocco::Config
::notify_single_level
|| $ref =~ m
(^refs
/[^/]+/[^/]) or return 0;
701 Girocco
::Project
::does_exist
($name, 1) or die "no such project: $name";
702 my $proj = Girocco
::Project
->load($name);
703 $proj or die "failed to load project $name";
704 my $has_notify = $proj->has_notify;
705 my $type = $has_notify ?
"notify" : "change";
708 if ($username && $username !~ /^%.*%$/) {
709 Girocco
::User
::does_exist
($username, 1) or die "no such user: $username";
710 $user = Girocco
::User
->load($username);
711 $user or die "failed to load user $username";
712 } elsif ($username eq "%$name%") {
716 request_throttle
("ref-change", $name) <= 0 or die "ref-change $name aborted (throttled)";
717 my $ind = ref_indicator
($proj->{path
}, $oldrev, $newrev);
718 statmsg
"ref-$type $username $name ($ref: @{[substr($oldrev,0,$abbrev)]}$ind@{[substr($newrev,0,$abbrev)]})";
719 open STDIN
, '<', File
::Spec
->devnull;
720 Girocco
::Notify
::ref_changes
($proj, $user, [$oldrev, $newrev, $ref]) if $has_notify;
726 my ($username, $name) = split(/\s+/, $arg);
727 $username && $name or return 0;
729 Girocco
::Project
::does_exist
($name, 1) or die "no such project: $name";
730 my $proj = Girocco
::Project
->load($name);
731 $proj or die "failed to load project $name";
732 my $has_notify = $proj->has_notify;
733 my $type = $has_notify ?
"notify" : "change";
736 if ($username && $username !~ /^%.*%$/) {
737 Girocco
::User
::does_exist
($username, 1) or die "no such user: $username";
738 $user = Girocco
::User
->load($username);
739 $user or die "failed to load user $username";
740 } elsif ($username eq "%$name%") {
746 my %deletedheads = ();
747 while (my $change = <STDIN
>) {
748 my ($oldrev, $newrev, $ref) = split(/\s+/, $change);
749 $oldrev ne "done" or last;
750 $oldrev =~ /^[0-9a-f]{40}$/ && $newrev =~ /^[0-9a-f]{40}$/ && $ref =~ m{^refs/} or next;
751 $Girocco::Config
::notify_single_level
|| $ref =~ m
(^refs
/[^/]+/[^/]) or next;
752 if ($ref =~ m{^refs/heads/.}) {
753 if ($oldrev =~ /^0{40}$/) {
754 delete $oldheads{$ref};
755 $deletedheads{$ref} = 1;
756 } elsif ($newrev ne $oldrev || (!exists($oldheads{$ref}) && !$deletedheads{$ref})) {
757 $oldheads{$ref} = $oldrev;
760 $newrev ne $oldrev or next;
761 push(@changes, [$oldrev, $newrev, $ref]);
763 return 0 unless @changes;
764 open STDIN
, '<', File
::Spec
->devnull;
765 request_throttle
("ref-change", $name) <= 0 or die "ref-changes $name aborted (throttled)";
767 my ($old, $new, $ref, $ran_mail_sh) = @_;
768 my ($ind, $ran_git) = ref_indicator
($proj->{path
}, $old, $new);
769 statmsg
"ref-$type $username $name ($ref: @{[substr($old,0,$abbrev)]}$ind@{[substr($new,0,$abbrev)]})";
777 Girocco
::Notify
::ref_changes
($proj, $user, $statproc, \
%oldheads, @changes);
779 &$statproc(@
$_) foreach @changes;
786 my ($pid, $classname, $text) = split(/\s+/, $arg);
787 $pid =~ /^\d+/ or return 0; # invalid pid
789 $pid > 0 or return 0; # invalid pid
790 kill(0, $pid) || $!{EPERM
} or return 0; # no such process
791 Throttle
::GetClassInfo
($classname) or return 0; # no such throttle class
792 defined($text) && $text ne '' or return 0; # no text no service
800 pipe($waker, $wakew) or die "pipe failed: $!";
801 select((select($wakew),$|=1)[0]);
803 $SIG{'TERM'} = sub {$throttled = 1; syswrite($wakew, '!')};
804 $SIG{'USR1'} = sub {$proceed = 1; syswrite($wakew, '!')};
805 $SIG{'USR2'} = sub {$error = 1; syswrite($wakew, '!')};
806 $SIG{'PIPE'} = sub {$controldead = 1; syswrite($wakew, '!')};
807 select((select(STDIN
),$|=1)[0]);
809 logmsg
"throttle $pid $classname $text request";
810 # After writing we can expect a SIGTERM or SIGUSR1
811 print $pipewrite "\nthrottle $$ $classname $text\n";
813 # NOTE: the only way to detect the socket close is to read all the
814 # data until EOF is reached -- recv can be used to peek.
816 vec($v, fileno(STDIN
), 1) = 1;
817 vec($v, fileno($waker), 1) = 1;
818 setnonblock
(\
*STDIN
);
820 until ($controldead || $throttled || $proceed || $error || $suppdead) {
822 select($r=$v, undef, $e=$v, 30);
823 my ($bytes, $discard);
824 do {$bytes = sysread($waker, $discard, 512)} while (defined($bytes) && $bytes > 0);
825 do {$bytes = sysread(STDIN
, $discard, 4096)} while (defined($bytes) && $bytes > 0);
826 $suppdead = 1 unless !defined($bytes) && $!{EAGAIN
};
827 print $pipewrite "\nkeepalive $$\n";
831 if ($throttled && !$suppdead) {
832 print STDIN
"throttled\n";
833 logmsg
"throttle $pid $classname $text throttled";
834 } elsif ($proceed && !$suppdead) {
835 print STDIN
"proceed\n";
836 logmsg
"throttle $pid $classname $text proceed";
837 $SIG{'TERM'} = 'DEFAULT';
838 # Stay alive until the child dies which we detect by EOF on STDIN
839 setnonblock
(\
*STDIN
);
840 until ($controldead || $suppdead) {
842 select($r=$v, undef, $e=$v, 30);
843 my ($bytes, $discard);
844 do {$bytes = sysread($waker, $discard, 512)} while (defined($bytes) && $bytes > 0);
845 do {$bytes = sysread(STDIN
, $discard, 512)} while (defined($bytes) && $bytes > 0);
846 $suppdead = 1 unless !defined($bytes) && $!{EAGAIN
};
847 print $pipewrite "\nkeepalive $$\n";
852 $prefix = "control" if $controldead && !$suppdead;
853 logmsg
"throttle $pid $classname $text ${prefix}died";
858 sub process_pipe_msg
{
859 my ($act, $pid, $cls, $text) = split(/\s+/, $_[0]);
860 if ($act eq "throttle") {
861 $pid =~ /^\d+$/ or return 0;
863 $pid > 0 or return 0; # invalid pid
864 kill(0, $pid) or return 0; # invalid pid
865 defined($cls) && $cls ne "" or kill('USR2', $pid), return 0;
866 defined($text) && $text ne "" or kill('USR2', $pid), return 0;
867 Throttle
::GetClassInfo
($cls) or kill('USR2', $pid), return 0;
868 # the AddSupplicant call could send SIGUSR1 before it returns
869 my $result = Throttle
::AddSupplicant
($pid, $cls, $text);
870 kill('USR2', $pid), return 0 if $result < 0;
871 kill('TERM', $pid), return 0 if $result > 0;
872 # $pid was added to class $cls and will receive SIGUSR1 when
873 # it's time for it to proceed
875 } elsif ($act eq "keepalive") {
876 # nothing to do although we could verify pid is valid and
877 # still in %Throttle::pids and send a SIGUSR2 if not, but
878 # really keepalive should just be ignored.
881 print STDERR
"discarding unknown pipe message \"$_[0]\"\n";
893 # Set to 1 for only syslog output (if enabled by mode)
894 # Set to 2 for only stderr output (if enabled by mode)
895 our $only = 0; # This is a hack
898 use Sys
::Syslog
qw(:DEFAULT :macros);
901 my ($fd, $data) = @_;
903 my $remaining = length($data);
905 my $bytes = POSIX
::write(
907 substr($data, $offset, $remaining),
909 next if !defined($bytes) && $!{EINTR
};
910 croak
"POSIX::write failed: $!" unless defined $bytes;
911 croak
"POSIX::write wrote 0 bytes" unless $bytes;
912 $remaining -= $bytes;
918 use POSIX
qw(STDERR_FILENO);
919 my ($self, $line) = @_;
920 $only = 0 unless defined($only);
921 writeall
(STDERR_FILENO
, $line) if $self->{'stderr'} && $only != 1;
922 substr($line, -1, 1) = '' if substr($line, -1, 1) eq "\n";
923 return unless length($line);
924 syslog
(LOG_NOTICE
, "%s", $line) if $self->{'syslog'} && $only != 2;
928 my $class = shift || 'OStream';
930 my $syslogname = shift;
931 my $syslogfacility = shift;
932 defined($syslogfacility) or $syslogfacility = LOG_USER
;
934 $self->{'syslog'} = $mode > 0;
935 $self->{'stderr'} = $mode <= 0 || $mode > 1;
936 $self->{'lastline'} = '';
937 if ($self->{'syslog'}) {
938 # Some Sys::Syslog have a stupid default setlogsock order
939 eval {Sys
::Syslog
::setlogsock
("native"); 1;} or
940 eval {Sys
::Syslog
::setlogsock
("unix");};
941 openlog
($syslogname, "ndelay,pid", $syslogfacility)
942 or croak
"Sys::Syslog::openlog failed: $!";
944 return bless $self, $class;
947 sub BINMODE
{return 1}
948 sub FILENO
{return undef}
954 my $template = shift;
955 return $self->PRINT(sprintf $template, @_);
960 my $data = join('', $self->{'lastline'}, @_);
962 while ((my $idx = index($data, "\n", $pos)) >= 0) {
964 my $line = substr($data, $pos, $idx - $pos);
965 substr($data, $pos, $idx - $pos) = '';
967 $self->dumpline($line);
969 $self->{'lastline'} = $data;
975 $self->dumpline($self->{'lastline'})
976 if length($self->{'lastline'});
982 my ($scalar, $length, $offset) = @_;
983 $scalar = '' if !defined($scalar);
984 $length = length($scalar) if !defined($length);
985 croak
"OStream::WRITE invalid length $length"
987 $offset = 0 if !defined($offset);
988 $offset += length($scalar) if $offset < 0;
989 croak
"OStream::WRITE invalid write offset"
990 if $offset < 0 || $offset > $length;
991 my $max = length($scalar) - $offset;
992 $length = $max if $length > $max;
993 $self->PRINT(substr($scalar, $offset, $length));
1005 # returns pid of process that will schedule jobd.pl restart on success
1006 # returns 0 if fork or other system call failed with error in $!
1007 # returns undef if jobd.pl does not currently appear to be running (no lockfile)
1008 sub schedule_jobd_restart
{
1009 use POSIX
qw(_exit setpgid dup2 :fcntl_h);
1010 my $devnull = File
::Spec
->devnull;
1012 my $jdlf = "/tmp/jobd-$Girocco::Config::tmpsuffix.lock";
1013 return undef unless -f
$jdlf;
1014 my $oldsigchld = $SIG{'CHLD'};
1015 defined($oldsigchld) or $oldsigchld = sub {};
1016 my ($read, $write, $read2, $write2);
1017 pipe($read, $write) or return 0;
1018 select((select($write),$|=1)[0]);
1019 if (!pipe($read2, $write2)) {
1025 select((select($write2),$|=1)[0]);
1026 $SIG{'CHLD'} = sub {};
1029 while (!defined($child) && $retries--) {
1031 sleep 1 unless defined($child) || !$retries;
1033 if (!defined($child)) {
1039 $SIG{'CHLD'} = $oldsigchld;
1042 # double fork the child
1047 while (!defined($child2) && $retries2--) {
1049 sleep 1 unless defined($child2) || !$retries2;
1051 if (!defined($child2)) {
1053 $ec = 255 unless $ec;
1054 print $write2 ":$ec";
1059 # pass new child pid up to parent and exit
1060 print $write2 $child2;
1064 # this is the grandchild
1069 my $result = <$read2>;
1071 chomp $result if defined($result);
1072 if (!defined($result) || $result !~ /^:?\d+$/) {
1073 # something's wrong with the child -- kill it
1074 kill(9, $child) && waitpid($child, 0);
1075 my $oldsigpipe = $SIG{'PIPE'};
1076 # make sure the grandchild, if any,
1077 # doesn't run the success proc
1078 $SIG{'PIPE'} = sub {};
1082 $SIG{'PIPE'} = defined($oldsigpipe) ?
1083 $oldsigpipe : 'DEFAULT';
1085 $SIG{'CHLD'} = $oldsigchld;
1088 if ($result =~ /^:(\d+)$/) {
1089 # fork failed in child, there is no grandchild
1095 $SIG{'CHLD'} = $oldsigchld;
1098 # reap the child and set $child to grandchild's pid
1103 # grandchild that actually initiates the jobd.pl restart
1106 my $ufd = POSIX
::open($devnull, O_RDWR
);
1107 if (defined($ufd)) {
1108 dup2
($ufd, 0) unless $ufd == 0;
1109 dup2
($ufd, 1) unless $ufd == 1;
1110 dup2
($ufd, 2) unless $ufd == 2;
1111 POSIX
::close($ufd) unless $ufd == 0 || $ufd == 1 || $ufd == 2;
1116 my $result = setpgid
(0, 0);
1117 if (!defined($result)) {
1123 my $result = &$makepg;
1124 defined($result) or $result = &$makepg;
1125 defined($result) or $result = &$makepg;
1126 defined($result) or $result = &$makepg;
1129 my $result = <$read>;
1131 chomp $result if defined($result);
1132 if (!defined($result) || $result eq 0) {
1133 open JDLF
, '+<', $jdlf or _exit
(1);
1134 select((select(JDLF
),$|=1)[0]);
1135 print JDLF
"restart\n";
1136 truncate JDLF
, tell(JDLF
);
1143 $SIG{'CHLD'} = $oldsigchld;
1147 sub cancel_jobd_restart
{
1148 my $restarter = shift;
1149 return unless defined($restarter) && $restarter != 0;
1150 return -1 unless kill(0, $restarter);
1151 kill(9, $restarter) or die "failed to kill jobd restarter process (pid $restarter): $!\n";
1152 # we must not waitpid because $restarter was doubly forked and will
1153 # NOT send us a SIGCHLD when it terminates
1157 my $reexec = Girocco
::ExecUtil
->new;
1158 my $realpath0 = realpath
($0);
1160 close(DATA
) if fileno(DATA
);
1162 Getopt
::Long
::Configure
('bundling');
1164 my $parse_res = GetOptions
(
1166 pod2usage
(-verbose
=> 2, -exitval
=> 0, -input
=> $realpath0)},
1167 'quiet|q' => \
$quiet,
1168 'no-quiet' => sub {$quiet = 0},
1169 'progress|P' => \
$progress,
1170 'inetd|i' => sub {$inetd = 1; $syslog = 1; $quiet = 1;},
1171 'idle-timeout|t=i' => \
$idle_timeout,
1172 'daemon' => sub {$daemon = 1; $syslog = 1; $quiet = 1;},
1173 'max-lifetime=i' => \
$max_lifetime,
1174 'syslog|s:s' => \
$sfac,
1175 'no-syslog' => sub {$syslog = 0; $sfac = undef;},
1176 'stderr' => \
$stderr,
1177 'abbrev=i' => \
$abbrev,
1178 'show-fast-forward-info' => \
$showff,
1179 'no-show-fast-forward-info' => sub {$showff = 0},
1180 'same-pid' => \
$same_pid,
1181 'no-same-pid' => sub {$same_pid = 0},
1182 'status-interval=i' => \
$stiv,
1183 'idle-status-interval=i' => \
$idiv,
1184 ) || pod2usage
(-exitval
=> 2, -input
=> $realpath0);
1185 $same_pid = !$daemon unless defined($same_pid);
1186 $syslog = 1 if defined($sfac);
1187 $progress = 1 unless $quiet;
1188 $abbrev = 128 unless $abbrev > 0;
1189 pod2usage
(-msg
=> "--inetd and --daemon are incompatible") if ($inetd && $daemon);
1190 if (defined($idle_timeout)) {
1191 die "--idle-timeout must be a whole number\n" unless $idle_timeout =~ /^\d+$/;
1192 die "--idle-timeout may not be used without --inetd\n" unless $inetd;
1194 if (defined($max_lifetime)) {
1195 die "--max-lifetime must be a whole number\n" unless $max_lifetime =~ /^\d+$/;
1198 defined($max_lifetime) or $max_lifetime = 604800; # 1 week
1199 if (defined($stiv)) {
1200 die "--status-interval must be a whole number\n" unless $stiv =~ /^\d+$/;
1201 $statusintv = $stiv * 60;
1203 if (defined($idiv)) {
1204 die "--idle-status-interval must be a whole number\n" unless $idiv =~ /^\d+$/;
1205 $idleintv = $idiv * 60;
1208 open STDIN
, '<'.File
::Spec
->devnull or die "could not redirect STDIN to /dev/null\n" unless $inetd;
1209 open STDOUT
, '>&STDERR' if $inetd;
1211 use Sys
::Syslog
qw();
1214 $sfac = "user" unless defined($sfac) && $sfac ne "";
1217 $sfac = 'LOG_'.$sfac unless $sfac =~ /^LOG_/;
1219 my %badfac = map({("LOG_$_" => 1)}
1220 (qw(PID CONS ODELAY NDELAY NOWAIT PERROR FACMASK NFACILITIES PRIMASK LFMT)));
1221 eval "\$facility = Sys::Syslog::$sfac; 1" or die "invalid syslog facility: $ofac\n";
1222 die "invalid syslog facility: $ofac\n"
1223 if ($facility & ~0xf8) || ($facility >> 3) > 23 || $badfac{$sfac};
1224 tie
*STDERR
, 'OStream', $mode, $progname, $facility or die "tie failed";
1227 open STDOUT
, '>', File
::Spec
->devnull;
1235 my $restart_file = $Girocco::Config
::chroot.'/etc/taskd.restart';
1236 my $restart_active = 1;
1237 my $resumefd = $ENV{(SOCKFDENV
)};
1238 delete $ENV{(SOCKFDENV
)};
1239 if (defined($resumefd)) {{
1240 unless ($resumefd =~ /^(\d+)(?::(-?\d+))?$/) {
1241 warn "ignoring invalid ".SOCKFDENV
." environment value (\"$resumefd\") -- bad format\n";
1246 ($resumefd, $resumeino) = ($1, $2);
1248 unless (isfdopen
($resumefd)) {
1249 warn "ignoring invalid ".SOCKFDENV
." environment value -- fd \"$resumefd\" not open\n";
1254 unless (defined($resumeino)) {
1255 warn "ignoring invalid ".SOCKFDENV
." environment value (\"$resumefd\") -- missing inode\n";
1256 POSIX
::close($resumefd);
1261 my $sockloc = $Girocco::Config
::chroot.'/etc/taskd.socket';
1262 my $slinode = (stat($sockloc))[1];
1263 unless (defined($slinode) && -S _
) {
1264 warn "ignoring ".SOCKFDENV
." environment value; socket file does not exist: $sockloc\n";
1265 POSIX
::close($resumefd);
1269 open Test
, "<&$resumefd" or die "open: $!";
1270 my $sockname = getsockname Test
;
1272 $sockpath = unpack_sockaddr_un
$sockname if $sockname && sockaddr_family
($sockname) == AF_UNIX
;
1274 if (!defined($resumeino) || !defined($sockpath) || $resumeino != $slinode || realpath
($sockloc) ne realpath
($sockpath)) {
1275 warn "ignoring ".SOCKFDENV
." environment value; does not match socket file: $sockloc\n";
1276 POSIX
::close($resumefd);
1282 if ($inetd || defined($resumefd)) {
1283 my $fdopen = defined($resumefd) ?
$resumefd : 0;
1284 open Server
, "<&=$fdopen" or die "open: $!";
1285 setcloexec
(\
*Server
) if $fdopen > $^F
;
1286 my $sockname = getsockname Server
;
1287 die "getsockname: $!" unless $sockname;
1288 die "socket already connected! must be 'wait' socket\n" if getpeername Server
;
1289 die "getpeername: $!" unless $!{ENOTCONN
};
1290 my $st = getsockopt Server
, SOL_SOCKET
, SO_TYPE
;
1291 die "getsockopt(SOL_SOCKET, SO_TYPE): $!" unless $st;
1292 my $socktype = unpack('i', $st);
1293 die "stream socket required\n" unless defined $socktype && $socktype == SOCK_STREAM
;
1294 die "AF_UNIX socket required\n" unless sockaddr_family
($sockname) == AF_UNIX
;
1295 $NAME = unpack_sockaddr_un
$sockname;
1296 my $expected = $Girocco::Config
::chroot.'/etc/taskd.socket';
1297 if (realpath
($NAME) ne realpath
($expected)) {
1298 $restart_active = 0;
1299 warn "listening on \"$NAME\" but expected \"$expected\", restart file disabled\n";
1301 my $mode = (stat($NAME))[2];
1302 die "stat: $!" unless $mode;
1304 if (($mode & 0660) != 0660) {
1305 chmod(($mode|0660), $NAME) == 1 or die "chmod ug+rw \"$NAME\" failed: $!";
1308 $NAME = $Girocco::Config
::chroot.'/etc/taskd.socket';
1309 my $uaddr = sockaddr_un
($NAME);
1311 socket(Server
, PF_UNIX
, SOCK_STREAM
, 0) or die "socket failed: $!";
1312 die "already exists but not a socket: $NAME\n" if -e
$NAME && ! -S _
;
1314 # Do not unlink another instance's active listen socket!
1315 socket(my $sfd, PF_UNIX
, SOCK_STREAM
, 0) or die "socket failed: $!";
1316 connect($sfd, $uaddr) || $!{EPROTOTYPE
} and
1317 die "Live socket '$NAME' exists. Please make sure no other instance of taskd is running.\n";
1321 bind(Server
, $uaddr) or die "bind failed: $!";
1322 listen(Server
, SOMAXCONN
) or die "listen failed: $!";
1323 chmod 0666, $NAME or die "chmod failed: $!";
1324 $INO = (stat($NAME))[1] or die "stat failed: $!";
1327 foreach my $throttle (@Girocco::Config
::throttle_classes
, @throttle_defaults) {
1328 my $classname = $throttle->{"name"};
1330 Throttle
::GetClassInfo
($classname, $throttle);
1334 return $_[0] <= $_[1] ?
$_[0] : $_[1];
1337 pipe($piperead, $pipewrite) or die "pipe failed: $!";
1338 setnonblock
($piperead);
1339 select((select($pipewrite), $|=1)[0]);
1341 my $fdset_both = '';
1342 vec($fdset_both, fileno($piperead), 1) = 1;
1343 my $fdset_pipe = $fdset_both;
1344 vec($fdset_both, fileno(Server
), 1) = 1;
1347 my $penaltytime = $t;
1348 my $nextwakeup = $t + 60;
1349 my $nextstatus = undef;
1350 $nextstatus = $t + $statusintv if $statusintv;
1351 if ($restart_active) {
1352 unless (unlink($restart_file) || $!{ENOENT
}) {
1353 $restart_active = 0;
1354 statmsg
"restart file disabled could not unlink \"$restart_file\": $!";
1357 daemon
(1, 1) or die "failed to daemonize: $!\n" if $daemon;
1358 my $starttime = time;
1359 my $endtime = $max_lifetime ?
$starttime + $max_lifetime : 0;
1360 statmsg
"listening on $NAME";
1362 my ($rout, $eout, $nfound);
1366 my $adjustpenalty = sub {
1367 if ($penaltytime < $now) {
1368 my $credit = $now - $penaltytime;
1369 $penalty = $penalty > $credit ?
$penalty - $credit : 0;
1370 $penaltytime = $now;
1373 if (defined($nextstatus) && $now >= $nextstatus) {
1374 unless ($idlestatus && !$children && (!$idleintv || $now - $idlestatus < $idleintv)) {
1375 my $statmsg = "STATUS: $children active";
1380 foreach my $cls (sort(Throttle
::GetClassList
())) {
1381 my $inf = Throttle
::GetClassInfo
($cls);
1382 if ($inf->{'total'}) {
1383 $cnt += $inf->{'total'};
1384 push(@stats, substr(lc($cls),0,1)."=".
1385 $inf->{'total'}.'/'.$inf->{'active'});
1388 push(@stats, "?=".($children-$cnt)) if @stats && $cnt < $children;
1389 $statmsg .= " (".join(" ",@stats).")" if @stats;
1390 foreach (Throttle
::GetRunningPids
()) {
1391 my ($cls, $ts, $desc) = Throttle
::GetPidInfo
($_);
1393 push(@running, "[${cls}::$desc] ".human_duration
($now-$ts));
1397 $statmsg .= ", idle " . human_duration
($idlesecs)
1398 if !$children && ($idlesecs = $now - $idlestart) >= 2;
1400 statmsg
"STATUS: currently running: ".join(", ", @running)
1402 $idlestatus = $now if !$children;
1404 $nextstatus += $statusintv while $nextstatus <= $now;
1406 $nextwakeup += 60, $now = time while ($wait = $nextwakeup - $now) <= 0;
1407 $wait = _min
($wait, (Throttle
::ServiceQueue
()||60));
1408 &$adjustpenalty; # this prevents ignoring accept when we shouldn't
1410 if ($penalty <= $maxspawn) {
1411 $fdset = $fdset_both;
1413 $fdset = $fdset_pipe;
1414 $wait = $penalty - $maxspawn if $wait > $penalty - $maxspawn;
1416 $nfound = select($rout=$fdset, undef, $eout=$fdset, $wait);
1417 logmsg
("select failed: $!"), exit(1) unless $nfound >= 0 || $!{EINTR
} || $!{EAGAIN
};
1419 Throttle
::RemoveSupplicant
($reaped) while ($reaped = shift(@reapedpids));
1421 &$adjustpenalty; # this prevents banking credits for elapsed time
1422 if (!$children && !$nfound && $restart_active && (($endtime && $now >= $endtime) || -e
$restart_file)) {
1423 statmsg
"RESTART: restart requested; max lifetime ($max_lifetime) exceeded" if $endtime && $now >= $endtime;
1424 $SIG{CHLD
} = sub {};
1425 my $restarter = schedule_jobd_restart
($inetd);
1426 if (defined($restarter) && !$restarter) {
1427 statmsg
"RESTART: restart requested; retrying failed scheduling of jobd restart: $!";
1429 $restarter = schedule_jobd_restart
;
1430 if (!defined($restarter)) {
1431 statmsg
"RESTART: restart requested; reschedule skipped jobd no longer running";
1432 } elsif (defined($restarter) && !$restarter) {
1433 statmsg
"RESTART: restart requested; retry of jobd restart scheduling failed, skipping jobd restart: $!";
1438 statmsg
"RESTART: restart requested; now exiting for inetd restart";
1439 statmsg
"RESTART: restart requested; jobd restart scheduled in 5 seconds" if $restarter;
1443 statmsg
"RESTART: restart requested; now restarting";
1444 statmsg
"RESTART: restart requested; jobd restart scheduled in 5 seconds" if $restarter;
1445 setnoncloexec
(\
*Server
);
1446 $reexec->setenv(SOCKFDENV
, fileno(Server
).":$INO");
1447 $reexec->reexec($same_pid);
1448 setcloexec
(\
*Server
) if fileno(Server
) > $^F
;
1449 statmsg
"RESTART: continuing after failed restart: $!";
1451 cancel_jobd_restart
($restarter) if $restarter;
1452 statmsg
"RESTART: scheduled jobd restart has been cancelled" if $restarter;
1456 if ($idle_timeout && !$children && !$nfound && $now - $idlestart >= $idle_timeout) {
1457 statmsg
"idle timeout (@{[human_duration($idle_timeout)]}) exceeded now exiting";
1460 } while $nfound < 1;
1461 my $reout = $rout | $eout;
1462 if (vec($reout, fileno($piperead), 1)) {{
1466 do {$bytes = sysread($piperead, $pipebuff, 512, length($pipebuff))}
1467 while (!defined($bytes) && $!{EINTR
});
1468 last if !defined($bytes) && $!{EAGAIN
};
1469 die "sysread failed: $!" unless defined $bytes;
1470 # since we always keep a copy of $pipewrite open EOF is fatal
1471 die "sysread returned EOF on pipe read" unless $bytes;
1472 $nloff = index($pipebuff, "\n", 0);
1473 if ($nloff < 0 && length($pipebuff) >= 512) {
1475 print STDERR
"discarding 512 bytes of control pipe data with no \\n found\n";
1477 redo unless $nloff >= 0;
1479 last unless $nloff >= 0;
1481 my $msg = substr($pipebuff, 0, $nloff);
1482 substr($pipebuff, 0, $nloff + 1) = '';
1483 $nloff = index($pipebuff, "\n", 0);
1484 process_pipe_msg
($msg) if length($msg);
1485 } while $nloff >= 0;
1488 next unless vec($reout, fileno(Server
), 1);
1489 unless (accept(Client
, Server
)) {
1490 logmsg
"accept failed: $!" unless $!{EINTR
};
1493 logmsg
"connection on $NAME";
1497 $inp = <STDIN
> if defined($inp) && $inp eq "\n";
1498 chomp $inp if defined($inp);
1499 # ignore empty and "nop" connects
1500 defined($inp) && $inp ne "" && $inp ne "nop" or exit 0;
1501 my ($cmd, $arg) = $inp =~ /^([a-zA-Z][a-zA-Z0-9._+-]*)(?:\s+(.*))?$/;
1502 defined($arg) or $arg = '';
1503 if ($cmd eq 'ref-changes') {
1505 } elsif ($cmd eq 'clone') {
1507 } elsif ($cmd eq 'ref-change') {
1508 statmsg
"processing obsolete ref-change message (please switch to ref-changes)";
1510 } elsif ($cmd eq 'throttle') {
1513 statmsg
"ignoring unknown command: $cmd";
1530 taskd.pl - Perform Girocco service tasks
1537 -h | --help detailed instructions
1538 -q | --quiet run quietly
1539 --no-quiet do not run quietly
1540 -P | --progress show occasional status updates
1541 -i | --inetd run as inetd unix stream wait service
1542 implies --quiet --syslog
1543 -t SECONDS | --idle-timeout=SECONDS how long to wait idle before exiting
1545 --daemon become a background daemon
1546 implies --quiet --syslog
1547 --max-lifetime=SECONDS how long before graceful restart
1548 default is 1 week, 0 disables
1549 -s | --syslog[=facility] send messages to syslog instead of
1550 stderr but see --stderr
1552 --no-syslog do not send message to syslog
1553 --stderr always send messages to stderr too
1554 --abbrev=n abbreviate hashes to n (default is 8)
1555 --show-fast-forward-info show fast-forward info (default is on)
1556 --no-show-fast-forward-info disable showing fast-forward info
1557 --same-pid keep same pid during graceful restart
1558 --no-same-pid do not keep same pid on graceful rstrt
1559 --status-interval=MINUTES status update interval (default 1)
1560 --idle-status-interval=IDLEMINUTES idle status interval (default 60)
1564 taskd.pl is Girocco's service request servant; it listens for service requests
1565 such as new clone requests and ref update notifications and spawns a task to
1566 perform the requested action.
1574 Print the full description of taskd.pl's options.
1578 Suppress non-error messages, e.g. for use when running this task as an inetd
1579 service. Enabled by default by --inetd.
1583 Enable non-error messages. When running in --inetd mode these messages are
1584 sent to STDERR instead of STDOUT.
1588 Show information about the current status of the task operation occasionally.
1589 This is automatically enabled if --quiet is not given.
1593 Run as an inetd wait service. File descriptor 0 must be an unconnected unix
1594 stream socket ready to have accept called on it. To be useful, the unix socket
1595 should be located at "$Girocco::Config::chroot/etc/taskd.socket". A warning
1596 will be issued if the socket is not in the expected location. Socket file
1597 permissions will be adjusted if necessary and if they cannot be taskd.pl will
1598 die. The --inetd option also enables the --quiet and --syslog options but
1599 --no-quiet and --no-syslog may be used to alter that.
1601 The correct specification for the inetd socket is a "unix" protocol "stream"
1602 socket in "wait" mode with user and group writable permissions (0660). An
1603 attempt will be made to alter the socket's file mode if needed and if that
1604 cannot be accomplished taskd.pl will die.
1606 Although most inetd stream services run in nowait mode, taskd.pl MUST be run
1607 in wait mode and will die if the passed in socket is already connected.
1609 Note that while *BSD's inetd happily supports unix sockets (and so does
1610 Darwin's launchd), neither xinetd nor GNU's inetd supports unix sockets.
1611 However, systemd does seem to.
1613 =item B<--idle-timeout=SECONDS>
1615 Only permitted when running in --inetd mode. After SECONDS of inactivity
1616 (i.e. all outstanding tasks have completed and no new requests have come in)
1617 exit normally. The default is no timeout at all (a SECONDS value of 0).
1618 Note that it may actually take up to SECONDS+60 for the idle exit to occur.
1622 Fork and become a background daemon. Implies B<--syslog> and B<--quiet> (which
1623 can be altered by subsequent B<--no-syslog> and/or B<--no-quiet> options).
1624 Also implies B<--no-same-pid>, but since graceful restarts work by re-exec'ing
1625 taskd.pl with all of its original arguments, using B<--same-pid> won't really
1626 be effective with B<--daemon> since although it will cause the graceful restart
1627 exec to happen from the same pid, when the B<--daemon> option is subsequently
1628 processed it will end up in a new pid anyway.
1630 =item B<--max-lifetime=SECONDS>
1632 After taskd has been running for SECONDS of realtime, it will behave as though
1633 a graceful restart has been requested. A graceful restart takes place the
1634 next time taskd becomes idle (which may require up to 60 seconds to notice).
1635 If jobd is running when a graceful restart occurs, then jobd will also receive
1636 a graceful restart request at that time. The default value is 1 week (604800),
1637 set to 0 to disable.
1639 =item B<--syslog[=facility]>
1641 Normally error output is sent to STDERR. With this option it's sent to
1642 syslog instead. Note that when running in --inetd mode non-error output is
1643 also affected by this option as it's sent to STDERR in that case. If
1644 not specified, the default for facility is LOG_USER. Facility names are
1645 case-insensitive and the leading 'LOG_' is optional. Messages are logged
1646 with the LOG_NOTICE priority.
1648 =item B<--no-syslog>
1650 Send error message output to STDERR but not syslog.
1654 Always send error message output to STDERR. If --syslog is in effect then
1655 a copy will also be sent to syslog. In --inetd mode this applies to non-error
1660 Abbreviate displayed hash values to only the first n hexadecimal characters.
1661 The default is 8 characters. Set to 0 for no abbreviation at all.
1663 =item B<--show-fast-forward-info>
1665 Instead of showing ' -> ' in ref-change/ref-notify update messages, show either
1666 '..' for a fast-forward, creation or deletion or '...' for non-fast-forward.
1667 This requires running an extra git command for each ref update that is not a
1668 creation or deletion in order to determine whether or not it's a fast forward.
1670 =item B<--no-show-fast-forward-info>
1672 Disable showing of fast-forward information for ref-change/ref-notify update
1673 messages. Instead just show a ' -> ' indicator.
1677 When performing a graceful restart, perform the graceful restart exec from
1678 the same pid rather than switching to a new one. This is implied when
1679 I<--daemon> is I<NOT> used.
1681 =item B<--no-same-pid>
1683 When performing a graceful restart, perform the graceful restart exec after
1684 switching to a new pid. This is implied when I<--daemon> I<IS> used.
1686 =item B<--status-interval=MINUTES>
1688 If progress is enabled (with --progress or by default if no --inetd or --quiet)
1689 status updates are shown at each MINUTES interval. Setting the interval to 0
1690 disables them entirely even with --progress.
1692 =item B<--idle-status-interval=IDLEMINUTES>
1694 Two consecutive "idle" status updates with no intervening activity will not be
1695 shown unless IDLEMINUTES have elapsed between them. The default is 60 minutes.
1696 Setting the interval to 0 prevents any consecutive idle updates (with no
1697 activity between them) from appearing at all.