diff options
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 1 | ||||
-rw-r--r-- | Bugzilla/JobQueue.pm | 21 | ||||
-rw-r--r-- | Bugzilla/JobQueue/Runner.pm | 163 | ||||
-rw-r--r-- | Bugzilla/JobQueue/Worker.pm | 30 | ||||
-rw-r--r-- | jobqueue-worker.pl | 40 | ||||
-rwxr-xr-x | jobqueue.pl | 9 |
6 files changed, 195 insertions, 69 deletions
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 97ab69b9b..71169345b 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -225,6 +225,7 @@ sub FILESYSTEM { 'metrics.pl' => { perms => WS_EXECUTE }, 'Makefile.PL' => { perms => OWNER_EXECUTE }, 'gen-cpanfile.pl' => { perms => OWNER_EXECUTE }, + 'jobqueue-worker.pl' => { perms => OWNER_EXECUTE }, 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE }, 'Bugzilla.pm' => { perms => CGI_READ }, diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm index 55d40bfb8..e3cf9733f 100644 --- a/Bugzilla/JobQueue.pm +++ b/Bugzilla/JobQueue.pm @@ -14,6 +14,10 @@ use warnings; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Install::Util qw(install_string); +use Bugzilla::DaemonControl qw(catch_signal); +use IO::Async::Timer::Periodic; +use IO::Async::Loop; +use Future; use base qw(TheSchwartz); # This maps job names for Bugzilla::JobQueue to the appropriate modules. @@ -91,6 +95,23 @@ sub insert { return $retval; } +sub work { + my ($self, $delay) = @_; + $delay ||= 5; + my $loop = IO::Async::Loop->new; + my $timer = IO::Async::Timer::Periodic->new( + first_interval => 0, + interval => $delay, + reschedule => 'drift', + on_tick => sub { $self->work_once } + ); + $loop->add($timer); + $timer->start; + Future->wait_any(map { catch_signal($_) } qw( INT TERM HUP ))->get; + $timer->stop; + $loop->remove($timer); +} + # Clear the request cache at the start of each run. sub work_once { my $self = shift; diff --git a/Bugzilla/JobQueue/Runner.pm b/Bugzilla/JobQueue/Runner.pm index 5b3164ef9..d95f9c3c3 100644 --- a/Bugzilla/JobQueue/Runner.pm +++ b/Bugzilla/JobQueue/Runner.pm @@ -14,23 +14,32 @@ package Bugzilla::JobQueue::Runner; use 5.10.1; use strict; use warnings; +use autodie qw(open close unlink system); +use Bugzilla::Constants; +use Bugzilla::DaemonControl qw(:utils); +use Bugzilla::JobQueue::Worker; +use Bugzilla::JobQueue; +use Bugzilla::Util qw(get_text); use Cwd qw(abs_path); +use English qw(-no_match_vars $PROGRAM_NAME $EXECUTABLE_NAME); use File::Basename; use File::Copy; +use File::Spec::Functions qw(catfile); +use Future; +use IO::Async::Loop; +use IO::Async::Process; +use IO::Async::Signal; use Pod::Usage; -use Bugzilla::Constants; -use Bugzilla::JobQueue; -use Bugzilla::Util qw(get_text); -BEGIN { eval "use base qw(Daemon::Generic)"; } +use parent qw(Daemon::Generic); -our $VERSION = BUGZILLA_VERSION; +our $VERSION = 2; # Info we need to install/uninstall the daemon. -our $chkconfig = "/sbin/chkconfig"; -our $initd = "/etc/init.d"; -our $initscript = "bugzilla-queue"; +our $chkconfig = '/sbin/chkconfig'; +our $initd = '/etc/init.d'; +our $initscript = 'bugzilla-queue'; # The Daemon::Generic docs say that it uses all sorts of # things from gd_preconfig, but in fact it does not. The @@ -40,11 +49,10 @@ sub gd_preconfig { my $self = shift; my $pidfile = $self->{gd_args}{pidfile}; - if (!$pidfile) { - $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} - . ".pid"; + if ( !$pidfile ) { + $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} . '.pid'; } - return (pidfile => $pidfile); + return ( pidfile => $pidfile ); } # All config other than the pidfile has to be done in gd_getopt @@ -54,24 +62,30 @@ sub gd_getopt { $self->SUPER::gd_getopt(); - if ($self->{gd_args}{progname}) { + if ( $self->{gd_args}{progname} ) { $self->{gd_progname} = $self->{gd_args}{progname}; } else { - $self->{gd_progname} = basename($0); + $self->{gd_progname} = basename($PROGRAM_NAME); } - # There are places that Daemon Generic's new() uses $0 instead of + # There are places that Daemon Generic's new() uses $PROGRAM_NAME instead of # gd_progname, which it really shouldn't, but this hack fixes it. - $self->{_original_zero} = $0; - $0 = $self->{gd_progname}; + $self->{_original_program_name} = $PROGRAM_NAME; + + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = $self->{gd_progname}; + ## use critic } sub gd_postconfig { my $self = shift; + # See the hack above in gd_getopt. This just reverses it # in case anything else needs the accurate $0. - $0 = delete $self->{_original_zero}; + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = delete $self->{_original_program_name}; + ## use critic } sub gd_more_opt { @@ -83,8 +97,8 @@ sub gd_more_opt { } sub gd_usage { - pod2usage({ -verbose => 0, -exitval => 'NOEXIT' }); - return 0 + pod2usage( { -verbose => 0, -exitval => 'NOEXIT' } ); + return 0; } sub gd_can_install { @@ -95,66 +109,63 @@ sub gd_can_install { my $sysconfig = '/etc/sysconfig'; my $config_file = "$sysconfig/$initscript"; - if (!-x $chkconfig or !-d $initd) { + if ( !-x $chkconfig || !-d $initd ) { return $self->SUPER::gd_can_install(@_); } return sub { - if (!-w $initd) { + if ( !-w $initd ) { print "You must run the 'install' command as root.\n"; return; } - if (-e $dest_file) { + if ( -e $dest_file ) { print "$initscript already in $initd.\n"; } else { - copy($source_file, $dest_file) + copy( $source_file, $dest_file ) or die "Could not copy $source_file to $dest_file: $!"; - chmod(0755, $dest_file) + chmod 0755, $dest_file or die "Could not change permissions on $dest_file: $!"; } - system($chkconfig, '--add', $initscript); - print "$initscript installed.", - " To start the daemon, do \"$dest_file start\" as root.\n"; + system $chkconfig, '--add', $initscript; + print "$initscript installed.", " To start the daemon, do \"$dest_file start\" as root.\n"; - if (-d $sysconfig and -w $sysconfig) { - if (-e $config_file) { + if ( -d $sysconfig and -w $sysconfig ) { + if ( -e $config_file ) { print "$config_file already exists.\n"; return; } - open(my $config_fh, ">", $config_file) - or die "Could not write to $config_file: $!"; - my $directory = abs_path(dirname($self->{_original_zero})); - my $owner_id = (stat $self->{_original_zero})[4]; - my $owner = getpwuid($owner_id); - print $config_fh <<END; + open my $config_fh, '>', $config_file; + my $directory = abs_path( dirname( $self->{_original_program_name} ) ); + my $owner_id = ( stat $self->{_original_program_name} )[4]; + my $owner = getpwuid $owner_id; + print $config_fh <<"END"; #!/bin/sh BUGZILLA="$directory" USER=$owner END - close($config_fh); + close $config_fh; } else { print "Please edit $dest_file to configure the daemon.\n"; } - } + } } sub gd_can_uninstall { my $self = shift; - if (-x $chkconfig and -d $initd) { + if ( -x $chkconfig and -d $initd ) { return sub { - if (!-e "$initd/$initscript") { + if ( !-e "$initd/$initscript" ) { print "$initscript not installed.\n"; return; } - system($chkconfig, '--del', $initscript); - print "$initscript disabled.", - " To stop it, run: $initd/$initscript stop\n"; - } + system $chkconfig, '--del', $initscript; + print "$initscript disabled.", " To stop it, run: $initd/$initscript stop\n"; + } } return $self->SUPER::gd_can_install(@_); @@ -164,49 +175,71 @@ sub gd_check { my $self = shift; # Get a count of all the jobs currently in the queue. - my $jq = Bugzilla->job_queue(); - my @dbs = $jq->bz_databases(); + my $jq = Bugzilla->job_queue(); + my @dbs = $jq->bz_databases(); my $count = 0; foreach my $driver (@dbs) { - $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []); + $count += $driver->select_one( 'SELECT COUNT(*) FROM ts_job', [] ); } - print get_text('job_queue_depth', { count => $count }) . "\n"; + print get_text( 'job_queue_depth', { count => $count } ) . "\n"; } +# override this to use IO::Async. sub gd_setup_signals { - my $self = shift; - $self->SUPER::gd_setup_signals(); - $SIG{TERM} = sub { $self->gd_quit_event(); } + my $self = shift; + my @signals = qw( INT HUP TERM ); + $self->{_signal_future} = Future->wait_any( map { catch_signal( $_, $_ ) } @signals ); } sub gd_other_cmd { my ($self) = shift; - if ($ARGV[0] eq "once") { - $self->_do_work("work_once"); - - exit(0); + if ( $ARGV[0] eq 'once' ) { + Bugzilla::JobQueue::Worker->run('work_once'); + exit; } - + $self->SUPER::gd_other_cmd(); } +sub gd_quit_event { FATAL('gd_quit_event() should never be called') } +sub gd_reconfig_event { FATAL('gd_reconfig_event() should never be called') } + sub gd_run { my $self = shift; - $self->_do_work("work"); + # This is so the process shows up in (h)top in a useful way. + local $PROGRAM_NAME = "$self->{gd_progname} [supervisor]"; + my $code = $self->run_worker('work')->get; + unlink $self->{gd_pidfile}; + exit $code; } -sub _do_work { - my ($self, $fn) = @_; +# This executes the script "jobqueue-worker.pl" +# $EXECUTABLE_NAME is the name of the perl interpreter. +sub run_worker { + my ( $self, $fn ) = @_; - my $jq = Bugzilla->job_queue(); - $jq->set_verbose($self->{debug}); - foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) { - eval "use $module"; - $jq->can_do($module); + my $script = catfile( bz_locations->{cgi_path}, 'jobqueue-worker.pl' ); + my @command = ( $EXECUTABLE_NAME, $script, '--function' => $fn ); + if ( $self->{gd_args}{progname} ) { + push @command, '--name' => "$self->{gd_args}{progname} [worker]"; } - $jq->$fn; + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my $worker = IO::Async::Process->new( + command => \@command, + on_finish => on_finish($exit_f), + on_exception => on_exception( 'jobqueue worker', $exit_f ) + ); + $exit_f->on_cancel( + sub { + DEBUG('terminate worker'); + $worker->kill('TERM'); + } + ); + $loop->add($worker); + return $exit_f; } 1; diff --git a/Bugzilla/JobQueue/Worker.pm b/Bugzilla/JobQueue/Worker.pm new file mode 100644 index 000000000..db8ebe35e --- /dev/null +++ b/Bugzilla/JobQueue/Worker.pm @@ -0,0 +1,30 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::JobQueue::Worker; +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::Logging; +use Module::Runtime qw(require_module); + +sub run { + my ( $class, $fn ) = @_; + DEBUG("Starting up for $fn"); + my $jq = Bugzilla->job_queue(); + + DEBUG('Loading jobqueue modules'); + foreach my $module ( values %{ Bugzilla::JobQueue->job_map() } ) { + DEBUG("JobQueue can do $module"); + require_module($module); + $jq->can_do($module); + } + $jq->$fn; +} + +1; diff --git a/jobqueue-worker.pl b/jobqueue-worker.pl new file mode 100644 index 000000000..6205e1bf4 --- /dev/null +++ b/jobqueue-worker.pl @@ -0,0 +1,40 @@ +#!/usr/bin/perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use 5.10.1; +use strict; +use warnings; + +use File::Basename qw(basename dirname); +use File::Spec::Functions qw(catdir rel2abs); + +BEGIN { + require lib; + my $dir = rel2abs( dirname(__FILE__) ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); + chdir $dir or die "chdir $dir failed: $!"; + +} + +use Bugzilla::JobQueue::Worker; +use Bugzilla::JobQueue; +use Bugzilla; +use English qw(-no_match_vars $PROGRAM_NAME); +use Getopt::Long qw(:config gnu_getopt); + +BEGIN { Bugzilla->extensions } +my $name = basename(__FILE__); + +GetOptions( 'name=s' => \$name ); + +if ($name) { + ## no critic (Variables::RequireLocalizedPunctuationVars) + $PROGRAM_NAME = $name; + ## use critic +} +Bugzilla::JobQueue::Worker->run('work'); diff --git a/jobqueue.pl b/jobqueue.pl index f5541e0fb..d9791b3d4 100755 --- a/jobqueue.pl +++ b/jobqueue.pl @@ -10,12 +10,13 @@ use 5.10.1; use strict; use warnings; -use File::Basename; -use File::Spec; +use File::Basename qw(dirname); +use File::Spec::Functions qw(catdir rel2abs); + BEGIN { require lib; - my $dir = File::Spec->rel2abs(dirname(__FILE__)); - lib->import($dir, File::Spec->catdir($dir, 'lib'), File::Spec->catdir($dir, qw(local lib perl5))); + my $dir = rel2abs( dirname(__FILE__) ); + lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) ); chdir $dir or die "chdir $dir failed: $!"; } |