You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

241 lines
7.3 KiB

package ksb::IPC;
# Handles the asynchronous communications needed to perform update and build
# processes at the same time. This can be thought of as a partially-abstract
# class, really you should use IPC::Null (which is fully synchronous) or
# IPC::Pipe, which both fall back to common methods implemented here.
use strict;
use warnings;
use v5.10;
our $VERSION = '0.20';
use ksb::Util; # make_exception, list_has
use ksb::Debug;
# IPC message types
use constant {
MODULE_SUCCESS => 1, # Used for a successful src checkout
MODULE_FAILURE => 2, # Used for a failed src checkout
MODULE_SKIPPED => 3, # Used for a skipped src checkout (i.e. build anyways)
MODULE_UPTODATE => 4, # Used to skip building a module when had no code updates
# One of these messages should be the first message placed on the queue.
ALL_SKIPPED => 5, # Used to indicate a skipped update process (i.e. build anyways)
ALL_FAILURE => 6, # Used to indicate a major update failure (don't build)
ALL_UPDATING => 7, # Informational message, feel free to start the build.
# Used to indicate specifically that a source conflict has occurred.
MODULE_CONFLICT => 8,
};
sub new
{
my $class = shift;
# Must bless a hash ref since subclasses expect it.
return bless {}, $class;
}
sub notifyUpdateSuccess
{
my $self = shift;
my ($module, $msg) = @_;
$self->sendIPCMessage(ksb::IPC::MODULE_SUCCESS, "$module,$msg");
}
# Waits for an update for a module with the given name.
# Returns a list containing whether the module was successfully updated,
# and any specific string message (e.g. for module update success you get
# number of files affected)
# Will throw an exception for an IPC failure or if the module should not be
# built.
sub waitForModule
{
my ($self, $module) = @_;
assert_isa($module, 'ksb::Module');
my $moduleName = $module->name();
my $updated = $self->{'updated'};
my $message;
# Wait for for the initial phase to complete, if it hasn't.
$self->waitForStreamStart();
# No update? Just mark as successful
if ($self->{'no_update'} || !$module->phases()->has('update')) {
$updated->{$moduleName} = 'success';
return ('success', 'Skipped');
}
while(! defined $updated->{$moduleName}) {
my $buffer;
info ("\tWaiting for source code update.");
my $ipcType = $self->receiveIPCMessage(\$buffer);
if (!$ipcType)
{
croak_runtime("IPC failure updating $moduleName: $!");
}
whisper ("\tReceived IPC status message for $buffer: $ipcType");
given ($ipcType) {
when (ksb::IPC::MODULE_SUCCESS) {
my ($ipcModuleName, $msg) = split(/,/, $buffer);
$message = $msg;
$updated->{$ipcModuleName} = 'success';
}
when (ksb::IPC::MODULE_SKIPPED) {
# The difference between success here and 'skipped' below
# is that success means we should build even though we
# didn't perform an update, while 'skipped' means the
# *build* should be skipped even though there was no
# failure.
$message = 'skipped';
$updated->{$buffer} = 'success';
}
when (ksb::IPC::MODULE_CONFLICT) {
$module->setPersistentOption('conflicts-present', 1);
$message = 'conflicts present';
$updated->{$buffer} = 'failed';
}
when (ksb::IPC::MODULE_FAILURE) {
$message = 'update failed';
$updated->{$buffer} = 'failed';
}
when (ksb::IPC::MODULE_UPTODATE) {
# Properly account for users manually doing --refresh-build or
# using .refresh-me.
$message = 'no files affected';
if ($module->buildSystem()->needsRefreshed())
{
$updated->{$buffer} = 'success';
note ("\tNo source update, but g[$module] meets other building criteria.");
}
else
{
$updated->{$buffer} = 'skipped';
}
}
default {
croak_internal("Unhandled IPC type: $ipcType");
}
}
}
# Out of while loop, should have a status now.
return ($updated->{$moduleName}, $message);
}
# Waits on the IPC connection until one of the ALL_* IPC codes is returned.
# If ksb::IPC::ALL_SKIPPED is returned then the 'no_update' entry will be set in
# $self to flag that you shouldn't wait.
# If ksb::IPC::ALL_FAILURE is returned then an exception will be thrown due to the
# fatal error.
# This method can be called multiple times, but only the first time will
# result in a wait.
sub waitForStreamStart
{
my $self = shift;
state $waited = 0;
return if $waited;
my $buffer = '';
my $ipcType = $self->receiveIPCMessage(\$buffer);
$waited = 1;
if (!$ipcType) {
croak_internal("IPC Failure waiting for stream start :( $!");
}
if ($ipcType == ksb::IPC::ALL_FAILURE)
{
croak_runtime("Unable to perform source update for any module:\n\t$buffer");
}
elsif ($ipcType == ksb::IPC::ALL_SKIPPED)
{
$self->{'no_update'} = 1;
}
elsif ($ipcType != ksb::IPC::ALL_UPDATING)
{
croak_runtime("IPC failure while expecting an update status: Incorrect type: $ipcType");
}
}
# Sends an IPC message along with some IPC type information.
#
# First parameter is the IPC type to send.
# Second parameter is the actual message.
# All remaining parameters are sent to the object's sendMessage()
# procedure.
sub sendIPCMessage
{
my ($self, $ipcType, $msg) = @_;
my $encodedMsg = pack("l! a*", $ipcType, $msg);
return $self->sendMessage($encodedMsg);
}
# Static class function to unpack a message.
#
# First parameter is the message.
# Second parameter is a reference to a scalar to store the result in.
#
# Returns the IPC message type.
sub unpackMsg
{
my ($msg, $outBuffer) = @_;
my $returnType;
($returnType, $$outBuffer) = unpack("l! a*", $msg);
return $returnType;
}
# Receives an IPC message and decodes it into the message and its
# associated type information.
#
# First parameter is a *reference* to a scalar to hold the message contents.
# All remaining parameters are passed to the underlying receiveMessage()
# procedure.
#
# Returns the IPC type, or undef on failure.
sub receiveIPCMessage
{
my $self = shift;
my $outBuffer = shift;
my $msg = $self->receiveMessage();
return ($msg ? unpackMsg($msg, $outBuffer) : undef);
}
# These must be reimplemented. They must be able to handle scalars without
# any extra frills.
#
# sendMessage should accept one parameter (the message to send) and return
# true on success, or false on failure. $! should hold the error information
# if false is returned.
sub sendMessage { croak_internal("Unimplemented."); }
# receiveMessage should return a message received from the other side, or
# undef for EOF or error. On error, $! should be set to hold the error
# information.
sub receiveMessage { croak_internal("Unimplemented."); }
# Should be reimplemented if default does not apply.
sub supportsConcurrency
{
return 0;
}
# Should be reimplemented if default does not apply.
sub close
{
}
1;