Francois Gouget : testbot: Better avoid race conditions in VMs::RunRevert( ).
Alexandre Julliard
julliard at winehq.org
Mon Oct 16 03:09:04 CDT 2017
Module: tools
Branch: master
Commit: fc96df4c171a80dc1df960c44d9c8734d987348c
URL: http://source.winehq.org/git/tools.git/?a=commit;h=fc96df4c171a80dc1df960c44d9c8734d987348c
Author: Francois Gouget <fgouget at codeweavers.com>
Date: Mon Oct 16 03:43:19 2017 +0200
testbot: Better avoid race conditions in VMs::RunRevert().
In order to use LibvirtTool for more than reverts, avoiding race
conditions cannot rely on the special relationship between
Status=reverting and ChildPid. So avoid the race conditions the right
way in VMs::RunRevert().
Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>
---
testbot/lib/WineTestBot/VMs.pm | 97 ++++++++++++++++++++++++++++++++----------
1 file changed, 75 insertions(+), 22 deletions(-)
diff --git a/testbot/lib/WineTestBot/VMs.pm b/testbot/lib/WineTestBot/VMs.pm
index 629d408..86dee6c 100644
--- a/testbot/lib/WineTestBot/VMs.pm
+++ b/testbot/lib/WineTestBot/VMs.pm
@@ -142,6 +142,8 @@ are undergoing maintenance.
=cut
+use File::Basename;
+
use ObjectModel::BackEnd;
use WineTestBot::Config;
use WineTestBot::Engine::Notify;
@@ -309,38 +311,89 @@ sub RunRevert($)
{
my ($self) = @_;
- $self->Status("reverting");
- my ($ErrProperty, $ErrMessage) = $self->Save();
- return $ErrMessage if (defined $ErrMessage);
-
- # Make sure the child process does not inherit the database connection
+ my $Tool = "LibvirtTool.pl";
+ my $Args = ["$BinDir/$Tool", "--log-only", "revert", $self->GetKey()];
+
+ # There are two $VM->ChildPid race conditions to avoid:
+ # - Between the child process and new calls to ScheduleJobs().
+ # We cannot leave setting ChildPid to the child process because then it
+ # may still not be set by the time the next ScheduleJobs() call happens,
+ # which would result in a new child being started.
+ # Note that the status is not guaranteed to change in _RunVMTool() so it
+ # cannot be relied on to avoid this race.
+ # - Between RunRevert() and the exit of the child process.
+ # The child process may exit before RunRevert() gets around to setting
+ # ChildPid after the fork(). This would result in ChildPid remaining set
+ # indefinitely.
+ # So set ChildPid in the parent and synchronize with the child so it only
+ # starts once this is done.
+
+ # Make sure the child process will use its own database connection
$self->GetBackEnd()->Close();
- my $Pid = fork;
+ use Fcntl;
+ my ($fd_read, $fd_write);
+ pipe($fd_read, $fd_write); # For synchronization
+
+ my $Pid = fork();
if (!defined $Pid)
{
+ close($fd_read);
+ close($fd_write);
return "Unable to start child process: $!";
}
- elsif (!$Pid)
+ if ($Pid)
{
- $ENV{PATH} = "/usr/bin:/bin";
- delete $ENV{ENV};
- require WineTestBot::Log;
- WineTestBot::Log::SetupRedirects();
- exec("$BinDir/LibvirtTool.pl", "--log-only", "revert", $self->GetKey()) or
- WineTestBot::Log::LogMsg("Unable to exec LibvirtTool.pl: $!\n");
- exit(1);
+ close($fd_read);
+
+ # Set the Status and ChildPid
+ $self->Status("reverting");
+ $self->ChildPid($Pid);
+ my ($ErrProperty, $ErrMessage) = $self->Save();
+ if ($ErrMessage)
+ {
+ close($fd_write);
+ return "Could not set the $Tool pid: $ErrMessage ($ErrProperty)";
+ }
+
+ # Let the child go
+ close($fd_write);
+
+ return undef;
}
- # Note that if the child process completes quickly (typically due to some
- # error), it may set ChildPid to undef before we get here. So we may end up
- # with non-reverting VMs for which ChildPid is set. That's ok because
- # ChildPid should be ignored anyway if Status is not 'reverting' or
- # 'sleeping'.
- $self->ChildPid($Pid);
- $self->Save();
+ # Wait for the parent to set $VM->ChildPid
+ close($fd_write);
+ sysread($fd_read, $fd_write, 1);
+ close($fd_read);
+
+ # Get up-to-date information on the VM and verify the pid. If the parent
+ # failed to set the pid it may try to start another process at any time.
+ # So abort in order avoid interference.
+ require WineTestBot::VMs;
+ my $VM = WineTestBot::VMs::CreateVMs()->GetItem($self->GetKey());
+ exit 1 if (($VM->ChildPid || 0) != $$);
+ $self->GetBackEnd()->Close();
+
+ require WineTestBot::Log;
+ WineTestBot::Log::LogMsg("Starting child: @$Args\n");
+
+ # Set up the file descriptors for the new process
+ WineTestBot::Log::SetupRedirects();
+
+ $ENV{PATH} = "/usr/bin:/bin";
+ exec(@$Args) or
+ WineTestBot::Log::LogMsg("Unable to exec $Tool: $!\n");
- return undef;
+ # Reset the Status and ChildPid since the exec failed
+ $self->Status("offline");
+ $self->ChildPid(undef);
+ my ($ErrProperty, $ErrMessage) = $self->Save();
+ if ($ErrMessage)
+ {
+ WineTestBot::Log::LogMsg("Could not remove the $Tool pid: $ErrMessage ($ErrProperty)\n");
+ }
+ exit 1;
}
More information about the wine-cvs
mailing list