Skip to content

Commit b3c41ca

Browse files
bdracotoddr
authored andcommitted
Avoid giving up a lock when we are forking
case CPANEL-17403 The TaskQueue would give up the lock on the state file in order to process as task. Since cPanel processes most of our tasks with the cPanel::TaskQueue::ChildProcessor task in the background this added a write state, unlock, relock, re-read from disk cycle to every task being processed. Since we return right after queueprocd fork()s there is little concern about holding the lock open. This should also reduce the potential for unknown race conditions as we no longer give up the lock in the middle of processing a task which had caused unexpected results in the past.
1 parent 5a62176 commit b3c41ca

3 files changed

Lines changed: 63 additions & 20 deletions

File tree

lib/cPanel/StateFile.pm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,18 @@ sub import {
175175
return;
176176
}
177177

178+
sub _in_child {
179+
my ($self) = @_;
180+
181+
# Ensure that the child process does not
182+
# remove the lock as that is the job of
183+
# the parent.
184+
$self->{'lock_file'} = undef;
185+
$self->{'file_handle'} = undef;
186+
187+
return;
188+
}
189+
178190
sub _unlock {
179191
my $self = shift;
180192
my $state_file = $self->{state_file};

lib/cPanel/TaskQueue.pm

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -589,28 +589,49 @@ END { undef %valid_processors } # case CPANEL-10871 to avoid a SEGV during gl
589589
push @{ $self->{processing_list} }, $task;
590590
$self->_add_task_to_deferral_object( $task, $processor );
591591

592-
# Finished making changes, save to disk.
593-
$guard->update_file();
594-
595-
# I don't want to stay locked while processing.
596592
my $pid;
597593
my $ex;
598-
$guard->call_unlocked(
599-
sub {
600-
my $orig_alarm;
601-
eval {
602-
local $SIG{'ALRM'} = sub { die "time out reached\n"; };
603-
$orig_alarm = alarm( $self->_timeout($processor) );
604-
$pid = $processor->process_task( $task->clone(), $self->{disk_state}->get_logger() );
605-
alarm $orig_alarm;
606-
1;
607-
} or do {
608-
$ex = $@; # save exception for later
609-
alarm $orig_alarm;
610-
};
611-
}
612-
);
613594

595+
if ( $processor->isa('cPanel::TaskQueue::ChildProcessor') ) {
596+
597+
#
598+
# Will fork() so there is no concern about the parent
599+
# keeping the lock since it should return right away and
600+
# will not be blocking other processes from getting a lock
601+
# as soon as we return from this sub.
602+
#
603+
# This avoids going though a whole cycle of update_file, unlock,
604+
# relock, and re-read from disk
605+
#
606+
# No need to set an alarm as we are going to fork() and
607+
# ChildProcessor already handles timeouts
608+
#
609+
eval { $pid = $processor->process_task( $task->clone(), $self->{disk_state}->get_logger(), $guard ) } or do {
610+
$ex = $@;
611+
};
612+
}
613+
else {
614+
# We are going to have to unlock and relock
615+
# Finished making changes, save to disk.
616+
$guard->update_file();
617+
618+
# I don't want to stay locked while processing.
619+
$guard->call_unlocked(
620+
sub {
621+
my $orig_alarm;
622+
eval {
623+
local $SIG{'ALRM'} = sub { die "time out reached\n"; };
624+
$orig_alarm = alarm( $self->_timeout($processor) );
625+
$pid = $processor->process_task( $task->clone(), $self->{disk_state}->get_logger() );
626+
alarm $orig_alarm;
627+
1;
628+
} or do {
629+
$ex = $@; # save exception for later
630+
alarm $orig_alarm;
631+
};
632+
}
633+
);
634+
}
614635
# Deal with a child process or remove from processing.
615636
if ($pid) {
616637
$task->set_pid($pid);

lib/cPanel/TaskQueue/ChildProcessor.pm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use cPanel::TaskQueue::Scheduler ();
3535
}
3636

3737
sub process_task {
38-
my ( $self, $task, $logger ) = @_;
38+
my ( $self, $task, $logger, $guard ) = @_;
3939
my $pid = fork();
4040

4141
$logger->throw( q{Unable to start a child process to handle the '} . $task->command() . "' task\n" )
@@ -44,6 +44,16 @@ use cPanel::TaskQueue::Scheduler ();
4444
# Parent returns
4545
return $pid if $pid;
4646

47+
if ( !$guard ) {
48+
die __PACKAGE__ . ': expected 3 arguments to process_task($task, $logger, $guard): $guard was missing';
49+
}
50+
51+
# Now in child
52+
# Ensure the child process never unlocks
53+
# the lock file as this should always be done
54+
# in the parent.
55+
$guard->_in_child();
56+
4757
my $timeout = $self->get_child_timeout() || $task->child_timeout();
4858
my $oldalarm;
4959
eval {

0 commit comments

Comments
 (0)