Files
UnrealEngine/Engine/Source/Editor/SwarmInterface/DotNET/SwarmInterface.cs
2025-05-18 13:04:45 +08:00

3255 lines
111 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using AgentInterface;
using System.Reflection;
namespace NSwarm
{
internal static class DebugLog
{
[Conditional("DEBUG")]
internal static void Write (string msg)
{
Console.WriteLine(msg);
}
}
/*
* PerfTiming
*
* An instance of a timing object. It tracks a single timing; the total time and the number of calls
*/
public class PerfTiming
{
public String Name;
public Stopwatch StopWatchTimer;
public Int32 Count;
public bool Accumulate;
public Int64 Counter;
public PerfTiming(String InName, bool InAccumulate)
{
Name = InName;
StopWatchTimer = new Stopwatch();
Count = 0;
Accumulate = InAccumulate;
Counter = 0;
}
public void Start()
{
StopWatchTimer.Start();
}
public void Stop()
{
Count++;
StopWatchTimer.Stop();
}
public void IncrementCounter(Int64 Adder)
{
Counter += Adder;
}
}
/*
* PerfTimer
*
* Tracks a dictionary of PerfTimings
*/
public class PerfTimer
{
public Stack<PerfTiming> LastTimers;
ReaderWriterDictionary<String, PerfTiming> Timings;
public PerfTimer()
{
Timings = new ReaderWriterDictionary<String, PerfTiming>();
LastTimers = new Stack<PerfTiming>();
}
public void Start(String Name, bool Accum, Int64 Adder)
{
Monitor.Enter(LastTimers);
PerfTiming Timing = null;
if (!Timings.TryGetValue(Name, out Timing))
{
Timing = new PerfTiming(Name, Accum);
Timings.Add(Name, Timing);
}
LastTimers.Push(Timing);
Timing.IncrementCounter(Adder);
Timing.Start();
Monitor.Exit(LastTimers);
}
public void Stop()
{
Monitor.Enter(LastTimers);
PerfTiming Timing = LastTimers.Pop();
Timing.Stop();
Monitor.Exit(LastTimers);
}
public String DumpTimings()
{
String Output = "";
double TotalTime = 0.0;
foreach (PerfTiming Timing in Timings.Values)
{
if (Timing.Count > 0)
{
double Elapsed = Timing.StopWatchTimer.Elapsed.TotalSeconds;
double Average = (Elapsed * 1000000.0) / Timing.Count;
Output += Timing.Name.PadLeft(30) + " : " + Elapsed.ToString("F3") + "s in " + Timing.Count + " calls (" + Average.ToString("F0") + "us per call)";
if (Timing.Counter > 0)
{
Output += " (" + (Timing.Counter / 1024) + "k)";
}
Output += "\n";
if (Timing.Accumulate)
{
TotalTime += Elapsed;
}
}
}
Output += "\nTotal time inside Swarm: " + TotalTime.ToString("F3") + "s\n";
return Output;
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Connection configuration parameters, filled in by OpenConnection
*/
public class AgentConfiguration
{
public AgentConfiguration()
{
AgentProcessID = -1;
AgentCachePath = null;
AgentJobGuid = null;
IsPureLocalConnection = false;
}
// Process ID of the owning process for the agent, which can be used to
// monitor it for crashes or hangs
public Int32 AgentProcessID;
// The full path of the cache directory this agent is using
public String AgentCachePath;
// The GUID of the job the agent has associated this connection with, if any
public AgentGuid AgentJobGuid;
// An indication of whether we're considered a "pure" local connection by the
// Agent, potentially relieving us from Agent coordination when using Channels
public bool IsPureLocalConnection;
}
///////////////////////////////////////////////////////////////////////////////
/**
* A wrapper to abstract away the complexity of asynchronous calls for the
* Agent API and the monitoring of the connection
*/
public class IAgentInterfaceWrapper
{
public IAgentInterfaceWrapper()
{
// TODO: Make URL configurable
Connection = (IAgentInterface)Activator.GetObject(typeof(IAgentInterface), "tcp://127.0.0.1:8008/SwarmAgent");
ConnectionDroppedEvent = new ManualResetEvent(false);
}
public void SignalConnectionDropped()
{
ConnectionDroppedEvent.Set();
}
///////////////////////////////////////////////////////////////////////////
// A duplication of the IAgentInterface API which this class wraps
public Int32 OpenConnection(Process AgentProcess, bool AgentProcessOwner, Int32 LocalProcessID, ELogFlags LoggingFlags, out AgentConfiguration NewConfiguration)
{
OpenConnectionDelegate DOpenConnection = Connection.OpenConnection;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["ProcessID"] = LocalProcessID;
InParameters["ProcessIsOwner"] = (AgentProcessOwner == true ? true : false);
InParameters["LoggingFlags"] = LoggingFlags;
Hashtable OutParameters = null;
IAsyncResult Result = DOpenConnection.BeginInvoke(InParameters, ref OutParameters, null, null);
// This will wait with an occasional wake up to check to see if the
// agent process is still alive and kicking (avoids infinite wait,
// allows for very long start up times while debugging)
Int32 StartupSleep = 1000;
while ((Result.AsyncWaitHandle.WaitOne(StartupSleep) == false) &&
(AgentProcess.HasExited == false) &&
(AgentProcess.Responding == true))
{
// While the application is alive and responding, wait
DebugLog.Write("[OpenConnection] Waiting for agent to respond ...");
}
if (Result.IsCompleted)
{
// If the invocation didn't fail, end to get the result
Int32 ReturnValue = DOpenConnection.EndInvoke(ref OutParameters, Result);
if (OutParameters != null)
{
if ((ESwarmVersionValue)OutParameters["Version"] == ESwarmVersionValue.VER_1_0)
{
NewConfiguration = new AgentConfiguration();
NewConfiguration.AgentProcessID = (Int32 )OutParameters["AgentProcessID"];
NewConfiguration.AgentCachePath = (String )OutParameters["AgentCachePath"];
NewConfiguration.AgentJobGuid = (AgentGuid )OutParameters["AgentJobGuid"];
if (OutParameters.ContainsKey("IsPureLocalConnection"))
{
NewConfiguration.IsPureLocalConnection = (bool)OutParameters["IsPureLocalConnection"];
}
// Complete and successful
return ReturnValue;
}
}
}
// Otherwise, error
NewConfiguration = null;
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 CloseConnection(Int32 ConnectionHandle)
{
CloseConnectionDelegate DCloseConnection = Connection.CloseConnection;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable InParameters = null;
Hashtable OutParameters = null;
IAsyncResult Result = DCloseConnection.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DCloseConnection.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 SendMessage(Int32 ConnectionHandle, AgentMessage NewMessage)
{
SendMessageDelegate DSendMessage = Connection.SendMessage;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["Message"] = NewMessage;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DSendMessage.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DSendMessage.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 GetMessage(Int32 ConnectionHandle, out AgentMessage NextMessage, Int32 Timeout)
{
GetMessageDelegate DGetMessage = Connection.GetMessage;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["Timeout"] = (Int32)Timeout;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DGetMessage.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation didn't fail, end to get the result
Int32 ReturnValue = DGetMessage.EndInvoke(ref OutParameters, Result);
if (OutParameters != null)
{
if((ESwarmVersionValue)OutParameters["Version"] == ESwarmVersionValue.VER_1_0)
{
NextMessage = (AgentMessage )OutParameters["Message"];
// Complete and successful
return ReturnValue;
}
}
}
// Otherwise, error
NextMessage = null;
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 AddChannel(Int32 ConnectionHandle, String FullPath, String ChannelName)
{
AddChannelDelegate DAddChannel = Connection.AddChannel;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["FullPath"] = FullPath;
InParameters["ChannelName"] = ChannelName;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DAddChannel.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DAddChannel.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 TestChannel(Int32 ConnectionHandle, String ChannelName)
{
TestChannelDelegate DTestChannel = Connection.TestChannel;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["ChannelName"] = ChannelName;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DTestChannel.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DTestChannel.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 OpenChannel(Int32 ConnectionHandle, String ChannelName, EChannelFlags ChannelFlags)
{
OpenChannelDelegate DOpenChannel = Connection.OpenChannel;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["ChannelName"] = ChannelName;
InParameters["ChannelFlags"] = ChannelFlags;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DOpenChannel.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DOpenChannel.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 CloseChannel(Int32 ConnectionHandle, Int32 ChannelHandle)
{
CloseChannelDelegate DCloseChannel = Connection.CloseChannel;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["ChannelHandle"] = ChannelHandle;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DCloseChannel.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DCloseChannel.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 OpenJob(Int32 ConnectionHandle, AgentGuid JobGuid )
{
OpenJobDelegate DOpenJob = Connection.OpenJob;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["JobGuid"] = JobGuid;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DOpenJob.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DOpenJob.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 BeginJobSpecification(Int32 ConnectionHandle, AgentJobSpecification Specification32, Hashtable Description32, AgentJobSpecification Specification64, Hashtable Description64)
{
BeginJobSpecificationDelegate DBeginJobSpecification = Connection.BeginJobSpecification;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["Specification32"] = Specification32;
InParameters["Specification64"] = Specification64;
InParameters["Description32"] = Description32;
InParameters["Description64"] = Description64;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DBeginJobSpecification.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DBeginJobSpecification.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 AddTask(Int32 ConnectionHandle, List<AgentTaskSpecification> Specifications)
{
AddTaskDelegate DAddTask = Connection.AddTask;
// Set up the versioned hashtable input parameters
Hashtable InParameters = new Hashtable();
InParameters["Version"] = ESwarmVersionValue.VER_1_0;
InParameters["Specifications"] = Specifications;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable OutParameters = null;
IAsyncResult Result = DAddTask.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DAddTask.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 EndJobSpecification(Int32 ConnectionHandle)
{
EndJobSpecificationDelegate DEndJobSpecification = Connection.EndJobSpecification;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable InParameters = null;
Hashtable OutParameters = null;
IAsyncResult Result = DEndJobSpecification.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DEndJobSpecification.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 CloseJob(Int32 ConnectionHandle)
{
CloseJobDelegate DCloseJob = Connection.CloseJob;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
Hashtable InParameters = null;
Hashtable OutParameters = null;
IAsyncResult Result = DCloseJob.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DCloseJob.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 Method(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters)
{
MethodDelegate DMethod = Connection.Method;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
IAsyncResult Result = DMethod.BeginInvoke(ConnectionHandle, InParameters, ref OutParameters, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DMethod.EndInvoke(ref OutParameters, Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
public Int32 Log(EVerbosityLevel Verbosity, ELogColour TextColour, String Line)
{
LogDelegate DLog = Connection.Log;
// Invoke the method, then wait for it to finish or to be notified that the connection dropped
IAsyncResult Result = DLog.BeginInvoke(Verbosity, TextColour, Line, null, null);
WaitHandle.WaitAny(new WaitHandle[]{ Result.AsyncWaitHandle, ConnectionDroppedEvent });
// If the method completed normally, return the result
if (Result.IsCompleted)
{
// If the invocation completed, success
return DLog.EndInvoke(Result);
}
// Otherwise, error
return Constants.ERROR_CONNECTION_DISCONNECTED;
}
///////////////////////////////////////////////////////////////////////////
// The wrapped connection
IAgentInterface Connection;
ManualResetEvent ConnectionDroppedEvent;
// Delegate type declarations
delegate Int32 OpenConnectionDelegate(Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 CloseConnectionDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 SendMessageDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 GetMessageDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 AddChannelDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 TestChannelDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 OpenChannelDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 CloseChannelDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 OpenJobDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 BeginJobSpecificationDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 AddTaskDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 EndJobSpecificationDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 CloseJobDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 MethodDelegate(Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters);
delegate Int32 LogDelegate(EVerbosityLevel Verbosity, ELogColour TextColour, String Line);
}
///////////////////////////////////////////////////////////////////////////////
public delegate void FConnectionCallback(IntPtr CallbackMessage, IntPtr CallbackData);
/**
* Helper struct for getting the message processing thread started
*/
public struct MessageThreadData
{
public FSwarmInterface Owner;
public IAgentInterfaceWrapper Connection;
public Int32 ConnectionHandle;
public FConnectionCallback ConnectionCallback;
public IntPtr ConnectionCallbackData;
public AgentConfiguration ConnectionConfiguration;
}
/**
* The C#implementation of FSwarmInterface
*/
public class FSwarmInterface
{
FSwarmInterface()
{
AgentProcess = null;
AgentProcessOwner = false;
Connection = null;
ConnectionHandle = Constants.INVALID;
ConnectionMessageThread = null;
ConnectionMonitorThread = null;
ConnectionConfiguration = null;
ConnectionCallback = null;
BaseChannelHandle = 0;
PendingTasks = null;
NetworkChannel = null;
PerfTimerInstance = null;
OpenChannels = new ReaderWriterDictionary<Int32, ChannelInfo>();
FreeChannelWriteBuffers = new Stack<byte[]>();
CleanupClosedConnectionLock = new Object();
// TODO: Delete old files
}
delegate int SwarmOpenConnectionProc(FConnectionCallback CallbackFunc, IntPtr CallbackData, ELogFlags LoggingFlags, IntPtr OptionsFolder);
delegate int SwarmCloseConnectionProc();
delegate int SwarmSendMessageProc(IntPtr Message);
delegate int SwarmAddChannelProc(IntPtr FullPath, IntPtr ChannelName);
delegate int SwarmTestChannelProc(IntPtr ChannelName);
delegate int SwarmOpenChannelProc(IntPtr ChannelName, EChannelFlags ChannelFlags);
delegate int SwarmCloseChannelProc(int Channel);
delegate int SwarmWriteChannelProc(int Channel, IntPtr Data, int DataSize);
delegate int SwarmReadChannelProc(int Channel, IntPtr Data, int DataSize);
delegate int SwarmOpenJobProc(IntPtr JobGuid);
delegate int SwarmBeginJobSpecificationProc(IntPtr Specification32, IntPtr Specification64);
delegate int SwarmAddTaskProc(IntPtr Specification);
delegate int SwarmEndJobSpecificationProc();
delegate int SwarmCloseJobProc();
delegate int SwarmLogProc(EVerbosityLevel Verbosity, ELogColour TextColour, IntPtr Message);
delegate int SwarmInterfaceLogDelegate(EVerbosityLevel Verbosity, IntPtr Message);
static SwarmInterfaceLogDelegate SwarmInterfaceLogCppProc;
private delegate void RegisterSwarmOpenConnectionProc(SwarmOpenConnectionProc Proc);
private delegate void RegisterSwarmCloseConnectionProc(SwarmCloseConnectionProc Proc);
private delegate void RegisterSwarmSendMessageProc(SwarmSendMessageProc Proc);
private delegate void RegisterSwarmAddChannelProc(SwarmAddChannelProc Proc);
private delegate void RegisterSwarmTestChannelProc(SwarmTestChannelProc Proc);
private delegate void RegisterSwarmOpenChannelProc(SwarmOpenChannelProc Proc);
private delegate void RegisterSwarmCloseChannelProc(SwarmCloseChannelProc Proc);
private delegate void RegisterSwarmWriteChannelProc(SwarmWriteChannelProc Proc);
private delegate void RegisterSwarmReadChannelProc(SwarmReadChannelProc Proc);
private delegate void RegisterSwarmOpenJobProc(SwarmOpenJobProc Proc);
private delegate void RegisterSwarmBeginJobSpecificationProc(SwarmBeginJobSpecificationProc Proc);
private delegate void RegisterSwarmAddTaskProc(SwarmAddTaskProc Proc);
private delegate void RegisterSwarmEndJobSpecificationProc(SwarmEndJobSpecificationProc Proc);
private delegate void RegisterSwarmCloseJobProc(SwarmCloseJobProc Proc);
private delegate void RegisterSwarmLogProc(SwarmLogProc Proc);
static int SwarmOpenConnection(FConnectionCallback CallbackFunc, IntPtr CallbackData, ELogFlags LoggingFlags, IntPtr OptionsFolder)
{
try
{
return GInstance.OpenConnection(CallbackFunc, CallbackData, (ELogFlags)LoggingFlags, FStringMarshaler.MarshalNativeToManaged(OptionsFolder));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmCloseConnection()
{
try
{
return GInstance.CloseConnection();
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmSendMessage(IntPtr Message)
{
try
{
return GInstance.SendMessage(Message);
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmAddChannel(IntPtr FullPath, IntPtr ChannelName)
{
try
{
return GInstance.AddChannel(FStringMarshaler.MarshalNativeToManaged(FullPath), FStringMarshaler.MarshalNativeToManaged(ChannelName));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmTestChannel(IntPtr ChannelName)
{
try
{
return GInstance.TestChannel(FStringMarshaler.MarshalNativeToManaged(ChannelName));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmOpenChannel(IntPtr ChannelName, EChannelFlags ChannelFlags)
{
try
{
return GInstance.OpenChannel(FStringMarshaler.MarshalNativeToManaged(ChannelName), ChannelFlags);
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmCloseChannel(int Channel)
{
try
{
return GInstance.CloseChannel(Channel);
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmWriteChannel(int Channel, IntPtr Data, int DataSize)
{
try
{
return GInstance.WriteChannel(Channel, Data, DataSize);
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmReadChannel(int Channel, IntPtr Data, int DataSize)
{
try
{
return GInstance.ReadChannel(Channel, Data, DataSize);
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmOpenJob(IntPtr JobGuid)
{
try
{
return GInstance.OpenJob((FGuid)Marshal.PtrToStructure(JobGuid, typeof(FGuid)));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static FJobSpecification MarshalJobSpecification(IntPtr SpecificationPtr)
{
FJobSpecificationMarshalHelper Helper = (FJobSpecificationMarshalHelper)Marshal.PtrToStructure(SpecificationPtr, typeof(FJobSpecificationMarshalHelper));
FJobSpecification Specification = new FJobSpecification();
Specification.ExecutableName = FStringMarshaler.MarshalNativeToManaged(Helper.ExecutableName);
Specification.Parameters = FStringMarshaler.MarshalNativeToManaged(Helper.Parameters);
Specification.Flags = Helper.Flags;
Specification.RequiredDependencyCount = Helper.RequiredDependencyCount;
Specification.RequiredDependencies = new String[Specification.RequiredDependencyCount];
for (UInt32 Index = 0; Index < Specification.RequiredDependencyCount; Index++)
{
Specification.RequiredDependencies[Index] = FStringMarshaler.MarshalNativeToManaged(Marshal.ReadIntPtr(Helper.RequiredDependencies, (Int32)Index * 8));
}
Specification.OptionalDependencyCount = Helper.OptionalDependencyCount;
Specification.OptionalDependencies = new String[Specification.OptionalDependencyCount];
for (UInt32 Index = 0; Index < Specification.OptionalDependencyCount; Index++)
{
Specification.OptionalDependencies[Index] = FStringMarshaler.MarshalNativeToManaged(Marshal.ReadIntPtr(Helper.OptionalDependencies, (Int32)Index * 8));
}
Specification.DescriptionCount = Helper.DescriptionCount;
Specification.DescriptionKeys = new String[Specification.DescriptionCount];
Specification.DescriptionValues = new String[Specification.DescriptionCount];
for (UInt32 Index = 0; Index < Specification.DescriptionCount; Index++)
{
Specification.DescriptionKeys[Index] = FStringMarshaler.MarshalNativeToManaged(Marshal.ReadIntPtr(Helper.DescriptionKeys, (Int32)Index * 8));
Specification.DescriptionValues[Index] = FStringMarshaler.MarshalNativeToManaged(Marshal.ReadIntPtr(Helper.DescriptionValues, (Int32)Index * 8));
}
return Specification;
}
static int SwarmBeginJobSpecification(IntPtr Specification32, IntPtr Specification64)
{
try
{
return GInstance.BeginJobSpecification(MarshalJobSpecification(Specification32), MarshalJobSpecification(Specification64));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static FTaskSpecification MarshalTaskSpecification(IntPtr SpecificationPtr)
{
FTaskSpecificationMarshalHelper Helper = (FTaskSpecificationMarshalHelper)Marshal.PtrToStructure(SpecificationPtr, typeof(FTaskSpecificationMarshalHelper));
FTaskSpecification Specification = new FTaskSpecification(Helper.TaskGuid, FStringMarshaler.MarshalNativeToManaged(Helper.Parameters), Helper.Flags);
Specification.Cost = Helper.Cost;
Specification.DependencyCount = Helper.DependencyCount;
Specification.Dependencies = new String[Specification.DependencyCount];
for (UInt32 Index = 0; Index < Specification.DependencyCount; Index++)
{
Specification.Dependencies[Index] = FStringMarshaler.MarshalNativeToManaged(Marshal.ReadIntPtr(Helper.Dependencies, (Int32)Index * 8));
}
return Specification;
}
static int SwarmAddTask(IntPtr Specification)
{
try
{
return GInstance.AddTask(MarshalTaskSpecification(Specification));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmEndJobSpecification()
{
try
{
return GInstance.EndJobSpecification();
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmCloseJob()
{
try
{
return GInstance.CloseJob();
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static int SwarmLog(EVerbosityLevel Verbosity, ELogColour TextColour, IntPtr Message)
{
try
{
return GInstance.Log((EVerbosityLevel)Verbosity, (ELogColour)TextColour, FStringMarshaler.MarshalNativeToManaged(Message));
}
catch(Exception Ex)
{
DebugLog.Write(Ex.Message + "\n" + Ex.ToString());
return 0;
}
}
static FSwarmInterface GInstance = new FSwarmInterface();
static SwarmOpenConnectionProc OpenConnectionProc = new SwarmOpenConnectionProc(SwarmOpenConnection);
static SwarmCloseConnectionProc CloseConnectionProc = new SwarmCloseConnectionProc(SwarmCloseConnection);
static SwarmSendMessageProc SendMessageProc = new SwarmSendMessageProc(SwarmSendMessage);
static SwarmAddChannelProc AddChannelProc = new SwarmAddChannelProc(SwarmAddChannel);
static SwarmTestChannelProc TestChannelProc = new SwarmTestChannelProc(SwarmTestChannel);
static SwarmOpenChannelProc OpenChannelProc = new SwarmOpenChannelProc(SwarmOpenChannel);
static SwarmCloseChannelProc CloseChannelProc = new SwarmCloseChannelProc(SwarmCloseChannel);
static SwarmWriteChannelProc WriteChannelProc = new SwarmWriteChannelProc(SwarmWriteChannel);
static SwarmReadChannelProc ReadChannelProc = new SwarmReadChannelProc(SwarmReadChannel);
static SwarmOpenJobProc OpenJobProc = new SwarmOpenJobProc(SwarmOpenJob);
static SwarmBeginJobSpecificationProc BeginJobSpecificationProc = new SwarmBeginJobSpecificationProc(SwarmBeginJobSpecification);
static SwarmAddTaskProc AddTaskProc = new SwarmAddTaskProc(SwarmAddTask);
static SwarmEndJobSpecificationProc EndJobSpecificationProc = new SwarmEndJobSpecificationProc(SwarmEndJobSpecification);
static SwarmCloseJobProc CloseJobProc = new SwarmCloseJobProc(SwarmCloseJob);
static SwarmLogProc LogProc = new SwarmLogProc(SwarmLog);
static bool KillMonitorThread = false;
#if !__MonoCS__
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string name);
#else
[DllImport("dl", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr dlopen(string name, int flag);
[DllImport("dl", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr dlsym(int handle, string name);
#endif
/**
*/
public static int InitCppBridgeCallbacks(String SwarmInterfaceDllName)
{
#if !__MonoCS__
IntPtr DllHandle = LoadLibrary(SwarmInterfaceDllName);
if (DllHandle == IntPtr.Zero)
{
return Constants.ERROR_FILE_FOUND_NOT;
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmOpenConnectionProc");
var Proc = (RegisterSwarmOpenConnectionProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenConnectionProc));
Proc(OpenConnectionProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmCloseConnectionProc");
var Proc = (RegisterSwarmCloseConnectionProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseConnectionProc));
Proc(CloseConnectionProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmSendMessageProc");
var Proc = (RegisterSwarmSendMessageProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmSendMessageProc));
Proc(SendMessageProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmAddChannelProc");
var Proc = (RegisterSwarmAddChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmAddChannelProc));
Proc(AddChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmTestChannelProc");
var Proc = (RegisterSwarmTestChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmTestChannelProc));
Proc(TestChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmOpenChannelProc");
var Proc = (RegisterSwarmOpenChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenChannelProc));
Proc(OpenChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmCloseChannelProc");
var Proc = (RegisterSwarmCloseChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseChannelProc));
Proc(CloseChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmWriteChannelProc");
var Proc = (RegisterSwarmWriteChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmWriteChannelProc));
Proc(WriteChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmReadChannelProc");
var Proc = (RegisterSwarmReadChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmReadChannelProc));
Proc(ReadChannelProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmOpenJobProc");
var Proc = (RegisterSwarmOpenJobProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenJobProc));
Proc(OpenJobProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmBeginJobSpecificationProc");
var Proc = (RegisterSwarmBeginJobSpecificationProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmBeginJobSpecificationProc));
Proc(BeginJobSpecificationProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmAddTaskProc");
var Proc = (RegisterSwarmAddTaskProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmAddTaskProc));
Proc(AddTaskProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmEndJobSpecificationProc");
var Proc = (RegisterSwarmEndJobSpecificationProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmEndJobSpecificationProc));
Proc(EndJobSpecificationProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmCloseJobProc");
var Proc = (RegisterSwarmCloseJobProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseJobProc));
Proc(CloseJobProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "RegisterSwarmLogProc");
var Proc = (RegisterSwarmLogProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmLogProc));
Proc(LogProc);
}
{
IntPtr ProcAddress = GetProcAddress(DllHandle, "SwarmInterfaceLog");
SwarmInterfaceLogCppProc = (SwarmInterfaceLogDelegate)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(SwarmInterfaceLogDelegate));
}
#else
IntPtr DllHandle = dlopen(SwarmInterfaceDllName, 9 /* RTLD_LAZY | RTLD_GLOBAL */);
if (DllHandle == IntPtr.Zero)
{
return Constants.ERROR_FILE_FOUND_NOT;
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmOpenConnectionProc");
var Proc = (RegisterSwarmOpenConnectionProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenConnectionProc));
Proc(SwarmOpenConnection);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmCloseConnectionProc");
var Proc = (RegisterSwarmCloseConnectionProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseConnectionProc));
Proc(SwarmCloseConnection);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmSendMessageProc");
var Proc = (RegisterSwarmSendMessageProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmSendMessageProc));
Proc(SwarmSendMessage);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmAddChannelProc");
var Proc = (RegisterSwarmAddChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmAddChannelProc));
Proc(SwarmAddChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmTestChannelProc");
var Proc = (RegisterSwarmTestChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmTestChannelProc));
Proc(SwarmTestChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmOpenChannelProc");
var Proc = (RegisterSwarmOpenChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenChannelProc));
Proc(SwarmOpenChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmCloseChannelProc");
var Proc = (RegisterSwarmCloseChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseChannelProc));
Proc(SwarmCloseChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmWriteChannelProc");
var Proc = (RegisterSwarmWriteChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmWriteChannelProc));
Proc(SwarmWriteChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmReadChannelProc");
var Proc = (RegisterSwarmReadChannelProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmReadChannelProc));
Proc(SwarmReadChannel);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmOpenJobProc");
var Proc = (RegisterSwarmOpenJobProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmOpenJobProc));
Proc(SwarmOpenJob);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmBeginJobSpecificationProc");
var Proc = (RegisterSwarmBeginJobSpecificationProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmBeginJobSpecificationProc));
Proc(SwarmBeginJobSpecification);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmAddTaskProc");
var Proc = (RegisterSwarmAddTaskProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmAddTaskProc));
Proc(SwarmAddTask);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmEndJobSpecificationProc");
var Proc = (RegisterSwarmEndJobSpecificationProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmEndJobSpecificationProc));
Proc(SwarmEndJobSpecification);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmCloseJobProc");
var Proc = (RegisterSwarmCloseJobProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmCloseJobProc));
Proc(SwarmCloseJob);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "RegisterSwarmLogProc");
var Proc = (RegisterSwarmLogProc)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(RegisterSwarmLogProc));
Proc(SwarmLog);
}
{
IntPtr ProcAddress = dlsym(-2 /* RTLD_DEFAULT */, "SwarmInterfaceLog");
SwarmInterfaceLogCppProc = (SwarmInterfaceLogDelegate)Marshal.GetDelegateForFunctionPointer(ProcAddress, typeof(SwarmInterfaceLogDelegate));
}
#endif
return Constants.SUCCESS;
}
/**
* Opens a new connection to the Swarm
*
* @param CallbackFunc The callback function Swarm will use to communicate back to the Instigator
*
* @return An INT containing the error code (if < 0) or the handle (>= 0) which is useful for debugging only
*/
public virtual Int32 OpenConnection(FConnectionCallback CallbackFunc, IntPtr CallbackData, ELogFlags LoggingFlags, string OptionsFolder)
{
// Checked here so we can time OpenConnection
if ((LoggingFlags & ELogFlags.LOG_TIMINGS) == ELogFlags.LOG_TIMINGS)
{
PerfTimerInstance = new PerfTimer();
}
StartTiming("OpenConnection-Managed", true);
// Establish a connection to the local Agent server object
ConnectionHandle = Constants.INVALID;
Int32 ReturnValue = Constants.INVALID;
try
{
EditorLog(EVerbosityLevel.Informative, "[OpenConnection] Registering TCP channel ...");
// Start up network services, by opening a network communication channel
NetworkChannel = new TcpClientChannel();
ChannelServices.RegisterChannel(NetworkChannel, false);
// See if an agent is already running, and if not, launch one
EnsureAgentIsRunning(OptionsFolder);
if (AgentProcess != null)
{
EditorLog(EVerbosityLevel.Informative, "[OpenConnection] Connecting to agent ...");
ReturnValue = TryOpenConnection(CallbackFunc, CallbackData, LoggingFlags);
if (ReturnValue >= 0)
{
AgentCacheFolder = ConnectionConfiguration.AgentCachePath;
if (AgentCacheFolder.Length == 0)
{
EditorLog(EVerbosityLevel.Critical, "[OpenConnection] Agent cache folder with 0 length.");
CloseConnection();
ReturnValue = Constants.ERROR_FILE_FOUND_NOT;
}
}
}
else
{
EditorLog(EVerbosityLevel.Critical, "[OpenConnection] Failed to find Swarm Agent");
ReturnValue = Constants.ERROR_FILE_FOUND_NOT;
}
}
catch (Exception Ex)
{
EditorLog(EVerbosityLevel.Critical, "[OpenConnection] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_EXCEPTION;
}
// Finally, if there have been no errors, assign the connection handle
if (ReturnValue >= 0)
{
ConnectionHandle = ReturnValue;
}
else
{
// If we've failed for any reason, call the clean up routine
CleanupClosedConnection();
}
StopTiming();
return ReturnValue;
}
/**
* Closes an existing connection to the Swarm
*
* @return Int32 error code (< 0 is error)
*/
public virtual Int32 CloseConnection()
{
// Dump any collected timing info
DumpTimings();
// Close the connection
StartTiming("CloseConnection-Managed", true);
Int32 ConnectionState = Constants.INVALID;
if (Connection != null)
{
try
{
StartTiming("CloseConnection-Remote", false);
Connection.CloseConnection(ConnectionHandle);
StopTiming();
ConnectionState = Constants.SUCCESS;
}
catch (Exception Ex)
{
ConnectionState = Constants.ERROR_EXCEPTION;
DebugLog.Write("[CloseConnection] Error: " + Ex.Message);
}
// Clean up the state of the object with the connection now closed
CleanupClosedConnection();
// With the connecton completely closed, clean up our threads
if (ConnectionMessageThread.Join(1000) == false)
{
// After calling CloseConnection, this thread is fair game to kill at any time
Debug.WriteLineIf( Debugger.IsAttached, "[CloseConnection] Error: Message queue thread failed to quit before timeout, killing.");
ConnectionMessageThread.Abort();
}
ConnectionMessageThread = null;
KillMonitorThread = true;
if (ConnectionMonitorThread.Join(1000) == false)
{
// We expect to abort this thread, no message necessary
ConnectionMonitorThread.Abort();
}
ConnectionMonitorThread = null;
}
else
{
ConnectionState = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return( ConnectionState );
}
/**
* Sends a message to an Agent (return messages are sent via the FConnectionCallback)
*
* @param Message The message being sent
*
* @return Int32 error code (< 0 is error)
*/
public virtual Int32 SendMessage(IntPtr NativeMessagePtr)
{
StartTiming("SendMessage-Managed", true);
FMessage NativeMessage = (FMessage)Marshal.PtrToStructure(NativeMessagePtr, typeof(FMessage));
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
AgentMessage ManagedMessage = null;
// TODO: As we add additional versions, convert to a switch rather than if-else.
// For now, just use a simple if since we only have one version and a switch is
// overkill.
if (NativeMessage.Version == ESwarmVersionValue.VER_1_0)
{
switch (NativeMessage.Type)
{
case EMessageType.TASK_REQUEST_RESPONSE:
// Swallow this message, since it should not be sent along to a local connection
// since all Job and Task information is contained within the Agent itself
break;
case EMessageType.TASK_STATE:
{
FTaskState NativeTaskStateMessage = (FTaskState)Marshal.PtrToStructure(NativeMessagePtr, typeof(FTaskState));
AgentGuid ManagedTaskGuid = new AgentGuid(NativeTaskStateMessage.TaskGuid.A,
NativeTaskStateMessage.TaskGuid.B,
NativeTaskStateMessage.TaskGuid.C,
NativeTaskStateMessage.TaskGuid.D);
EJobTaskState TaskState = (EJobTaskState)NativeTaskStateMessage.TaskState;
AgentTaskState ManagedTaskStateMessage = new AgentTaskState(null, ManagedTaskGuid, TaskState);
ManagedTaskStateMessage.TaskExitCode = NativeTaskStateMessage.TaskExitCode;
ManagedTaskStateMessage.TaskRunningTime = NativeTaskStateMessage.TaskRunningTime;
// If there is a message, be sure copy and pass it on
if (NativeTaskStateMessage.TaskMessage != IntPtr.Zero)
{
ManagedTaskStateMessage.TaskMessage = FStringMarshaler.MarshalNativeToManaged(NativeTaskStateMessage.TaskMessage);
}
ManagedMessage = ManagedTaskStateMessage;
}
break;
case EMessageType.INFO:
{
// Create the managed version of the info message
FInfoMessage NativeInfoMessage = (FInfoMessage)Marshal.PtrToStructure(NativeMessagePtr, typeof(FInfoMessage));
AgentInfoMessage ManagedInfoMessage = new AgentInfoMessage();
if (NativeInfoMessage.TextMessage != IntPtr.Zero)
{
ManagedInfoMessage.TextMessage = FStringMarshaler.MarshalNativeToManaged(NativeInfoMessage.TextMessage);
}
ManagedMessage = ManagedInfoMessage;
}
break;
case EMessageType.ALERT:
{
// Create the managed version of the alert message
FAlertMessage NativeAlertMessage = (FAlertMessage)Marshal.PtrToStructure(NativeMessagePtr, typeof(FAlertMessage));
AgentGuid JobGuid = new AgentGuid(NativeAlertMessage.JobGuid.A,
NativeAlertMessage.JobGuid.B,
NativeAlertMessage.JobGuid.C,
NativeAlertMessage.JobGuid.D);
AgentAlertMessage ManagedAlertMessage = new AgentAlertMessage(JobGuid);
ManagedAlertMessage.AlertLevel = (EAlertLevel)(NativeAlertMessage.AlertLevel);
AgentGuid ObjectGuid = new AgentGuid(NativeAlertMessage.ObjectGuid.A,
NativeAlertMessage.ObjectGuid.B,
NativeAlertMessage.ObjectGuid.C,
NativeAlertMessage.ObjectGuid.D);
ManagedAlertMessage.ObjectGuid = ObjectGuid;
ManagedAlertMessage.TypeId = NativeAlertMessage.TypeId;
if (NativeAlertMessage.TextMessage != IntPtr.Zero)
{
ManagedAlertMessage.TextMessage = FStringMarshaler.MarshalNativeToManaged(NativeAlertMessage.TextMessage);
}
ManagedMessage = ManagedAlertMessage;
}
break;
case EMessageType.TIMING:
{
// Create the managed version of the info message
FTimingMessage NativeTimingMessage = (FTimingMessage)Marshal.PtrToStructure(NativeMessagePtr, typeof(FTimingMessage));
AgentTimingMessage ManagedTimingMessage = new AgentTimingMessage((EProgressionState)NativeTimingMessage.State, NativeTimingMessage.ThreadNum);
ManagedMessage = ManagedTimingMessage;
}
break;
default:
// By default, just pass the message version and type through, but
// any additional payload of a specialized type will be lost
ManagedMessage = new AgentMessage((EMessageType)NativeMessage.Type);
break;
}
}
if (ManagedMessage != null)
{
try
{
// Finally, send the message to the Agent
StartTiming("SendMessage-Remote", false);
Connection.SendMessage(ConnectionHandle, ManagedMessage);
StopTiming();
ReturnValue = Constants.SUCCESS;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:SendMessage] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return( ReturnValue );
}
/**
* Adds an existing file to the cache. Note, any existing channel with the same
* name will be overwritten.
*
* @param FullPath The full path name to the file that should be copied into the cache
* @param ChannelName The name of the channel once it's in the cache
*
* @return Int32 error code (< 0 is error)
*/
public virtual Int32 AddChannel(String FullPath, String ChannelName)
{
StartTiming("AddChannel-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
try
{
StartTiming("AddChannel-Remote", false);
ReturnValue = Connection.AddChannel(ConnectionHandle, FullPath, ChannelName);
StopTiming();
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:AddChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return( ReturnValue );
}
/**
* Determines if the named channel is in the cache
*
* @param ChannelName The name of the channel to look for
*
* @return Int32 error code (< 0 is error)
*/
public virtual Int32 TestChannel(String ChannelName)
{
StartTiming("TestChannel-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
try
{
if (!ConnectionConfiguration.IsPureLocalConnection)
{
StartTiming("TestChannel-Remote", false);
ReturnValue = Connection.TestChannel(ConnectionHandle, ChannelName);
StopTiming();
}
else
{
// Testing a channel only tests the main, persistent cache for files to read
EChannelFlags ChannelFlags = (EChannelFlags)(EChannelFlags.TYPE_PERSISTENT | EChannelFlags.ACCESS_READ);
String FullManagedName = GenerateFullChannelName(ChannelName, ChannelFlags);
if (File.Exists(FullManagedName))
{
ReturnValue = Constants.SUCCESS;
}
else
{
ReturnValue = Constants.ERROR_FILE_FOUND_NOT;
}
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:TestChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return( ReturnValue );
}
/**
* Opens a data channel for streaming data into the cache associated with an Agent
*
* @param ChannelName The name of the channel being opened
* @param ChannelFlags The mode, access, and other attributes of the channel being opened
*
* @return A handle to the opened channel (< 0 is error)
*/
public virtual Int32 OpenChannel(String ChannelName, EChannelFlags ChannelFlags)
{
StartTiming("OpenChannel-Managed", true);
Int32 ChannelHandle = Constants.INVALID;
bool NewChannelSuccessfullyCreated = false;
if (Connection != null)
{
try
{
// Ask the Agent if the file is safe to open, if required
if (!ConnectionConfiguration.IsPureLocalConnection)
{
StartTiming("OpenChannel-Remote", false);
ChannelHandle = Connection.OpenChannel(ConnectionHandle, ChannelName, (EChannelFlags)ChannelFlags);
StopTiming();
}
else
{
// If this is a pure local connection, then all we need to assure
// if that the handle we generate here is unique to the connection
ChannelHandle = Interlocked.Increment(ref BaseChannelHandle);
}
// If the channel is safe to open, open it up
if (ChannelHandle >= 0)
{
// Track the newly created temp file
ChannelInfo NewChannelInfo = new ChannelInfo();
NewChannelInfo.ChannelName = ChannelName;
NewChannelInfo.ChannelFlags = ChannelFlags;
NewChannelInfo.ChannelHandle = ChannelHandle;
NewChannelInfo.ChannelFileStream = null;
NewChannelInfo.ChannelData = null;
NewChannelInfo.ChannelOffset = 0;
// Determine the proper path name for the file
String FullManagedName = GenerateFullChannelName(ChannelName, ChannelFlags);
// Try to open the file
if ((ChannelFlags & EChannelFlags.ACCESS_WRITE) != 0)
{
// Open the file stream
NewChannelInfo.ChannelFileStream = new FileStream(FullManagedName, FileMode.Create, FileAccess.Write, FileShare.None);
// Slightly different path for compressed files
if ((ChannelFlags & EChannelFlags.MISC_ENABLE_COMPRESSION) != 0)
{
Stream NewChannelStream = NewChannelInfo.ChannelFileStream;
NewChannelInfo.ChannelFileStream = new GZipStream(NewChannelStream, CompressionMode.Compress, false);
}
// If we were able to open the file, add it to the active channel list
Monitor.Enter(FreeChannelWriteBuffers);
try
{
// If available, take the next free write buffer from the list
if (FreeChannelWriteBuffers.Count > 0)
{
NewChannelInfo.ChannelData = FreeChannelWriteBuffers.Pop();
}
else
{
// Otherwise, allocate a new write buffer for this channel (default to 1MB)
NewChannelInfo.ChannelData = new byte[1024 * 1024];
}
}
finally
{
Monitor.Exit( FreeChannelWriteBuffers );
}
// Track the newly created file
OpenChannels.Add(ChannelHandle, NewChannelInfo);
NewChannelSuccessfullyCreated = true;
}
else if ((ChannelFlags & EChannelFlags.ACCESS_READ) != 0)
{
if (File.Exists(FullManagedName))
{
// Slightly different paths for compressed and non-compressed files
if ((ChannelFlags & EChannelFlags.MISC_ENABLE_COMPRESSION) != 0)
{
// Open the input stream, loading it entirely into memory
byte[] RawCompressedData = File.ReadAllBytes(FullManagedName);
// Allocate the destination buffer
// The size of the uncompressed data is contained in the last four bytes of the file
// http://www.ietf.org/rfc/rfc1952.txt?number=1952
Int32 UncompressedSize = BitConverter.ToInt32(RawCompressedData, RawCompressedData.Length - 4);
NewChannelInfo.ChannelData = new byte[UncompressedSize];
// Open the decompression stream and decompress directly into the destination
Stream DecompressionChannelStream = new GZipStream(new MemoryStream(RawCompressedData), CompressionMode.Decompress, false);
DecompressionChannelStream.Read(NewChannelInfo.ChannelData, 0, UncompressedSize);
DecompressionChannelStream.Close();
}
else
{
// Simply read in the entire file in one go
NewChannelInfo.ChannelData = File.ReadAllBytes(FullManagedName);
}
// Track the newly created channel
OpenChannels.Add(ChannelHandle, NewChannelInfo);
NewChannelSuccessfullyCreated = true;
}
else
{
// Failed to find the channel to read, return an error
ChannelHandle = Constants.ERROR_CHANNEL_NOT_FOUND;
}
}
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:OpenChannel] Error: " + Ex.ToString());
ChannelHandle = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
// If we opened the channel on the agent, but failed to create
// the file, close it on the agent
if ((ChannelHandle >= 0) &&
(NewChannelSuccessfullyCreated == false))
{
if (!ConnectionConfiguration.IsPureLocalConnection)
{
StartTiming("CloseChannel-Remote", false);
try
{
Connection.CloseChannel(ConnectionHandle, ChannelHandle);
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:OpenChannel] Cleanup error: " + Ex.Message);
CleanupClosedConnection();
}
StopTiming();
}
ChannelHandle = Constants.ERROR_CHANNEL_IO_FAILED;
}
}
else
{
ChannelHandle = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ChannelHandle;
}
/**
* Closes an open channel
*
* @param Channel An open channel handle, returned by OpenChannel
*
* @return Int32 error code (< 0 is error)
*/
public virtual Int32 CloseChannel(Int32 Channel)
{
StartTiming("CloseChannel-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
// Get the channel info, so we can close the connection on the Agent
ChannelInfo ChannelToClose = null;
if (OpenChannels.TryGetValue(Channel, out ChannelToClose))
{
try
{
// If the channel was open for WRITE, make sure any buffers are flushed
if ((ChannelToClose.ChannelFlags & EChannelFlags.ACCESS_WRITE) != 0)
{
FlushChannel(ChannelToClose);
// Now that we're done with the write buffer, add it to the free stack
// for another channel to use (only for WRITE)
Monitor.Enter(FreeChannelWriteBuffers);
try
{
FreeChannelWriteBuffers.Push(ChannelToClose.ChannelData);
}
finally
{
Monitor.Exit(FreeChannelWriteBuffers);
}
}
// Remove the channel from the collection of open channels for this connection
OpenChannels.Remove(Channel);
// Close the file handle
if (ChannelToClose.ChannelFileStream != null)
{
ChannelToClose.ChannelFileStream.Close();
ChannelToClose.ChannelFileStream = null;
}
// Notify the Agent that the channel is closed, if required
if (!ConnectionConfiguration.IsPureLocalConnection)
{
StartTiming("CloseChannel-Remote", false);
Connection.CloseChannel(ConnectionHandle, ChannelToClose.ChannelHandle);
StopTiming();
}
else
{
// Since this is a pure local connection, all we need to do is make
// sure the now-closed channel is moved from the staging area into
// the main cache, but only if the channel is PERSISTENT and WRITE
if ((ChannelToClose.ChannelFlags & EChannelFlags.TYPE_PERSISTENT) != 0)
{
if ((ChannelToClose.ChannelFlags & EChannelFlags.ACCESS_WRITE) != 0)
{
// Get the final location path
EChannelFlags WriteChannelFlags = (EChannelFlags)(EChannelFlags.TYPE_PERSISTENT | EChannelFlags.ACCESS_WRITE);
String SrcChannelName = GenerateFullChannelName(ChannelToClose.ChannelName, WriteChannelFlags);
EChannelFlags ReadChannelFlags = (EChannelFlags)(EChannelFlags.TYPE_PERSISTENT | EChannelFlags.ACCESS_READ);
String DstChannelName = GenerateFullChannelName(ChannelToClose.ChannelName, ReadChannelFlags);
// Always remove the destination file if it already exists
FileInfo DstChannel = new FileInfo(DstChannelName);
if (DstChannel.Exists)
{
DstChannel.IsReadOnly = false;
DstChannel.Delete();
}
// Copy if the paper trail is enabled; Move otherwise
if ((ChannelToClose.ChannelFlags & EChannelFlags.MISC_ENABLE_PAPER_TRAIL) != 0)
{
File.Copy(SrcChannelName, DstChannelName);
}
else
{
File.Move(SrcChannelName, DstChannelName);
}
}
}
}
ReturnValue = Constants.SUCCESS;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CloseChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
}
else
{
ReturnValue = Constants.ERROR_CHANNEL_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Writes the provided data to the open channel opened for WRITE
*
* @param Channel An open channel handle, returned by OpenChannel
* @param Data Source buffer for the write
* @param Data Size of the source buffer
*
* @return The number of bytes written (< 0 is error)
*/
public virtual Int32 WriteChannel(Int32 Channel, IntPtr Data, Int32 DataSize)
{
StartTiming("WriteChannel-Managed", true, DataSize);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
ChannelInfo ChannelToWrite = null;
if ((OpenChannels.TryGetValue(Channel, out ChannelToWrite)) &&
(ChannelToWrite.ChannelFileStream != null))
{
try
{
bool WriteBuffered = false;
if (ChannelToWrite.ChannelData != null)
{
// See if the new data will fit into the write buffer
if ((ChannelToWrite.ChannelOffset + DataSize) <= ChannelToWrite.ChannelData.Length)
{
Marshal.Copy(Data, ChannelToWrite.ChannelData, ChannelToWrite.ChannelOffset, DataSize);
ChannelToWrite.ChannelOffset += DataSize;
ReturnValue = DataSize;
WriteBuffered = true;
}
else
{
// Otherwise, flush any pending writes and try again with the reset buffer
FlushChannel(ChannelToWrite);
if (DataSize <= ChannelToWrite.ChannelData.Length)
{
Marshal.Copy(Data, ChannelToWrite.ChannelData, 0, DataSize);
ChannelToWrite.ChannelOffset = DataSize;
ReturnValue = DataSize;
WriteBuffered = true;
}
}
}
// Write was not buffered, just write it directly out
if (!WriteBuffered)
{
try
{
// Allocate a temporary buffer and copy the data in
byte[] TempChannelData = new byte[DataSize];
Marshal.Copy(Data, TempChannelData, 0, DataSize);
ChannelToWrite.ChannelFileStream.Write(TempChannelData, 0, DataSize);
ReturnValue = DataSize;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:WriteChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CHANNEL_IO_FAILED;
}
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:WriteChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
// If this was not a successful IO operation, remove the channel
// from the set of active channels
if (ReturnValue < 0)
{
OpenChannels.Remove(Channel);
try
{
ChannelToWrite.ChannelFileStream.Close();
ChannelToWrite.ChannelFileStream = null;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:WriteChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
}
}
else
{
ReturnValue = Constants.ERROR_CHANNEL_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Reads data from a channel opened for READ into the provided buffer
*
* @param Channel An open channel handle, returned by OpenChannel
* @param Data Destination buffer for the read
* @param Data Size of the destination buffer
*
* @return The number of bytes read (< 0 is error)
*/
public virtual Int32 ReadChannel(Int32 Channel, IntPtr Data, Int32 DataSize)
{
StartTiming("ReadChannel-Managed", true, DataSize);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
ChannelInfo ChannelToRead = null;
if ((OpenChannels.TryGetValue(Channel, out ChannelToRead)) &&
(ChannelToRead.ChannelData != null))
{
try
{
// Read the data directly from our buffered copy of the file
Int32 DataRemaining = ChannelToRead.ChannelData.Length - ChannelToRead.ChannelOffset;
if (DataRemaining >= 0)
{
Int32 SizeToRead = DataRemaining < DataSize ? DataRemaining : DataSize;
if (SizeToRead > 0)
{
Marshal.Copy(ChannelToRead.ChannelData, ChannelToRead.ChannelOffset, Data, SizeToRead);
ChannelToRead.ChannelOffset += SizeToRead;
}
ReturnValue = SizeToRead;
}
else
{
ReturnValue = Constants.ERROR_CHANNEL_IO_FAILED;
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:ReadChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
// If this was not a successful IO operation, remove the channel
// from the set of active channels
if (ReturnValue < 0)
{
OpenChannels.Remove(Channel);
}
}
else
{
ReturnValue = Constants.ERROR_CHANNEL_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Opens a Job session, which allows a Job to be specified, Tasks added, Job
* channels opened and used, etc. When the Job is complete and no more Job
* related data is needed from the Swarm, call CloseJob.
*
* @param JobGuid A GUID that uniquely identifies this Job, generated by the caller
*
* @return Int32 Error code (< 0 is an error)
*/
public virtual Int32 OpenJob(FGuid JobGuid)
{
StartTiming("OpenJob-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
StartTiming("OpenJob-Remote", false);
try
{
AgentGuid ManagedJobGuid = new AgentGuid(JobGuid.A, JobGuid.B, JobGuid.C, JobGuid.D);
ReturnValue = Connection.OpenJob(ConnectionHandle, ManagedJobGuid);
if (ReturnValue >= 0)
{
// If the call was successful, assign the Job Guid as the active one
ConnectionConfiguration.AgentJobGuid = ManagedJobGuid;
// Allocate a new list to collect tasks until the specification is complete
PendingTasks = new List<AgentTaskSpecification>();
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:OpenJob] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
StopTiming();
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Begins a Job specification, which allows a series of Tasks to be specified
* via AddTask. When Tasks are done being specified, call EndJobSpecification.
*
* The default behavior will be to execute the Job executable with the
* specified parameters. If Tasks are added for the Job, they are expected
* to be requested by the executable run for the Job. If no Tasks are added
* for the Job, it is expected that the Job executable will perform its
* operations without additional Task input from Swarm.
*
* @param Specification32 A structure describing a new 32-bit Job (can be an empty specification)
* @param Specification64 A structure describing a new 64-bit Job (can be an empty specification)
*
* @return Int32 Error code (< 0 is an error)
*/
public virtual Int32 BeginJobSpecification(FJobSpecification Specification32, FJobSpecification Specification64)
{
StartTiming("BeginJobSpecification-Managed", true );
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
if (ConnectionConfiguration.AgentJobGuid != null)
{
// Convert the specifications from native to managed
AgentJobSpecification NewSpecification32 = ConvertJobSpecification(ref Specification32, false);
AgentJobSpecification NewSpecification64 = ConvertJobSpecification(ref Specification64, true);
// Ensure all the files are in the cache with the right cache compatible name
if (NewSpecification32 != null)
{
ReturnValue = CacheAllFiles(NewSpecification32);
}
if (NewSpecification64 != null && (NewSpecification32 == null || ReturnValue < 0))
{
ReturnValue = CacheAllFiles(NewSpecification64);
}
if (ReturnValue >= 0)
{
// Pack up the optional descriptions into Hashtables and pass them along
UInt32 DescriptionIndex;
Hashtable NewDescription32 = null;
Hashtable NewDescription64 = null;
// 32-bit specification description
if (Specification32.DescriptionCount > 0)
{
try
{
NewDescription32 = new Hashtable();
NewDescription32["Version"] = ESwarmVersionValue.VER_1_0;
for (DescriptionIndex = 0; DescriptionIndex < Specification32.DescriptionCount; DescriptionIndex++)
{
String NewKey = Specification32.DescriptionKeys[DescriptionIndex];
String NewValue = Specification32.DescriptionValues[DescriptionIndex];
NewDescription32[NewKey] = NewValue;
}
}
catch (Exception Ex)
{
// Failed to transfer optional description, log and continue
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:BeginJobSpecification] Error with Specification32 Description: " + Ex.Message);
NewDescription32 = null;
}
}
// 64-bit specification description
if (Specification64.DescriptionCount > 0)
{
try
{
NewDescription64 = new Hashtable();
NewDescription64["Version"] = ESwarmVersionValue.VER_1_0;
for (DescriptionIndex = 0; DescriptionIndex < Specification64.DescriptionCount; DescriptionIndex++)
{
String NewKey = Specification64.DescriptionKeys[DescriptionIndex];
String NewValue = Specification64.DescriptionValues[DescriptionIndex];
NewDescription64[NewKey] = NewValue;
}
}
catch (Exception Ex)
{
// Failed to transfer optional description, log and continue
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:BeginJobSpecification] Error with Specification64 Description: " + Ex.Message);
NewDescription64 = null;
}
}
StartTiming("BeginJobSpecification-Remote", false);
try
{
ReturnValue = Connection.BeginJobSpecification(ConnectionHandle, NewSpecification32, NewDescription32, NewSpecification64, NewDescription64);
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:BeginJobSpecification] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
StopTiming();
}
}
else
{
ReturnValue = Constants.ERROR_JOB_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Adds a Task to the current Job
*
* @param Specification A structure describing the new Task
*
* @return Int32 Error code (< 0 is an error)
*/
public virtual Int32 AddTask(FTaskSpecification Specification)
{
StartTiming("AddTask-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
if (ConnectionConfiguration.AgentJobGuid != null)
{
// Convert the parameters from native to managed
AgentGuid TaskGuid = new AgentGuid(Specification.TaskGuid.A,
Specification.TaskGuid.B,
Specification.TaskGuid.C,
Specification.TaskGuid.D);
String Parameters = Specification.Parameters;
List<String> Dependencies = null;
if (Specification.DependencyCount > 0)
{
Dependencies = new List<String>();
for (UInt32 i = 0; i < Specification.DependencyCount; i++)
{
Dependencies.Add(Specification.Dependencies[i]);
}
}
AgentTaskSpecification NewSpecification =
new AgentTaskSpecification(ConnectionConfiguration.AgentJobGuid, TaskGuid, (Int32)Specification.Flags, Parameters, (Int32)Specification.Cost, Dependencies);
// Ensure all the files are in the cache with the right cache compatible name
ReturnValue = CacheAllFiles(NewSpecification);
if (ReturnValue >= 0)
{
// Queue up all tasks until the specification is complete and submit them all at once
PendingTasks.Add(NewSpecification);
}
}
else
{
ReturnValue = Constants.ERROR_JOB_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Ends the Job specification, after which no additional Tasks may be defined. Also,
* this is generally the point when the Agent will validate and launch the Job executable,
* potentially distributing the Job to other Agents.
*
* @return Int32 Error code (< 0 is an error)
*/
public virtual Int32 EndJobSpecification()
{
StartTiming("EndJobSpecification-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
if (ConnectionConfiguration.AgentJobGuid != null)
{
StartTiming("EndJobSpecification-Remote", false);
try
{
// Add all queued up and pending tasks now, all at once
ReturnValue = Connection.AddTask(ConnectionHandle, PendingTasks);
if( ReturnValue >= 0 )
{
// Finally, end the specification
ReturnValue = Connection.EndJobSpecification(ConnectionHandle);
}
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:EndJobSpecification] Error: " + Ex.Message + " " + Ex.ToString());
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
PendingTasks = null;
StopTiming();
}
else
{
ReturnValue = Constants.ERROR_JOB_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Ends the Job, after which all Job-related API usage (except OpenJob) will be rejected
*
* @return Int32 Error code (< 0 is an error)
*/
public virtual Int32 CloseJob()
{
StartTiming("CloseJob-Managed", true);
Int32 ReturnValue = Constants.INVALID;
if (Connection != null)
{
if (ConnectionConfiguration.AgentJobGuid != null)
{
StartTiming("CloseJob-Remote", false);
try
{
ReturnValue = Connection.CloseJob(ConnectionHandle);
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CloseJob] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
StopTiming();
// Reset the active Job Guid value
try
{
ConnectionConfiguration.AgentJobGuid = null;
}
catch (Exception)
{
// The ConnectionConfiguration can be set to null
// asynchronously, so we'll need to try/catch here
}
}
else
{
ReturnValue = Constants.ERROR_JOB_NOT_FOUND;
}
}
else
{
ReturnValue = Constants.ERROR_CONNECTION_NOT_FOUND;
}
StopTiming();
return ReturnValue;
}
/**
* Adds a line of text to the Agent log window
*
* @param Verbosity the importance of this message
* @param TextColour the colour of the text
* @param Message the line of text to add
*/
public virtual Int32 Log(EVerbosityLevel Verbosity, ELogColour TextColour, String Message)
{
try
{
Connection.Log(Verbosity, TextColour, Message);
}
catch (Exception)
{
}
return Constants.SUCCESS;
}
/**
* The function for the message queue monitoring thread used for
* calling the callback from the remote Agent
*/
static void MessageThreadProc(Object ThreadParameters)
{
MessageThreadData ThreadData = (MessageThreadData)ThreadParameters;
IAgentInterfaceWrapper Connection = ThreadData.Connection;
try
{
// Because the way we use the GetMessage call is blocking, if we ever break out, quit
AgentMessage ManagedMessage = null;
while (Connection.GetMessage(ThreadData.ConnectionHandle, out ManagedMessage, -1) >= 0)
{
// TODO: As we add additional versions, convert to a switch rather than if-else.
// For now, just use a simple if since we only have one version and a switch is
// overkill.
if (ManagedMessage.Version == ESwarmVersionValue.VER_1_0)
{
IntPtr NativeMessage = IntPtr.Zero;
String PinnedStringData = null;
switch (ManagedMessage.Type)
{
case EMessageType.JOB_STATE:
{
AgentJobState JobStateMessage = (AgentJobState)ManagedMessage;
FGuid JobGuid = new FGuid(JobStateMessage.JobGuid.A,
JobStateMessage.JobGuid.B,
JobStateMessage.JobGuid.C,
JobStateMessage.JobGuid.D);
FJobState NativeJobStateMessage = new FJobState(JobGuid, (EJobTaskState)JobStateMessage.JobState);
NativeJobStateMessage.JobExitCode = JobStateMessage.JobExitCode;
NativeJobStateMessage.JobRunningTime = JobStateMessage.JobRunningTime;
// If there is a message, be sure to pin and pass it on
if (JobStateMessage.JobMessage != null)
{
Char[] RawJobMessageData = JobStateMessage.JobMessage.ToCharArray();
if (RawJobMessageData.Length > 0)
{
// Pin the string data for the message
PinnedStringData = new String(RawJobMessageData);
NativeJobStateMessage.JobMessage = FStringMarshaler.MarshalManagedToNative(PinnedStringData);
}
}
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FJobState)));
Marshal.StructureToPtr(NativeJobStateMessage, NativeMessage, false);
}
break;
case EMessageType.TASK_REQUEST:
// Swallow this message, since it should not be sent along to a local connection
// since all Job and Task information is contained within the Agent itself
break;
case EMessageType.TASK_REQUEST_RESPONSE:
{
AgentTaskRequestResponse TaskRequestResponseMessage = (AgentTaskRequestResponse)ManagedMessage;
// Switch again on the response type
ETaskRequestResponseType ResponseType = (ETaskRequestResponseType)TaskRequestResponseMessage.ResponseType;
switch (ResponseType)
{
case ETaskRequestResponseType.RELEASE:
case ETaskRequestResponseType.RESERVATION:
{
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FTaskRequestResponse)));
Marshal.StructureToPtr(new FTaskRequestResponse((ETaskRequestResponseType)ResponseType), NativeMessage, false);
}
break;
case ETaskRequestResponseType.SPECIFICATION:
{
AgentTaskSpecification TaskSpecificationMessage = (AgentTaskSpecification)TaskRequestResponseMessage;
FGuid TaskGuid = new FGuid(TaskSpecificationMessage.TaskGuid.A,
TaskSpecificationMessage.TaskGuid.B,
TaskSpecificationMessage.TaskGuid.C,
TaskSpecificationMessage.TaskGuid.D);
EJobTaskFlags TaskFlags = (EJobTaskFlags)TaskSpecificationMessage.TaskFlags;
Char[] RawParametersData = TaskSpecificationMessage.Parameters.ToCharArray();
if (RawParametersData.Length > 0)
{
// Pin the string data for the message
PinnedStringData = new String(RawParametersData);
}
FTaskSpecificationMarshalHelper MarshalHelper = new FTaskSpecificationMarshalHelper();
MarshalHelper.TaskGuid = TaskGuid;
MarshalHelper.Parameters = FStringMarshaler.MarshalManagedToNative(PinnedStringData);
MarshalHelper.Flags = TaskFlags;
MarshalHelper.Cost = 0;
MarshalHelper.DependencyCount = 0;
MarshalHelper.Dependencies = IntPtr.Zero;
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FTaskSpecificationMarshalHelper)));
Marshal.StructureToPtr(MarshalHelper, NativeMessage, false);
}
break;
}
}
break;
case EMessageType.TASK_STATE:
{
AgentTaskState TaskStateMessage = (AgentTaskState)ManagedMessage;
// TODO: Assert that we have a valid Job GUID, since this must be the Instigator
// TODO: Assert that the Job GUID of the message matches the ConnectionConfiguration.AgentJobGuid
FGuid TaskGuid = new FGuid(TaskStateMessage.TaskGuid.A,
TaskStateMessage.TaskGuid.B,
TaskStateMessage.TaskGuid.C,
TaskStateMessage.TaskGuid.D);
FTaskState NativeTaskStateMessage = new FTaskState(TaskGuid, (EJobTaskState)TaskStateMessage.TaskState);
// If there is a message, be sure to pin and pass it
if (TaskStateMessage.TaskMessage != null)
{
Char[] RawTaskMessageData = TaskStateMessage.TaskMessage.ToCharArray();
if (RawTaskMessageData.Length > 0)
{
// Pin the string data for the message
PinnedStringData = new String(RawTaskMessageData);
NativeTaskStateMessage.TaskMessage = FStringMarshaler.MarshalManagedToNative(PinnedStringData);
}
}
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FTaskState)));
Marshal.StructureToPtr(NativeTaskStateMessage, NativeMessage, false);
}
break;
case EMessageType.INFO:
{
// Create the managed version of the info message
AgentInfoMessage ManagedInfoMessage = (AgentInfoMessage)ManagedMessage;
FInfoMessage NativeInfoMessage = new FInfoMessage("");
if (ManagedInfoMessage.TextMessage != null)
{
Char[] RawTaskMessageData = ManagedInfoMessage.TextMessage.ToCharArray();
if (RawTaskMessageData.Length > 0)
{
// Pin the string data for the message
PinnedStringData = new String(RawTaskMessageData);
NativeInfoMessage.TextMessage = FStringMarshaler.MarshalManagedToNative(PinnedStringData);
}
}
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FInfoMessage)));
Marshal.StructureToPtr(NativeInfoMessage, NativeMessage, false);
}
break;
case EMessageType.ALERT:
{
// Create the managed version of the info message
AgentAlertMessage ManagedAlertMessage = (AgentAlertMessage)ManagedMessage;
FGuid JobGuid = new FGuid(ManagedAlertMessage.JobGuid.A,
ManagedAlertMessage.JobGuid.B,
ManagedAlertMessage.JobGuid.C,
ManagedAlertMessage.JobGuid.D);
FGuid ObjectGuid = new FGuid(ManagedAlertMessage.ObjectGuid.A,
ManagedAlertMessage.ObjectGuid.B,
ManagedAlertMessage.ObjectGuid.C,
ManagedAlertMessage.ObjectGuid.D);
FAlertMessage NativeAlertMessage = new FAlertMessage(
JobGuid,
ManagedAlertMessage.AlertLevel,
ObjectGuid,
ManagedAlertMessage.TypeId);
if (ManagedAlertMessage.TextMessage != null)
{
Char[] RawTaskMessageData = ManagedAlertMessage.TextMessage.ToCharArray();
if (RawTaskMessageData.Length > 0)
{
// Pin the string data for the message
PinnedStringData = new String(RawTaskMessageData);
NativeAlertMessage.TextMessage = FStringMarshaler.MarshalManagedToNative(PinnedStringData);
}
}
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FAlertMessage)));
Marshal.StructureToPtr(NativeAlertMessage, NativeMessage, false);
}
break;
default:
// By default, just pass the message version and type through, but
// any additional payload of a specialized type will be lost
NativeMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FMessage)));
Marshal.StructureToPtr(new FMessage(ESwarmVersionValue.VER_1_0, (EMessageType)ManagedMessage.Type), NativeMessage, false);
break;
}
// If a message was created to pass on to the user's callback, send it on
if (NativeMessage != IntPtr.Zero)
{
// Call the user's callback function
ThreadData.ConnectionCallback(NativeMessage, ThreadData.ConnectionCallbackData);
// All finished with the message, free and set to null
NativeMessage = IntPtr.Zero;
}
if (ManagedMessage.Type == EMessageType.QUIT)
{
// Return from this function, which will exit this message processing thread
DebugLog.Write("Message queue thread shutting down from a QUIT message");
return;
}
}
// Reset the message handle
ManagedMessage = null;
}
}
catch (ThreadAbortException)
{
// An expected exception when closing the connection
DebugLog.Write("Message queue thread shutting down normally after being closed by CloseConnection");
}
catch (Exception Ex)
{
DebugLog.Write("Error: Exception in the message queue thread: " + Ex.Message);
// If the connection has thrown us an exception, close the connection
DebugLog.Write("MessageThreadProc calling CleanupClosedConnection");
ThreadData.Owner.CleanupClosedConnection();
}
// Only write out in debug
DebugLog.Write("Message queue thread shutting down normally");
}
/**
* The function for the agent monitoring thread used to watch
* for potential crashed or hung agents
*/
static void MonitorThreadProc(Object ThreadParameters)
{
MessageThreadData ThreadData = (MessageThreadData)ThreadParameters;
AgentConfiguration ConnectionConfiguration = ThreadData.ConnectionConfiguration;
IAgentInterfaceWrapper Connection = ThreadData.Connection;
bool NeedToCleanupClosedConnection = false;
try
{
if (Connection != null)
{
// We'll just monitor the process itself to see if it exits
Int32 ConnectionProcessID = ConnectionConfiguration.AgentProcessID;
Process AgentProcess = Process.GetProcessById(ConnectionProcessID);
if (AgentProcess != null)
{
// As long as the process is still running, yield
bool KeepRunning = true;
while (KeepRunning && !KillMonitorThread)
{
// Sleep for a little bit at the beginning of each iteration
Thread.Sleep(1000);
if (AgentProcess.HasExited == true)
{
// If the Agent process has exited, game over
NeedToCleanupClosedConnection = true;
KeepRunning = false;
}
}
KillMonitorThread = false;
}
}
}
catch (ThreadAbortException)
{
// An expected exception when closing the connection
DebugLog.Write("Agent monitor thread shutting down normally after being closed by CloseConnection");
}
catch (Exception Ex)
{
DebugLog.Write("Exception in the agent monitor thread: " + Ex.Message);
// If the connection has thrown us an exception, close the connection
NeedToCleanupClosedConnection = true;
}
// If needed, clean up the connection
if (NeedToCleanupClosedConnection)
{
DebugLog.Write("MonitorThreadProc calling CleanupClosedConnection");
ThreadData.Owner.CleanupClosedConnection();
}
DebugLog.Write("Agent monitor thread shutting down normally");
}
/*
* Start a timer going
*/
void StartTiming(String Name, bool Accum, Int64 Adder)
{
if (PerfTimerInstance != null)
{
PerfTimerInstance.Start(Name, Accum, Adder);
}
}
void StartTiming(String Name, bool Accum)
{
StartTiming(Name, Accum, 0);
}
/*
* Stop a timer
*/
void StopTiming()
{
if (PerfTimerInstance != null)
{
PerfTimerInstance.Stop();
}
}
/*
* Print out all the timing info
*/
void DumpTimings()
{
if (PerfTimerInstance != null)
{
String PerfMessage = PerfTimerInstance.DumpTimings();
IntPtr PerfMessagePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FInfoMessage)));
Marshal.StructureToPtr(new FInfoMessage(PerfMessage), PerfMessagePtr, true);
SendMessage(PerfMessagePtr);
PerfTimerInstance = null;
}
}
/*
* Clean up all of the remainders from a closed connection, including the network channels, etc.
*/
Int32 CleanupClosedConnection()
{
Monitor.Enter(CleanupClosedConnectionLock);
// NOTE: Do not make any calls to the real Connection in here!
// If, for any reason, the connection has died, calling into it from here will
// end up causing this thread to hang, waiting for the dead connection to respond.
DebugLog.Write("[Interface:CleanupClosedConnection] Closing all connections to the Agent");
// Reset all necessary assigned variables
AgentProcess = null;
AgentProcessOwner = false;
if (Connection != null)
{
// Notify the connection wrapper that the connection is gone
Connection.SignalConnectionDropped();
Connection = null;
}
ConnectionHandle = Constants.INVALID;
ConnectionConfiguration = null;
// Clean up and close up the connection callback
if (ConnectionCallback != null)
{
IntPtr QuitMessage = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FMessage)));
Marshal.StructureToPtr(new FMessage(ESwarmVersionValue.VER_1_0, EMessageType.QUIT), QuitMessage, false);
ConnectionCallback(QuitMessage, ConnectionCallbackData);
}
ConnectionCallback = null;
ConnectionCallbackData = IntPtr.Zero;
// Close all associated channels
if (OpenChannels.Count > 0)
{
foreach (ChannelInfo NextChannel in OpenChannels.Values)
{
// Just close the handle and we'll clear the entire list later
if (NextChannel.ChannelFileStream != null)
{
NextChannel.ChannelFileStream.Close();
NextChannel.ChannelFileStream = null;
}
}
OpenChannels.Clear();
}
// Unregister the primary network communication channel
if (NetworkChannel != null)
{
ChannelServices.UnregisterChannel(NetworkChannel);
NetworkChannel = null;
}
Monitor.Exit(CleanupClosedConnectionLock);
return Constants.SUCCESS;
}
/** All agent/swarm-specific variables */
Process AgentProcess;
bool AgentProcessOwner;
/** All connection-specific variables */
IAgentInterfaceWrapper Connection;
Int32 ConnectionHandle;
Thread ConnectionMessageThread;
Thread ConnectionMonitorThread;
FConnectionCallback ConnectionCallback;
IntPtr ConnectionCallbackData;
ELogFlags ConnectionLoggingFlags;
AgentConfiguration ConnectionConfiguration;
Object CleanupClosedConnectionLock;
/** A convenience class for packing a few managed types together */
class ChannelInfo
{
public String ChannelName;
public EChannelFlags ChannelFlags;
public Int32 ChannelHandle;
public Stream ChannelFileStream;
// For buffered reads and writes
public byte[] ChannelData;
public Int32 ChannelOffset;
};
ReaderWriterDictionary<Int32, ChannelInfo> OpenChannels;
Stack<byte[]> FreeChannelWriteBuffers;
Int32 BaseChannelHandle;
/** While a job is being specified, collect the task specifications until it's done, then submit all at once */
List<AgentTaskSpecification> PendingTasks;
/** A pair of locations needed for using channels */
String AgentCacheFolder;
/** The network channel used to communicate with the Agent */
TcpClientChannel NetworkChannel;
/** A helper class for timing info */
PerfTimer PerfTimerInstance;
Int32 TryOpenConnection(FConnectionCallback CallbackFunc, IntPtr CallbackData, ELogFlags LoggingFlags)
{
try
{
// Allocate our new connection wrapper object
Connection = new IAgentInterfaceWrapper();
// Make sure the agent is alive and responsive before continuing
EditorLog(EVerbosityLevel.Informative, "[TryOpenConnection] Testing the Agent");
Hashtable InParameters = null;
Hashtable OutParameters = null;
bool AgentIsReady = false;
while (!AgentIsReady)
{
try
{
// Simply try to call the method and if it doesn't throw
// an exception, consider it a success
Connection.Method(0, InParameters, ref OutParameters);
AgentIsReady = true;
}
catch (Exception ex)
{
// Wait a little longer
EditorLog(EVerbosityLevel.Critical, "[TryOpenConnection] Waiting for the agent to start up ...");
EditorLog(EVerbosityLevel.Critical, ex.ToString());
Thread.Sleep(5000);
}
}
// Request an official connection to the Agent
EditorLog(EVerbosityLevel.Informative, "[TryOpenConnection] Opening Connection to Agent");
EditorLog(EVerbosityLevel.Informative, "[TryOpenConnection] Local Process ID is " + Process.GetCurrentProcess().Id.ToString());
StartTiming("OpenConnection-Remote", false);
ConnectionHandle = Connection.OpenConnection(AgentProcess, AgentProcessOwner, Process.GetCurrentProcess().Id, LoggingFlags, out ConnectionConfiguration);
StopTiming();
if (ConnectionHandle >= 0)
{
Log(EVerbosityLevel.Informative, ELogColour.Green, "[Interface:TryOpenConnection] Local connection established");
// Spawn a thread to monitor the message queue
MessageThreadData ThreadData = new MessageThreadData();
ThreadData.Owner = this;
ThreadData.Connection = Connection;
ThreadData.ConnectionHandle = ConnectionHandle;
ThreadData.ConnectionCallback = CallbackFunc;
ThreadData.ConnectionCallbackData = CallbackData;
ThreadData.ConnectionConfiguration = ConnectionConfiguration;
// Launch the message queue thread
ConnectionMessageThread = new Thread(new ParameterizedThreadStart(MessageThreadProc));
ConnectionMessageThread.Name = "ConnectionMessageThread";
ConnectionMessageThread.Start( ThreadData );
// Launch the agent monitor thread
ConnectionMonitorThread = new Thread(new ParameterizedThreadStart(MonitorThreadProc));
ConnectionMonitorThread.Name = "ConnectionMonitorThread";
ConnectionMonitorThread.Start(ThreadData);
// Save the user's callback routine
ConnectionCallback = CallbackFunc;
ConnectionCallbackData = CallbackData;
ConnectionLoggingFlags = LoggingFlags;
}
}
catch (Exception Ex)
{
EditorLog(EVerbosityLevel.Critical, "[TryOpenConnection] Error: " + Ex.Message);
EditorLog(EVerbosityLevel.Critical, Ex.ToString());
ConnectionHandle = Constants.INVALID;
Connection = null;
}
return ConnectionHandle;
}
void EnsureAgentIsRunning(string OptionsFolder)
{
// See if an agent is already running, and if not, launch one
AgentProcess = null;
AgentProcessOwner = false;
#if !__MonoCS__
Process[] RunningSwarmAgents = Process.GetProcessesByName("SwarmAgent");
if (RunningSwarmAgents.Length > 0)
{
// If any are running, just grab the first one
AgentProcess = RunningSwarmAgents[0];
}
#else
Process[] RunningSwarmAgents = Process.GetProcessesByName("mono");
if (RunningSwarmAgents.Length == 0)
{
RunningSwarmAgents = Process.GetProcessesByName("mono-sgen");
}
foreach (Process Iter in RunningSwarmAgents)
{
foreach(ProcessModule m in Iter.Modules)
{
if(m.ModuleName.Contains("SwarmAgent"))
{
AgentProcess = Iter;
break;
}
}
if(AgentProcess != null)
break;
}
if (RunningSwarmAgents.Length > 0)
{
// If any are running, just grab the first one
AgentProcess = RunningSwarmAgents[0];
}
#endif
if (AgentProcess == null)
{
String StartupPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
#if !__MonoCS__
StartupPath += "/../DotNET/SwarmAgent.exe";
#else
StartupPath += "/../Mac/SwarmAgent.exe";
#endif
// If none, launch the Agent binary
DebugLog.Write("[OpenConnection] Spawning agent: " + StartupPath);
if (File.Exists(StartupPath))
{
String Arguments = "IAmSwarmAgent";
if ( OptionsFolder.Length > 0 )
{
Arguments += " -OptionsFolder=\"" + OptionsFolder + "\"";
}
AgentProcess = Process.Start(StartupPath, Arguments);
if (AgentProcess == null)
{
DebugLog.Write("[OpenConnection] Failed to start the Agent executable: " + StartupPath);
}
else
{
// If we started the process, we own it and control its lifetime
AgentProcessOwner = true;
}
}
else
{
DebugLog.Write("[OpenConnection] Failed to find the Agent executable: " + StartupPath);
}
}
}
/**
* Converts an native job specification to a managed version.
*
* @param Specification Native job specification (may be empty)
* @param bIs64bit Whether the job specification is 64-bit or not
* @return Newly created managed job specification, or null if the specification was empty
*/
AgentJobSpecification ConvertJobSpecification(ref FJobSpecification Specification, bool bIs64bit)
{
if (Specification.ExecutableName == null)
{
return null;
}
else
{
// Convert the parameters from native to managed
String ExecutableName = Specification.ExecutableName;
String Parameters = Specification.Parameters;
EJobTaskFlags Flags = (EJobTaskFlags)((Specification.Flags & ~EJobTaskFlags.FLAG_64BIT) | (bIs64bit ? EJobTaskFlags.FLAG_64BIT : 0));
List<String> RequiredDependencies = null;
if (Specification.RequiredDependencyCount > 0)
{
RequiredDependencies = new List<String>();
for (UInt32 i = 0; i < Specification.RequiredDependencyCount; i++)
{
RequiredDependencies.Add(Specification.RequiredDependencies[i]);
}
}
List<String> OptionalDependencies = null;
if (Specification.OptionalDependencyCount > 0)
{
OptionalDependencies = new List<String>();
for (UInt32 i = 0; i < Specification.OptionalDependencyCount; i++)
{
OptionalDependencies.Add(Specification.OptionalDependencies[i]);
}
}
return new AgentJobSpecification(ConnectionConfiguration.AgentJobGuid, (EJobTaskFlags)Flags, ExecutableName, Parameters, RequiredDependencies, OptionalDependencies);
}
}
/*
* Generates the full channel name, including Job path and staging area path if necessary
*/
String GenerateFullChannelName(String ManagedChannelName, EChannelFlags ChannelFlags)
{
if ((ChannelFlags & EChannelFlags.TYPE_PERSISTENT) != 0)
{
if ((ChannelFlags & EChannelFlags.ACCESS_WRITE) != 0)
{
// A persistent cache channel open for writing opens in the staging area
String StagingAreaName = Path.Combine(AgentCacheFolder, "AgentStagingArea");
return Path.Combine(StagingAreaName, ManagedChannelName);
}
else if ((ChannelFlags & EChannelFlags.ACCESS_READ) != 0)
{
// A persistent cache channel open for reading opens directly in the cache
return Path.Combine(AgentCacheFolder, ManagedChannelName);
}
}
else if ((ChannelFlags & EChannelFlags.TYPE_JOB_ONLY) != 0)
{
AgentGuid JobGuid = ConnectionConfiguration.AgentJobGuid;
if (JobGuid == null)
{
// If there's no Job associated with this connection at this point, provide
// a default GUID for one for debugging access to the agent cache
JobGuid = new AgentGuid(0x00000123, 0x00004567, 0x000089ab, 0x0000cdef);
}
// A Job Channel opens directly in the Job-specific directory
String JobsFolder = Path.Combine(AgentCacheFolder, "Jobs");
String JobFolderName = Path.Combine(JobsFolder, "Job-" + JobGuid.ToString());
return Path.Combine(JobFolderName, ManagedChannelName);
}
return "";
}
/*
* CacheFileAndConvertName
*
* Checks to see if the file with the correct timestamp and size exists in the cache
* If it does not, add it to the cache
*/
Int32 CacheFileAndConvertName(String FullPathName, out String CachedFileName, bool bUse64bitNamingConvention)
{
Int32 ReturnValue = Constants.INVALID;
CachedFileName = "";
// First, check that the original file exists
if (File.Exists(FullPathName))
{
// The full cache name is based on the file name, the last modified timestamp on the file, and the file size
FileInfo FullPathFileInfo = new FileInfo(FullPathName);
// The last modified timestamp
String LastModifiedTimeUTCString = FullPathFileInfo.LastWriteTimeUtc.ToString("yyyy-MM-dd_HH-mm-ss");
// The file size, which we get by opening the file
String FileSizeInBytesString = FullPathFileInfo.Length.ToString();
String Suffix64bit = bUse64bitNamingConvention ? "-64bit" : "";
// Compose the full cache name
CachedFileName = String.Format("{0}_{1}_{2}{3}{4}",
Path.GetFileNameWithoutExtension(FullPathName),
LastModifiedTimeUTCString,
FileSizeInBytesString,
Suffix64bit,
FullPathFileInfo.Extension);
// Test the cache with the cached file name
if (Connection.TestChannel(ConnectionHandle, CachedFileName) < 0)
{
// If not already in the cache, attempt to add it now
ReturnValue = Connection.AddChannel(ConnectionHandle, FullPathName, CachedFileName);
}
else
{
ReturnValue = Constants.SUCCESS;
}
}
else
{
ReturnValue = Constants.ERROR_FILE_FOUND_NOT;
}
// Didn't find the file, return failure
return ReturnValue;
}
/*
* CacheAllFiles
*
* Ensures all executables and dependencies are correctly placed in the cache
*/
Int32 CacheAllFiles(AgentJobSpecification JobSpecification)
{
Int32 ReturnValue = Constants.INVALID;
try
{
// Allocate the dictionary we'll use for the name mapping
Dictionary<String, String> DependenciesOriginalNames = new Dictionary<String, String>();
bool bUse64bitNamingConvention = (JobSpecification.JobFlags & EJobTaskFlags.FLAG_64BIT) != 0;
// Check for and possibly cache the executable
String OriginalExecutableFullPath = Path.GetFullPath(JobSpecification.ExecutableName);
String CachedExecutableName;
ReturnValue = CacheFileAndConvertName(OriginalExecutableFullPath, out CachedExecutableName, bUse64bitNamingConvention);
if (ReturnValue < 0)
{
// Failed to cache the executable, return failure
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CacheAllFiles] Failed to cache the executable: " + OriginalExecutableFullPath);
return ReturnValue;
}
JobSpecification.ExecutableName = CachedExecutableName;
String OriginalExecutableName = Path.GetFileName(OriginalExecutableFullPath);
DependenciesOriginalNames.Add(CachedExecutableName, OriginalExecutableName);
// Check and cache all the required dependencies
if ((JobSpecification.RequiredDependencies != null) &&
(JobSpecification.RequiredDependencies.Count > 0))
{
for (Int32 i = 0; i < JobSpecification.RequiredDependencies.Count; i++)
{
String OriginalDependencyFullPath = Path.GetFullPath(JobSpecification.RequiredDependencies[i]);
String CachedDependencyName;
ReturnValue = CacheFileAndConvertName(OriginalDependencyFullPath, out CachedDependencyName, bUse64bitNamingConvention);
if (ReturnValue < 0)
{
// Failed to cache the dependency, return failure
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CacheAllFiles] Failed to cache the required dependency: " + OriginalDependencyFullPath);
return ReturnValue;
}
JobSpecification.RequiredDependencies[i] = CachedDependencyName;
String OriginalDependencyName = Path.GetFileName(OriginalDependencyFullPath);
DependenciesOriginalNames.Add(CachedDependencyName, OriginalDependencyName);
}
}
// Check and cache any optional dependencies
if ((JobSpecification.OptionalDependencies != null) &&
(JobSpecification.OptionalDependencies.Count > 0))
{
for (Int32 i = 0; i < JobSpecification.OptionalDependencies.Count; i++)
{
String OriginalDependencyFullPath = JobSpecification.OptionalDependencies[i];
String CachedDependencyName;
ReturnValue = CacheFileAndConvertName(OriginalDependencyFullPath, out CachedDependencyName, bUse64bitNamingConvention);
if (ReturnValue < 0)
{
// Failed to cache the dependency, log a warning
Log(EVerbosityLevel.Verbose, ELogColour.Orange, "[Interface:CacheAllFiles] Failed to cache an optional dependency: " + OriginalDependencyFullPath);
}
else
{
JobSpecification.OptionalDependencies[i] = CachedDependencyName;
String OriginalDependencyName = Path.GetFileName(OriginalDependencyFullPath);
DependenciesOriginalNames.Add(CachedDependencyName, OriginalDependencyName);
}
}
}
// Set the newly created name mapping dictionary
JobSpecification.DependenciesOriginalNames = DependenciesOriginalNames;
ReturnValue = Constants.SUCCESS;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CacheAllFiles] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
return ReturnValue;
}
Int32 CacheAllFiles(AgentTaskSpecification TaskSpecification)
{
Int32 ReturnValue = Constants.INVALID;
try
{
TaskSpecification.DependenciesOriginalNames = null;
if ((TaskSpecification.Dependencies != null) &&
(TaskSpecification.Dependencies.Count > 0))
{
// Allocate the dictionary we'll use for the name mapping
Dictionary<String, String> DependenciesOriginalNames = new Dictionary<String, String>();
// Check and cache all the dependencies
for (Int32 i = 0; i < TaskSpecification.Dependencies.Count; i++)
{
String OriginalDependencyFullPath = TaskSpecification.Dependencies[i];
String CachedDependencyName;
ReturnValue = CacheFileAndConvertName(OriginalDependencyFullPath, out CachedDependencyName, false);
if (ReturnValue < 0)
{
// Failed to cache the dependency, return failure
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CacheAllFiles] Failed to cache the dependency: " + OriginalDependencyFullPath);
return ReturnValue;
}
TaskSpecification.Dependencies[i] = CachedDependencyName;
String OriginalDependencyName = Path.GetFileName(OriginalDependencyFullPath);
DependenciesOriginalNames.Add(CachedDependencyName, OriginalDependencyName);
}
// Set the newly created name mapping dictionary
TaskSpecification.DependenciesOriginalNames = DependenciesOriginalNames;
}
ReturnValue = Constants.SUCCESS;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:CacheAllFiles] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CONNECTION_DISCONNECTED;
CleanupClosedConnection();
}
return ReturnValue;
}
/*
* Flushes any buffered writes to the channel
*/
Int32 FlushChannel(ChannelInfo ChannelToFlush)
{
Int32 ReturnValue = Constants.INVALID;
if ((ChannelToFlush.ChannelFlags & EChannelFlags.ACCESS_WRITE) != 0 &&
(ChannelToFlush.ChannelOffset > 0) &&
(ChannelToFlush.ChannelData != null) &&
(ChannelToFlush.ChannelFileStream != null))
{
try
{
ChannelToFlush.ChannelFileStream.Write(ChannelToFlush.ChannelData, 0, ChannelToFlush.ChannelOffset);
ReturnValue = ChannelToFlush.ChannelOffset;
}
catch (Exception Ex)
{
Log(EVerbosityLevel.Critical, ELogColour.Red, "[Interface:FlushChannel] Error: " + Ex.Message);
ReturnValue = Constants.ERROR_CHANNEL_IO_FAILED;
}
ChannelToFlush.ChannelOffset = 0;
}
return ReturnValue;
}
private void EditorLog(EVerbosityLevel Verbosity, string Message)
{
if(SwarmInterfaceLogCppProc != null)
{
SwarmInterfaceLogCppProc(Verbosity, FStringMarshaler.MarshalManagedToNative(Message));
}
else
{
DebugLog.Write(Message);
}
}
}
}