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
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;
|
|
|