releasing/cbrtools/perl/CommandController.pm
changeset 602 3145852acc89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/releasing/cbrtools/perl/CommandController.pm	Fri Jun 25 18:37:20 2010 +0800
@@ -0,0 +1,294 @@
+# Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of the License "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+# 
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+# 
+# Contributors:
+# 
+# Description:
+# 
+#
+
+package CommandController;
+
+use strict;
+
+
+#
+# Constants.
+#
+
+use constant READER_SEMAPHORE_NAME => "CommandControllerReaderSemaphore_";
+use constant WRITER_SEMAPHORE_NAME => "CommandControllerWriterSemaphore_";
+use constant MAX_NUM_CONCURRENT_READERS => 100;
+use constant CMD_INDEPENDANT => 0; # Commands that can be run regardless of what else is running.
+use constant CMD_ENV_READER => 1;  # Commands that only read the environment.
+use constant CMD_ENV_WRITER => 2;  # Commands that modify the environment.
+
+my %commandInfo = (
+ 		   EnvMembership => CMD_INDEPENDANT,
+		   CleanRemote => CMD_INDEPENDANT,
+		   ExportEnv => CMD_INDEPENDANT,
+		   ExportRel => CMD_INDEPENDANT,
+		   CopyRel => CMD_INDEPENDANT,
+		   ImportEnv => CMD_INDEPENDANT,
+		   ImportRel => CMD_INDEPENDANT,		
+		   LatestVer => CMD_INDEPENDANT,
+		   PullEnv => CMD_INDEPENDANT,
+		   PushEnv => CMD_INDEPENDANT,
+		   PushRel => CMD_INDEPENDANT,
+		   PullRel => CMD_INDEPENDANT,
+		   DeltaEnv => CMD_INDEPENDANT,
+		   BinInfo => CMD_ENV_READER,
+		   SourceInfo => CMD_ENV_READER,
+		   DiffEnv => CMD_ENV_READER,
+		   DiffRel => CMD_ENV_READER,
+		   ModNotes => CMD_ENV_READER,
+		   ViewNotes => CMD_ENV_READER,
+		   BuildRel => CMD_ENV_READER,
+		   EnvSize => CMD_ENV_READER,
+		   MakeSnapShot => CMD_ENV_READER,
+		   CleanEnv => CMD_ENV_WRITER,
+		   EnvInfo => CMD_ENV_WRITER,
+		   GetEnv => CMD_ENV_WRITER,
+		   GetRel => CMD_ENV_WRITER,
+		   GetSource => CMD_ENV_WRITER,
+		   InstallSnapShot => CMD_ENV_WRITER,
+		   MakeEnv => CMD_ENV_WRITER,
+		   MakeRel => CMD_ENV_WRITER,
+		   RemoveRel => CMD_ENV_WRITER,
+		   RemoveSource => CMD_ENV_READER,
+		   PrepEnv => CMD_ENV_WRITER,
+		   PrepRel => CMD_ENV_WRITER,
+		   ValidateEnv => CMD_ENV_WRITER,
+		   ValidateRel => CMD_ENV_WRITER,
+		   EnvData => CMD_ENV_WRITER
+		  );
+
+
+#
+# Public.
+#
+
+sub New {
+  my $pkg = shift;
+  my $self = {};
+  bless $self, $pkg;
+  $self->{iniData} = shift;
+  $self->{command} = shift;
+  unless ($self->{iniData}->Win32ExtensionsDisabled()) {
+    $self->OpenSemaphores();
+    unless ($self->CanRun()) {
+      die "Error: Cannot run $self->{command} because another command is already running\n";
+    }
+  }
+  return $self;
+}
+
+
+#
+# Private.
+#
+
+sub CanRun {
+  my $self = shift;
+  $self->{canRun} = 0;
+  my $commandType = $self->CommandType();
+  if ($commandType == CMD_INDEPENDANT) {
+    $self->{canRun} = 1;
+  }
+  elsif ($commandType == CMD_ENV_READER) {
+    unless ($self->WriterRunning()) {
+      $self->{canRun} = 1;
+      $self->IncReadersRunning();
+    }
+  }
+  elsif ($commandType == CMD_ENV_WRITER) {
+    if (($self->NumReadersRunning() == 0) and not $self->WriterRunning()) {
+      $self->{canRun} = 1;
+      $self->SetWriterRunning();
+    }
+  }
+  return $self->{canRun};
+}
+
+sub DESTROY {
+  my $self = shift;
+  if ($self->{canRun}) {
+    my $commandType = $self->CommandType();
+    if ($commandType == CMD_INDEPENDANT) {
+      # Nothing to do.
+    }
+    elsif ($commandType == CMD_ENV_READER) {
+      $self->DecReadersRunning();
+    }
+    elsif ($commandType == CMD_ENV_WRITER) {
+      $self->ClearWriterRunning();
+    }
+  }
+}
+
+sub OpenSemaphores {
+  my $self = shift;
+  my $currentEnvironment = Utils::CurrentDriveLetter() . lc(Utils::EpocRoot());
+  $currentEnvironment =~ s/[:\\\/]+/_/g; # Can't have slashes in semaphore name
+  
+  require Win32::Semaphore;
+  # No longer 'use', as that fails with some versions of Perl
+  $self->{writerSemaphore} = Win32::Semaphore->new(0, 2, WRITER_SEMAPHORE_NAME . $currentEnvironment) or die; # 2 because when counting the semaphore, it need to be incremented and then decremented (release(0, $var) doesn't work).
+  $self->{readerSemaphore} = Win32::Semaphore->new(0, MAX_NUM_CONCURRENT_READERS, READER_SEMAPHORE_NAME . $currentEnvironment) or die;
+}
+
+sub CommandType {
+  my $self = shift;
+  die unless exists $commandInfo{$self->{command}};
+  return $commandInfo{$self->{command}};
+}
+
+sub WriterRunning {
+  my $self = shift;
+  my $writerRunning = SemaphoreCount($self->{writerSemaphore});
+  die if $writerRunning > 1;
+  return $writerRunning;
+}
+
+sub SetWriterRunning {
+  my $self = shift;
+  SemaphoreInc($self->{writerSemaphore});
+}
+
+sub ClearWriterRunning {
+  my $self = shift;
+  SemaphoreDec($self->{writerSemaphore});
+}
+
+sub NumReadersRunning {
+  my $self = shift;
+  return SemaphoreCount($self->{readerSemaphore});
+}
+
+sub IncReadersRunning {
+  my $self = shift;
+  SemaphoreInc($self->{readerSemaphore});
+}
+
+sub DecReadersRunning {
+  my $self = shift;
+  SemaphoreInc($self->{readerSemaphore});
+}
+
+sub SemaphoreCount {
+  my $semaphore = shift;
+  my $count;
+  $semaphore->release(1, $count) or die;
+  $semaphore->wait();
+  return $count;
+}
+
+sub SemaphoreInc {
+  my $semaphore = shift;
+  $semaphore->release(1) or die;
+}
+
+sub SemaphoreDec {
+  my $semaphore = shift;
+  $semaphore->wait();
+}
+
+1;
+
+=head1 NAME
+
+CommandController.pm - Provides a means of controlling which commands can run concurrently within a single environment.
+
+=head1 DESCRIPTION
+
+Certain commands can reliably be run while others are running, whereas others must be run in isolation. This class has responsibility for defining a set of rules regarding concurrent running of commands and ensuring that they are followed. Each command is classified into one of three types:
+
+=over 4
+
+=item 1 Independant
+
+Commands of this type can be run regardless of whatever else may also be running at the time because they neither read nor modify the environment. Commands of this type:
+
+ 		   EnvMembership
+		   CleanRemote
+		   ExportEnv
+		   ExportRel
+		   ImportEnv
+		   ImportRel		
+		   LatestVer
+		   PullEnv
+		   PullRel
+		   PushEnv
+		   PushRel
+		   DeltaEnv
+
+
+=item 2 Environment readers
+
+Commands of this type can be run provided there aren't any writers running. Commands of this type:
+
+		   BinInfo
+		   DiffEnv
+		   DiffRel
+                   MakeSnapShot
+		   ModNotes
+                   RemoveSource
+		   ViewNotes
+
+=item 3 Environment writers
+
+Commands of this type may modify the state of the environment, and so may only run providing there are no other writers or readers running. Commands of this type:
+
+		   CleanEnv
+		   EnvInfo
+		   GetEnv
+		   GetRel
+		   GetSource
+                   InstallSnapShot
+		   MakeEnv
+		   MakeRel
+		   RemoveRel
+		   PrepEnv
+		   PrepRel
+		   ValidateEnv
+		   ValidateRel
+
+=back
+
+To enforce these runs, multiple instances of C<CommandController> (running in different processes) need to know what else is running at any particular point in time. This information could have been stored in a file, but this has the significant problem that commands that are prematurely killed by the user (perhaps by hitting ctrl-c), they will not cleanup after themselves and so the environment could get stuck in an invalid state. To avoid this problem, a pair of Win32 semaphores are used to count the number of readers and writers currently active at any point in time. Note, only the counting properties of the semaphores are used, which is somewhat unusual (normally semaphores are used to control the execution of threads). The advantage of this scheme is that even if a command is prematurely killed by the user, its handles to the semaphoreswill be released. This may mean that for a period of time the semaphores may have invalid value, but once all commands that are currently running have completed, the semaphores will be destroyed (kernel side) and the environment is guaranteed of being in a 'ready to run' state.
+
+=head1 INTERFACE
+
+=head2 New
+
+Expects to be passed an C<IniData> reference and the name of the command that is about to be run (this is case sensitive). Creates and returns a new C<CommandController> instance if the command if free to run. Dies if not. The C<IniData> reference is used to determine if Win32 extensions have been disabled. If this is the case then the check to see if this command is free to run is not done (since doing so relies on Win32 functionality).
+
+=head1 KNOWN BUGS
+
+None.
+
+=head1 COPYRIGHT
+
+ Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
+ All rights reserved.
+ This component and the accompanying materials are made available
+ under the terms of the License "Eclipse Public License v1.0"
+ which accompanies this distribution, and is available
+ at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ 
+ Initial Contributors:
+ Nokia Corporation - initial contribution.
+ 
+ Contributors:
+ 
+ Description:
+ 
+
+=cut