Changed pipe names to end with ".pipe".

Added timeouts to closing.
This commit is contained in:
Harrison Deng 2021-04-10 13:57:38 -05:00
parent 526e657f59
commit 78e9e185c2
5 changed files with 238 additions and 218 deletions

View File

@ -18,27 +18,28 @@ namespace GameServiceWarden.Core.Games
{
public class ServiceDescriptor //entity
{
private const string DISTRIBUTOR_SUFFIX = "_dist";
private const int INIT_TIMEOUT = 1000;
private const string LOG_DISTRIBUTOR_PREFIX = "log_dist_";
private const int TIMEOUT = 1000;
/// <summary>
/// The name of the service itself, independent of the name of the module this service is using.
/// </summary>
public string ServiceName { get { return serviceName; } }
private readonly string serviceName;
private bool running;
private readonly Guid runningUID;
private volatile bool running;
private readonly IService service;
/// <summary>
/// The services log output pipe name.
/// </summary>
public string ServiceLogPipeName { get { return (serviceName); } }
public string ServiceLogPipeName { get { return (runningUID.ToString() + ".pipe"); } }
private string moduleName;
private readonly string assemblyName;
private NamedPipeServerStream logReceiver;
private NamedPipeClientStream logSender;
private volatile NamedPipeServerStream logReceiver;
private volatile NamedPipeClientStream logSender;
private ConcurrentStack<NamedPipeServerStream> logStreamListeners;
private Task logUpdateTask;
private Task acceptingTask;
private CancellationTokenSource stopToken;
private volatile CancellationTokenSource stopToken;
/// <summary>
/// Name of module this service uses.
@ -52,6 +53,8 @@ namespace GameServiceWarden.Core.Games
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
this.serviceName = serviceName ?? throw new ArgumentNullException("serviceName");
this.service.StateChangeEvent += OnServiceStateChange;
runningUID = Guid.NewGuid();
Dictionary<string, IServiceConfigurable> tempConfigurables = new Dictionary<string, IServiceConfigurable>();
foreach (IServiceConfigurable configurable in service.Configurables)
@ -71,23 +74,25 @@ namespace GameServiceWarden.Core.Games
{
Logger.Log($"\"{ServiceName}\" is starting.");
if (running) throw new InvalidOperationException("Service instance already running.");
logReceiver = new NamedPipeServerStream(ServiceLogPipeName + DISTRIBUTOR_SUFFIX, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
logReceiver = new NamedPipeServerStream(LOG_DISTRIBUTOR_PREFIX + ServiceLogPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Task waitForConnection = logReceiver.WaitForConnectionAsync();
logSender = new NamedPipeClientStream(".", ServiceLogPipeName + DISTRIBUTOR_SUFFIX, PipeDirection.Out);
logSender = new NamedPipeClientStream(".", LOG_DISTRIBUTOR_PREFIX + ServiceLogPipeName, PipeDirection.Out);
logSender.Connect();
waitForConnection.Wait();
byte[] idToken = Guid.NewGuid().ToByteArray();
ValueTask sendTokenTask = logSender.WriteAsync(idToken);
byte[] receivedToken = new byte[idToken.Length];
logReceiver.Read(receivedToken);
if (!sendTokenTask.AsTask().Wait(500) || !sendTokenTask.IsCompletedSuccessfully) {
if (!sendTokenTask.AsTask().Wait(500) || !sendTokenTask.IsCompletedSuccessfully)
{
throw new ServiceInitializationException("Error while sending identification token.");
}
if (!idToken.SequenceEqual(receivedToken)) {
if (!idToken.SequenceEqual(receivedToken))
{
throw new ServiceInitializationException("Wrong distributor identification token.");
}
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(INIT_TIMEOUT);
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TIMEOUT);
Task initializationTask = Task.Run(() => service.InitializeService(logSender), cancellationTokenSource.Token);
initializationTask.Wait();
cancellationTokenSource.Dispose();
@ -102,18 +107,27 @@ namespace GameServiceWarden.Core.Games
/// <exception cref="InvalidOperationException">Is thrown when the is not running.</exception>
public void Stop()
{
Logger.Log("\"{ServiceName}\" is stopping.");
if (!running) throw new InvalidOperationException("Service instance not running.");
Logger.Log($"\"{ServiceName}\" is stopping.");
service.ElegantShutdown();
logSender.Close();
logReceiver.Close();
stopToken.Cancel();
try {
acceptingTask.Wait();
} catch (AggregateException e) {
try
{
if (!acceptingTask.Wait(TIMEOUT)) {
throw new TimeoutException($"Could not stop \"{ServiceName}\" accepting task within {TIMEOUT}ms.");
}
}
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException);
}
try
{
logUpdateTask.Wait();
if (!logUpdateTask.Wait(TIMEOUT)) {
throw new TimeoutException($"Could not stop \"{ServiceName}\" broadcast within{TIMEOUT}ms.");
}
}
catch (AggregateException e)
{
@ -163,11 +177,13 @@ namespace GameServiceWarden.Core.Games
return running;
}
public string GetServiceName() {
public string GetServiceName()
{
return serviceName;
}
public string GetModuleName() {
public string GetModuleName()
{
return moduleName;
}
@ -180,7 +196,7 @@ namespace GameServiceWarden.Core.Games
private void OnServiceStateChange(object sender, bool running)
{
this.running = running;
Logger.Log($"{ServiceName}'s state changed to {(running ? "running" : "stopped")}.", LogLevel.DEBUG);
Logger.Log($"The service \"{ServiceName}\" is changing states to {(running ? "running" : "stopped")}.", LogLevel.DEBUG);
ServiceStateChangeEvent?.Invoke(this, running);
}
@ -197,22 +213,26 @@ namespace GameServiceWarden.Core.Games
Logger.Log($"\"{ServiceName}\" stopped accepting log listeners.");
}
private async Task BroadcastLog() {
private async Task BroadcastLog()
{
Stack<NamedPipeServerStream> completeStack = new Stack<NamedPipeServerStream>();
Stack<(Task, CancellationTokenSource)> writeTasks = new Stack<(Task, CancellationTokenSource)>();
byte[] buffer = new byte[1024 * 8];
int fill;
Logger.Log($"\"{ServiceName}\" is now listening to the service log and broadcasting.");
while ((fill = await logReceiver.ReadAsync(buffer, 0, buffer.Length, stopToken.Token)) > 0)
while (running && (fill = await logReceiver.ReadAsync(buffer, 0, buffer.Length, stopToken.Token)) > 0)
{
Logger.Log($"Broadcasting {fill} bytes.", LogLevel.DEBUG);
NamedPipeServerStream pipe;
while (logStreamListeners.TryPop(out pipe))
{
if (!pipe.IsConnected) {
if (!pipe.IsConnected)
{
pipe.Dispose();
Logger.Log($"\"{ServiceName}\" detected a disconnected log listener. Removing from list of listener(s).", LogLevel.DEBUG);
} else {
}
else
{
CancellationTokenSource cancelToken = new CancellationTokenSource(1000);
writeTasks.Push((pipe.WriteAsync(buffer, 0, fill, cancelToken.Token), cancelToken));
completeStack.Push(pipe);
@ -224,7 +244,8 @@ namespace GameServiceWarden.Core.Games
logStreamListeners.Push(completePipe);
}
(Task, CancellationTokenSource) taskAndCancel;
while (writeTasks.TryPop(out taskAndCancel)) {
while (writeTasks.TryPop(out taskAndCancel))
{
await taskAndCancel.Item1;
taskAndCancel.Item2.Dispose();
}

View File

@ -204,7 +204,6 @@ namespace GameServiceWarden.Core.Games
{
switch (action.action)
{
//TODO FINISH MOVING THIS!!!!
case ServiceManagerAction.Type.View:
GetServiceNames();
GetRunningServiceNames();

View File

@ -15,7 +15,8 @@ namespace GameServiceWarden.Core.UI
{
public class IPCMediator
{
private const int CONNECT_TIMEOUT = 1000;
private const int TIMEOUT = 1000;
public string PipeName {get { return name + ".pipe"; } }
private readonly string name;
private readonly ConcurrentDictionary<string, (NamedPipeServerStream, Task)> pipes;
public BlockingCollection<(string, CommunicableType, byte[])> RequestQueue { get; private set; }
@ -48,7 +49,7 @@ namespace GameServiceWarden.Core.UI
stopAcceptingToken.Cancel();
try
{
acceptTask.Wait();
if (!acceptTask.Wait(TIMEOUT)) throw new TimeoutException($"IPCMediator \"{name}\" was unable to stop accepting task within {TIMEOUT}ms.");
}
catch (AggregateException e)
{
@ -109,7 +110,7 @@ namespace GameServiceWarden.Core.UI
Logger.Log("Accepting Interface connections.");
while (active)
{
NamedPipeServerStream pipe = new NamedPipeServerStream(name, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Logger.Log("Waiting for connection.", LogLevel.DEBUG);
await pipe.WaitForConnectionAsync(stopAcceptingToken.Token);
connectionTasks.Add(OnConnection(pipe));
@ -135,7 +136,7 @@ namespace GameServiceWarden.Core.UI
Logger.Log("Interface attempting to connect.", LogLevel.DEBUG);
byte[] headerBuffer = new byte[sizeof(uint) * 2];
int headerFill = 0;
CancellationTokenSource headerCancel = new CancellationTokenSource(CONNECT_TIMEOUT);
CancellationTokenSource headerCancel = new CancellationTokenSource(TIMEOUT);
try
{
int readLength;
@ -148,7 +149,7 @@ namespace GameServiceWarden.Core.UI
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException);
Logger.Log($"Interface did not send header data within {CONNECT_TIMEOUT}ms.", LogLevel.DEBUG);
Logger.Log($"Interface did not send header data within {TIMEOUT}ms.", LogLevel.DEBUG);
} finally {
await pipe.DisposeAsync();
headerCancel.Dispose();
@ -164,7 +165,7 @@ namespace GameServiceWarden.Core.UI
byte[] bodyBuffer = new byte[bodyLength];
int bodyFill = 0;
CancellationTokenSource bodyCancel = new CancellationTokenSource(CONNECT_TIMEOUT);
CancellationTokenSource bodyCancel = new CancellationTokenSource(TIMEOUT);
try
{
int readLength = 0;
@ -177,7 +178,7 @@ namespace GameServiceWarden.Core.UI
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException);
Logger.Log($"Interface failed to send body data within {CONNECT_TIMEOUT}.", LogLevel.DEBUG);
Logger.Log($"Interface failed to send body data within {TIMEOUT}.", LogLevel.DEBUG);
} finally {
await pipe.DisposeAsync();
bodyCancel.Dispose();
@ -204,7 +205,7 @@ namespace GameServiceWarden.Core.UI
requestAccepted = true;
response.identifier = request.requestedIdentifier;
}
CancellationTokenSource cancelResponse = new CancellationTokenSource(CONNECT_TIMEOUT);
CancellationTokenSource cancelResponse = new CancellationTokenSource(TIMEOUT);
try
{
await pipe.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(response), cancelResponse.Token);
@ -212,7 +213,7 @@ namespace GameServiceWarden.Core.UI
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException);
Logger.Log($"Interface did not receive response within {CONNECT_TIMEOUT}ms.", LogLevel.DEBUG);
Logger.Log($"Interface did not receive response within {TIMEOUT}ms.", LogLevel.DEBUG);
}
if (!requestAccepted) {
cancelResponse.Dispose();

View File

@ -5,18 +5,18 @@
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-5" value="ServiceDescriptor" style="swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;" parent="1" vertex="1">
<mxGeometry x="695" y="1100" width="510" height="410" as="geometry">
<mxGeometry x="695" y="1100" width="510" height="420" as="geometry">
<mxRectangle x="762" y="1030" width="130" height="26" as="alternateBounds"/>
</mxGeometry>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-6" value="+ ServiceName: string property&#10;- serviceName: string&#10;- running: bool&#10;- service: IService&#10;- ServiceLogPipeName: string property&#10;- moduleName: string&#10;- assemblyName: string&#10;- logStreamListeners: ConcurrentStack&lt;NamedPipeServerStream&gt;&#10;- logUpdateTask: Task&#10;- acceptingTask: Task&#10;- stopToken: CancellationTokenSource&#10;- configurables: IReadOnlyDictionary&lt;string, IServiceConfigurable&gt;&#10;+ ServiceStateChangeEvent: event EventHandler&lt;bool&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
<mxGeometry y="26" width="510" height="194" as="geometry"/>
<mxCell id="dmd0HlDYcxYugIlahWj0-6" value="+ ServiceName: string property&#10;- serviceName: string&#10;- runningUID: Guid&#10;- running: bool&#10;- service: IService&#10;- ServiceLogPipeName: string property&#10;- moduleName: string&#10;- assemblyName: string&#10;- logStreamListeners: ConcurrentStack&lt;NamedPipeServerStream&gt;&#10;- logUpdateTask: Task&#10;- acceptingTask: Task&#10;- stopToken: CancellationTokenSource&#10;- configurables: IReadOnlyDictionary&lt;string, IServiceConfigurable&gt;&#10;+ ServiceStateChangeEvent: event EventHandler&lt;bool&gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
<mxGeometry y="26" width="510" height="204" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-7" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
<mxGeometry y="220" width="510" height="8" as="geometry"/>
<mxGeometry y="230" width="510" height="8" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-8" value="+ Start(): void&#10;+ Stop(): void&#10;+ ExecuteCommand(command: string): void&#10;+ GetConfigurableOptions(): ISet&lt;string&gt;&#10;+ SetConfigurableValue(configurationName: string, value: string): bool&#10;+ GetConfigurableValue(configurationName: string): string&#10;+ GetServiceState(): bool&#10;+ GetModuleName(): string &#10;+ GetassemblyName(): string&#10;- OnServiceStateChange(sender: object, running: bool): void&#10;- AcceptLogConnections(): Task&#10;- BroadcastLog(): Task" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="dmd0HlDYcxYugIlahWj0-5" vertex="1">
<mxGeometry y="228" width="510" height="182" as="geometry"/>
<mxGeometry y="238" width="510" height="182" as="geometry"/>
</mxCell>
<mxCell id="dmd0HlDYcxYugIlahWj0-15" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="1" source="dmd0HlDYcxYugIlahWj0-11" target="dmd0HlDYcxYugIlahWj0-5" edge="1">
<mxGeometry relative="1" as="geometry"/>
@ -193,348 +193,348 @@
</mxGraphModel>
</diagram>
<diagram id="gj0qHRc3eh050ABAey3g" name="Data-Flow">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGraphModel dx="1024" dy="592" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<root>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-1" parent="jVG6p58vlRYGO9X4wXeX-0"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-21" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-2" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-2" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="10" y="300" width="30" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-12" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-22" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=0.75;entryY=0.1;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-2" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-0" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="jVG6p58vlRYGO9X4wXeX-4" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-3" target="28FAlPysTx9DMYvLwa-2-7" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-3" value="Console View" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="80" y="300" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-13" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-2" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-4" target="jVG6p58vlRYGO9X4wXeX-5" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-4" value="string command (request)" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="260" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-18" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-5" target="jVG6p58vlRYGO9X4wXeX-9" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-5" value="MainController" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-6" value="http://www.plainionist.net/Implementing-Clean-Architecture-Controller-Presenter/" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry y="840" width="480" height="20" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-7" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;ICommand" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="372.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-10" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="jVG6p58vlRYGO9X4wXeX-7" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="809.9999999999998" y="512.5000000000002" as="sourcePoint"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-3" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="jVG6p58vlRYGO9X4wXeX-9" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="jVG6p58vlRYGO9X4wXeX-9" value="ServiceController" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="575" y="482.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-4" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-5" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;strokeColor=#f0f0f0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-2" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1.008;entryY=0.625;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<Array as="points">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="580"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</Array>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-16" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="345FJoVc2gbAayMsQlD7-7" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<Array as="points">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="338"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxPoint x="960" y="83"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</Array>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxGeometry>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-1" value="ServiceManager" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="307.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-9" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-12" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;strokeColor=#f0f0f0;dashed=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-11" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-13" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-3" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-5" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-5" value="ServicePresenter" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="575" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-8" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;fillColor=#1ba1e2;strokeColor=#006EAF;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="jVG6p58vlRYGO9X4wXeX-3" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-5" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-7" target="tM_Gde3HH8YiZ2frBV5J-0" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="28FAlPysTx9DMYvLwa-2-7" value="String Output" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="260" y="122.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-1" value="ServiceAction &amp;lt;DS&amp;gt;" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="542.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-6" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-2" target="UY-EM7-1ECCvWtENr50b-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-20" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-1" target="28FAlPysTx9DMYvLwa-2-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-2" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServiceManipulator" style="whiteSpace=wrap;html=1;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="432.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-22" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-10" target="28FAlPysTx9DMYvLwa-2-5" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-10" value="ServicesResult &amp;lt;DS&amp;gt;" style="whiteSpace=wrap;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="67.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-14" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#f0f0f0;dashed=1;" parent="jVG6p58vlRYGO9X4wXeX-1" source="UY-EM7-1ECCvWtENr50b-11" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-21" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;endArrow=open;endFill=0;strokeColor=#006EAF;fillColor=#1ba1e2;" parent="jVG6p58vlRYGO9X4wXeX-1" source="28FAlPysTx9DMYvLwa-2-1" target="UY-EM7-1ECCvWtENr50b-10" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-11" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IServicesMonitor" style="whiteSpace=wrap;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="800" y="167.5" width="120" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-70" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="220" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-71" value="" style="line;strokeWidth=2;direction=south;html=1;fillColor=none;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="760" y="50" width="10" height="570" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="UY-EM7-1ECCvWtENr50b-73" value="Page 191 (Chapter 22) of Clean Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry y="870" width="480" height="20" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-2" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=block;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-4" value="Use" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;dashed=1;endArrow=open;endFill=0;" parent="jVG6p58vlRYGO9X4wXeX-1" source="tM_Gde3HH8YiZ2frBV5J-0" target="tM_Gde3HH8YiZ2frBV5J-1" edge="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry relative="1" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-0" value="MainPresenter" style="html=1;dashed=0;whitespace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="122.5" width="110" height="60" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxCell id="tM_Gde3HH8YiZ2frBV5J-1" value="&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt;&lt;br&gt;IConsoleOutput" style="html=1;dashed=0;whitespace=wrap;" parent="jVG6p58vlRYGO9X4wXeX-1" vertex="1">
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
<mxGeometry x="420" y="20" width="110" height="50" as="geometry"/>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxCell>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</root>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</mxGraphModel>
&#xa; &#xa; &#xa;&#xa;&#xa;
&#xa; &#xa; &#xa; &#xa; &#xa;&#xa;&#xa;&#xa;&#xa;
</diagram>
</mxfile>

View File

@ -302,15 +302,14 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{
using (StreamReader reader = new StreamReader(clientStreams[i]))
{
CancellationTokenSource cancelToken = new CancellationTokenSource(15000);
string message = null;
Task task = Task.Run(() => message = reader.ReadLine(), cancelToken.Token);
Assert.True(task.Wait(10000));
Task clientTask = Task.Run(() => message = reader.ReadLine());
Assert.True(clientTask.Wait(1000));
Assert.True(COMMAND.Equals(message), $"Received message \"{message}\" when expecting \"{COMMAND}\"");
cancelToken.Dispose();
}
}
serviceManager.StopService(FAKE_SERVICE_NAME);
Task task = Task.Run(() => serviceManager.StopService(FAKE_SERVICE_NAME));
Assert.True(task.Wait(5000)); //TODO FIX WHY THIS IS HAPPENING!!!!!
}
[Fact]