Logo Search packages:      
Sourcecode: adun.app version File versions  Download package

ULProcessManager.m

/*
   Project: UL

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   Created: 2005-05-23 14:10:58 +0200 by michael johnston

   This application is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This application is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

#include "ULProcessManager.h"

static id processManager;

@implementation ULProcessManager 

- (BOOL) _checkForServerOnHost: (NSString*) host
{
      NSPort* port;

      port = nil;
      if([host isEqual: [[NSHost currentHost] name]])
            port = [[NSMessagePortNameServer sharedInstance] portForName: @"AdunServer"];
            
      if(port == nil)
            port = [[NSSocketPortNameServer sharedInstance] portForName: @"AdunServer" onHost: host];

      if(port == nil)
            return NO;

      return YES;
}

- (void) _handleConnectionsDidDie: (NSNotification*) aNotification
{
      id host, process;
      NSEnumerator* processEnum;
      NSString* errorString, *suggestionString;
      NSError* error;
      NSMutableDictionary* errorInfo;
      NSMutableDictionary* userInfo;

      [[NSNotificationCenter defaultCenter] removeObserver: self
            name: NSConnectionDidDieNotification
            object: [aNotification object]];
      
      //check the host

      host = [[connections allKeysForObject: [aNotification object]] 
                  objectAtIndex: 0];

      /*
       * We receive this when the last simulation run by the server dies OR
       * when the server crashes. We must distinguish between these two possibilities.
       *    In the first case simply remove the invalid connection from the connections dict.
       * When the next simulation is started a new one will be established
       *    In the second case we have to remove the connection, set all the processes running on 
       * the host controlled by the server to "Disconnected", set an error and post a notification
       * of the servers death.
       */

      [connections removeObjectForKey: host];

      if(![self _checkForServerOnHost: host])
      {     
            NSWarnLog(@"Detected server death");

            //set all current processes on the host as disconnected

            processEnum = [spawnedStack objectEnumerator];
            while(process = [processEnum nextObject])
                  if([[process processHost] isEqual: host] && [[process processStatus] isEqual: @"Running"])
                        [process setProcessStatus: @"Disconnected"];

            errorString = [NSString stringWithFormat: 
                        @"Unexpected disconnection from AdunServer on host %@\nPossible server crash.", host];
            suggestionString = @"There is currently no way to reaquire a dynamic connection to disconnected simulations.\
\nHowever it is likely they are still running.\nExamine the AdunCore.logs for the disconnected process to determine the\
situation.\nDisconnected simulations will continue to collect results which can still be accessed through the results\
interface.\nPease send the file AdunServerCrash.log in the adun directory to the developers.";

            errorInfo = [NSMutableDictionary dictionary];
            [errorInfo setObject: errorString forKey: NSLocalizedDescriptionKey];
            [errorInfo setObject: suggestionString forKey: @"NSLocalizedRecoverySuggestionKey"];

            error = [NSError errorWithDomain: @"ULServerConnectionDomain" 
                        code: 1
                        userInfo: errorInfo];

            userInfo = [NSMutableDictionary dictionary];
            [userInfo setObject: error forKey: @"ULDisconnectionErrorKey"];
            [userInfo setObject: host forKey: @"ULDisconnectedHostKey"];

            [[NSNotificationCenter defaultCenter] postNotificationName: @"ULDisconnectedFromServerNotification"
                  object: self
                  userInfo: userInfo];
      }
}

//The exceptions here should be errors

- (id) _proxyForHost: (NSString*) hostname
{
      id connection;
      NSPort* port;

      if((connection = [connections objectForKey: hostname]) == nil)
      {
            if([hostname isEqual: [[NSHost currentHost] name]])
            {
                  //if we're trying to connect to the local host first try to connect to
                  //AdunServer through message ports then socket ports

                  connection =  [NSConnection connectionWithRegisteredName: @"AdunServer" 
                              host: nil 
                              usingNameServer: [NSMessagePortNameServer sharedInstance]]; 
            
                  if(connection == nil)
                        connection =  [NSConnection connectionWithRegisteredName: @"AdunServer" 
                                    host: hostname 
                                    usingNameServer: [NSSocketPortNameServer sharedInstance]]; 
            }     
            else
            {
                  //if we're not trying to connect to the local host their must be
                  //an AdunServer using NSSocketPorts on the remote machine

                  connection =  [NSConnection connectionWithRegisteredName: @"AdunServer" 
                              host: hostname 
                              usingNameServer: [NSSocketPortNameServer sharedInstance]]; 
            }
            
            if(connection == nil)
            {
                  [[NSException exceptionWithName: @"ULCouldNotConnectToServerException" 
                        reason: [NSString stringWithFormat: @"Couldn't connect to host %@.", hostname]
                        userInfo: [NSDictionary dictionaryWithObject: hostname forKey: @"host"]]
                        raise];
            }           

            [connections setObject: connection forKey: hostname];

            //register for notifications

            [[NSNotificationCenter defaultCenter] addObserver: self
                  selector: @selector(_handleConnectionsDidDie:)
                  name: NSConnectionDidDieNotification
                  object: connection];
      }

      //check is connection still valid 

      if(![connection isValid])
      {
            //try to reconnect
                  
            port = [connection sendPort];
      
            if([port isMemberOfClass: [NSMessagePort class]])
                  connection =  [NSConnection connectionWithRegisteredName: @"AdunServer" 
                              host: hostname 
                              usingNameServer: [NSMessagePortNameServer sharedInstance]]; 
            else
                  connection =  [NSConnection connectionWithRegisteredName: @"AdunServer" 
                              host: hostname 
                              usingNameServer: [NSSocketPortNameServer sharedInstance]]; 
      
            if(connection == nil)
                  [NSException raise: NSInternalInconsistencyException 
                        format: @"Connection for host %@ invalid and couldnt reconnect.", hostname];
            
            [connections setObject: connection forKey: hostname];
      }

      return [connection rootProxy];
}

- (void) dealloc
{
      [newStack release];
      [spawnedStack release];
      [finishedStack release];
      [connections release];
      [hosts release];
}

+ (id) appProcessManager
{
      return processManager;
}

- (id) init
{
      if(processManager != nil)
            return processManager;

      if(self = [super init])
      {
            newStack = [NSMutableArray arrayWithCapacity: 1];
            spawnedStack = [NSMutableArray arrayWithCapacity: 1];
            finishedStack = [NSMutableArray arrayWithCapacity: 1];
            [newStack retain];
            [spawnedStack retain];
            [finishedStack retain];
            connections = [NSMutableDictionary new];

            hosts = [[ULIOManager appIOManager] valueForKey:@"AdunHosts"];
            [hosts retain];

            NSDebugLLog(@"ULProcessManager", @"Available hosts %@", hosts);
            processManager = self;
      }

      return self;
}

- (void) newProcessWithSystem: (id) newSystem options: (id) newOptions host: (NSString*) host
{
      int i;
00231       id anObj;
      NSEnumerator* anEnum;
      id process;

      //add the process to the newStack
      
      NSDebugLLog(@"ULProcessManager", @"Creating process with system %@ and options %@", 
                  newSystem, 
                  [newOptions valueForKey:@"Options"]);

      process = [ULProcess processWithSystem: newSystem options: newOptions];
      [process setProcessStatus: @"Waiting"];
      [process setProcessHost: host];
      [newStack addObject: process]; 
                         
      [[NSNotificationCenter defaultCenter] postNotificationName: @"ULDidCreateNewProcessNotification"
            object: self];

      NSDebugLLog(@"ULProcessManager", @"Added process to newStack. Currently %d objects on newStack", 
             [newStack count]);
      NSDebugLLog(@"ULProcessManager", @"New object id %@", 
            [[newStack objectAtIndex: 0] valueForKey: @"Identification"]);
}

- (void) newProcessWithSystems: (id) newSystems options: (id) newOptions host: (NSString*) host;
{
      int i;
00258       id anObj;
      NSEnumerator* anEnum;
      id process;

      //add the process to the newStack

      NSDebugLLog(@"ULProcessManager", @"Creating process with systems %@ and options %@", 
                  newSystems, 
                  [newOptions valueForKey:@"Options"]);

      process = [ULProcess processWithSystems: newSystems options: newOptions];
      [process setProcessStatus: @"Waiting"];
      [process setProcessHost: host];
      [newStack addObject: process]; 
             
      [[NSNotificationCenter defaultCenter] postNotificationName: @"ULDidCreateNewProcessNotification"
            object: self];

      NSDebugLLog(@"ULProcessManager", @"Added process to newStack. Currently %d objects on newStack",  
                  [newStack count]);
      NSDebugLLog(@"ULProcessManager", @"New object id %@",
                   [[newStack objectAtIndex: 0] valueForKey: @"Identification"]);
      NSDebugLLog(@"ULProcessManager", @"Systems %@", 
                   [process valueForKey:@"Systems"]);
}

- (void) spawnNewProcess
{
      id proxy;
00287       id process;
      id host;
      id error;

      NSDebugLLog(@"ULProcessManager", @"Preparing process for launch");

      if([newStack count] == 0)
            [NSException raise: NSInternalInconsistencyException
                  format: @"There are no simulations waiting to be spawned"];

      process = [[newStack objectAtIndex: 0] retain];
      host = [process processHost]; 
      
      NSDebugLLog(@"ULProcessManager", @"Spawining process on host %@", host);
      
      proxy = [self _proxyForHost: host];

      GSPrintf(stdout, @"Starting Sim using proxy %@\n", proxy);
      NSDebugLLog(@"ULProcessManager", @"Process is %@ , %@", 
            [process valueForKey:@"systems"], [process valueForKeyPath: @"options.Options"]);
      [process processWillStart];   
      error = [proxy startSimulation: process];
      /**
      Clear up created directories if the above error isnt nill
      (Thought maybe this could be done by ULProcess┬┐)
      */
      [process setValue: [NSDate date] forKey: @"started"];
      
      if(error == nil)
      {
            [[NSNotificationCenter defaultCenter] addObserver: self
                  selector: @selector(processTermination:)
                  name: @"ULProcessDidFinishNotification"
                  object: process];
            [newStack removeObject: process];
            [spawnedStack addObject: process];
            [process setProcessStatus: @"Running"];
            [process release];      
            [[NSNotificationCenter defaultCenter] postNotificationName: @"ULDidLaunchProcessNotification"
                  object: self];
      }
      else
            [NSException raise: NSInternalInconsistencyException
                  format: [error localizedDescription]];
}

- (void) processTermination: (NSNotification*) aNotification
{
      id process, dataSet;
00336       ULSimulation *simulation;
      NSArray* dataSets;
      NSEnumerator* dataSetEnum;
            
      NSDebugLLog(@"ULProcessManager", @"Received a process termination message");

      process = [aNotification object];
      [[NSNotificationCenter defaultCenter] removeObserver: self
            name: nil
            object: process];
      [spawnedStack removeObject: process];
      [finishedStack addObject: process];

      //Add the simulation data to the database.
      simulation = [process simulationData];
      [[ULDatabaseInterface databaseInterface] 
            addObjectToFileSystemDatabase: simulation];

      /**
      Import any dataSets that the simulation created to the database.
      Add input references to the data sets and output references to the simulation.
      */
      
      if((dataSets = [process controllerResults]) != nil)
      {
            dataSetEnum = [dataSets objectEnumerator];
            while(dataSet = [dataSetEnum nextObject])
            {
                  //Add input references for the data sets.       
                  [dataSet addInputReferenceToObject: simulation];
                  NSDebugMLLog(@"ULAnalyser", @"Data set input references %@", 
                        [dataSet inputReferences]);

                  [[ULDatabaseInterface databaseInterface]
                        addObjectToFileSystemDatabase: dataSet];

                  //Add output reference to the simulation
                  [simulation addOutputReferenceToObject: dataSet];
            }           
            
            NSDebugMLLog(@"ULAnalyser", @"Simulation output references %@", 
                  [simulation outputReferences]);

            //update the database with the new output references for the simulation

            [[ULDatabaseInterface databaseInterface]
                  updateOutputReferencesForObject: simulation];
      }
      

      [[NSNotificationCenter defaultCenter] 
            postNotificationName: @"ULProcessDidFinishNotification"
            object: self
            userInfo: [aNotification userInfo]];
}

- (int) numberWaitingProcesses
{
      return [newStack count];
}

- (int) numberSpawnedProcesses
{
      return [spawnedStack count];
}

- (NSMutableArray*) WaitingProcesses
{
      return newStack;
}

- (NSMutableArray*) SpawnedProcesses
{
      return spawnedStack;
}

- (NSMutableArray*) FinishedProcesses
{
      return finishedStack;
}

- (NSMutableArray*) allProcesses
{
      id array;

      array = [newStack arrayByAddingObjectsFromArray: spawnedStack];
      return [[array arrayByAddingObjectsFromArray: finishedStack] mutableCopy];
}

- (NSMutableArray*) Hosts
{
      return hosts;
}

//Basic Commands
//Refactor these messages to use execute:error:process

- (BOOL) exportProcess: (ULProcess*) process 
            toFile: (NSString*) filename 
            overwrite: (BOOL) flag
            error: (NSError**) error 
{
      NSMutableData* data;
      NSKeyedArchiver* archiver;
      BOOL isDir;
      NSString* dir, *reason;
      NSMutableDictionary* userInfo;
      id temp, name, fileManager;

      fileManager = [NSFileManager defaultManager];
      userInfo = [NSMutableDictionary dictionary];
      
      //check file
      name =      [[filename lastPathComponent] stringByTrimmingCharactersInSet: 
                                    [NSCharacterSet whitespaceCharacterSet]];
      NSDebugLLog(@"Export", @"Name of file is %@", name);
      if([name isEqual: @""])
      {
            reason = @"Filename invalid - Must contain characters other than whitespace";
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 1
                        userInfo: userInfo];
            return NO;
      }

      temp = [[filename pathComponents] mutableCopy];
      [temp removeLastObject];
      dir = [NSString pathWithComponents: temp];

      NSDebugLLog(@"Export", @"Exporting to dir %@", dir);
      if(![fileManager fileExistsAtPath: dir isDirectory: &isDir])
      {
            reason = [NSString stringWithFormat: @"The specfied directory (%@) does not exist", dir];
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 1
                        userInfo: userInfo];
            return NO;
      }

      if(![fileManager isWritableFileAtPath: dir])
      {
            reason = [NSString stringWithFormat: @"Specified directory (%@) is not writable", dir];
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 1
                        userInfo: userInfo];
            return NO;
      }

      if(!isDir)
      {
            reason = [NSString stringWithFormat: @"%@ is not a directory", dir];
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 1
                        userInfo: userInfo];
            return NO;
      }


      if([fileManager fileExistsAtPath: filename] && flag == NO)
      {
            reason = @"File already exists - Really overwrite?";
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 10
                        userInfo: userInfo];
            return NO;
      }
      else if([fileManager fileExistsAtPath: filename] && flag == YES)
      {
            if(![fileManager isWritableFileAtPath:filename]) 
            {
                  reason = @"Cannot overwrite file - write protected";
                  [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
                  *error = [NSError errorWithDomain: @"ULErrorDomain"
                              code: 1
                              userInfo: userInfo];
                  return NO;
            }
      }

      data = [NSMutableData new];
      archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
      [archiver setOutputFormat: NSPropertyListXMLFormat_v1_0];
      [archiver encodeObject: process forKey: @"process"];
      [archiver finishEncoding];

      if(![data writeToFile: filename atomically: NO])
      {
            reason = @"Unable to export file - Reason unknown";
            [userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 1
                        userInfo: userInfo];
            return NO;
      }

      [archiver release];
      [data release];

      return YES;
}

- (void) haltProcess: (ULProcess*) process
{
      id proxy;

      if([[process processStatus] isEqual: @"Running"])
      {
            proxy = [self _proxyForHost: [process processHost]];
            [proxy haltProcess: process];
            [process setProcessStatus: @"Suspended"];
      }
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not running"];
      
      [[NSNotificationCenter defaultCenter] 
            postNotificationName: @"ULProcessStatusDidChangeNotification"
            object: self];
}

- (void) restartProcess: (ULProcess*) process
{
      id proxy;

      NSDebugMLLog(@"ULProcessManager", @"Restarting %@, status %@", 
            process, [process processStatus]);

      if([[process processStatus] isEqual: @"Suspended"])
      {
            proxy = [self _proxyForHost: [process processHost]];
            [proxy restartProcess: process];
            [process setProcessStatus: @"Running"];
      }
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not suspended"];

      [[NSNotificationCenter defaultCenter] 
            postNotificationName: @"ULProcessStatusDidChangeNotification"
            object: self];
}

- (void) terminateProcess: (ULProcess*) process
{
      id proxy;
      id status;

      status = [process processStatus];
      NSDebugMLLog(@"ULProcess", @"Terminating %@, status %@", 
            process, [process processStatus]);

      if([status isEqual: @"Running"] || 
            [status isEqual: @"Suspended"])
      {
            proxy = [self _proxyForHost: [process processHost]];
            [proxy terminateProcess: process];
      }
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process has not been started"];
}

- (void) startProcess: (ULProcess*) process
{
      if([[process processStatus] isEqual: @"Waiting"])
      {
            [newStack removeObject: process];
            [newStack insertObject: process atIndex: 0];
            [self spawnNewProcess];
      }
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not waiting"];

      [[NSNotificationCenter defaultCenter] 
            postNotificationName: @"ULProcessStatusDidChangeNotification"
            object: self];
}

- (void) removeProcess: (ULProcess*) process
{
      if([[process processStatus] isEqual: @"Finished"])
            [finishedStack removeObject: process];
      else if([[process processStatus] isEqual: @"Waiting"])
            [newStack removeObject: process];
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is running"];

      [[NSNotificationCenter defaultCenter] 
            postNotificationName: @"ULProcessStatusDidChangeNotification"
            object: self];
}

- (id) execute: (NSDictionary*) commandDict error: (NSError**) error process: (id) process
{
      id proxy;
      id result;
      id receivePort, connection;
      NSMutableDictionary* errorInfo;
      NSString* descriptionString, *suggestionString;
      
      if([[process processStatus] isEqual: @"Running"])
            proxy = [self _proxyForHost: [process processHost]];
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not running"];

      NS_DURING
      {
            [[proxy connectionForProxy] setReplyTimeout: 10.0];
            [[proxy connectionForProxy] setRequestTimeout: 10.0];
            result = [proxy execute: commandDict 
                        error: error
                        process: process];
      }
      NS_HANDLER
      {
            connection = [proxy connectionForProxy];
            
            //if the port is still valid - set an error
            if([connection isValid])
            {
                  errorInfo = [NSMutableDictionary dictionary];
                  
                  descriptionString = [NSString stringWithFormat: @"Attempt to send command %@ to %@ timed out.\n",
                                    [commandDict objectForKey: @"command"], 
                                    [process processHost]];
                  suggestionString = @"This could be due to the server being busy or the underlying operating\
system.\nTry again later.";

                  [errorInfo setObject: descriptionString 
                        forKey: NSLocalizedDescriptionKey];
                  [errorInfo setObject: suggestionString 
                        forKey: @"NSLocalizedRecoverySuggestionKey"];
                  *error = [NSError errorWithDomain: @"ULServerConnectionDomain"
                              code: 1
                              userInfo: errorInfo];
            }     
            
            /*
            If either port (i.e. send or recieve) was invalidated and was an NSMessagePort 
            we will receive an NSConnectionDidDieNotification and we will handle the results then. 
            However if we are using socket ports we wont recieve this notification 
            for an exception on the remote (the receive) port and 
            must check for it here and raise the exception ourselves
            */

            receivePort = [connection receivePort];
            if([receivePort isMemberOfClass: [NSSocketPort class]])
                  if(![receivePort isValid])
                        [[NSNotificationCenter defaultCenter] 
                              postNotificationName: NSConnectionDidDieNotification
                              object: [proxy connectionForProxy]];
      }
      NS_ENDHANDLER

      return result;
}

- (NSMutableDictionary*) optionsForCommand: (NSString*) name process: (id) process
{
      id proxy;
      
00704       if([[process processStatus] isEqual: @"Running"])
            proxy = [self _proxyForHost: [process processHost]];
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not running"];

      return [proxy optionsForCommand: name
                  process: process];
}

- (NSArray*) validCommandsForProcess: (id) process
{
      id proxy;
      
00718 
      if([[process processStatus] isEqual: @"Running"])
            proxy = [self _proxyForHost: [process processHost]];
      else
            [NSException raise: @"NSInvalidArgumentException"
                  format: @"Selected process is not running"];

      return [proxy validCommandsForProcess: process];
}

@end

Generated by  Doxygen 1.6.0   Back to index