[#59550] ExternalControlServer: Fix deadlocking in RunFor
diff --git a/src/Renode/Network/ExternalControl/RunFor.cs b/src/Renode/Network/ExternalControl/RunFor.cs
index 8e80afc..c10a7fe 100644
--- a/src/Renode/Network/ExternalControl/RunFor.cs
+++ b/src/Renode/Network/ExternalControl/RunFor.cs
@@ -6,36 +6,102 @@
 //
 using System;
 using System.Collections.Generic;
+using System.Threading;
 using Antmicro.Renode.Core;
 using Antmicro.Renode.Logging;
 using Antmicro.Renode.Time;
 
 namespace Antmicro.Renode.Network.ExternalControl
 {
-    public class RunFor : BaseCommand
+    public class RunFor : BaseCommand, IDisposable
     {
         public RunFor(IEmulationElement parent)
             : base(parent)
         {
         }
 
+        public void Dispose()
+        {
+            if(cancellationToken != null)
+            {
+                parent.Log(LogLevel.Warning, "RunFor disposed while running");
+            }
+            cancellationToken?.Cancel();
+            cancellationToken = null;
+            disposed = true;
+        }
+
         public override Response Invoke(List<byte> data)
         {
+            if(disposed)
+            {
+                return Response.CommandFailed(Identifier, "Command is unavailable");
+            }
+
             if(data.Count != 8)
             {
-                return Response.CommandFailed(Command.RunFor, "Expected 8 bytes of payload");
+                return Response.CommandFailed(Identifier, "Expected 8 bytes of payload");
             }
 
+            if(cancellationToken != null)
+            {
+                return Response.CommandFailed(Identifier, "One RunFor command can be running at any given time");
+            }
+
+            cancellationToken = new CancellationTokenSource();
+            exception = null;
+            success = false;
+
             var microseconds = BitConverter.ToUInt64(data.ToArray(), 0);
             var interval = TimeInterval.FromMicroseconds(microseconds);
 
-            parent.Log(LogLevel.Info, "Executing RunFor({0}) command", interval);
-            EmulationManager.Instance.CurrentEmulation.RunFor(interval);
+            var thread = new Thread(() =>
+            {
+                try
+                {
+                    parent.Log(LogLevel.Info, "Executing RunFor({0}) command", interval);
+                    EmulationManager.Instance.CurrentEmulation.RunFor(interval);
 
-            return Response.Success(Command.RunFor);
+                    if(cancellationToken.IsCancellationRequested)
+                    {
+                        return;
+                    }
+
+                    success = true;
+                }
+                catch(Exception e)
+                {
+                    exception = e;
+                }
+                cancellationToken?.Cancel();
+            })
+            {
+                IsBackground = true,
+                Name = GetType().Name
+            };
+
+            thread.Start();
+            cancellationToken.Token.WaitHandle.WaitOne();
+            cancellationToken = null;
+
+            if(exception != null)
+            {
+                throw exception;
+            }
+
+            if(success)
+            {
+                return Response.Success(Identifier);
+            }
+            return Response.CommandFailed(Identifier, "RunFor was interrupted");
         }
 
         public override Command Identifier => Command.RunFor;
         public override byte Version => 0x0;
+
+        private bool success;
+        private Exception exception;
+        private CancellationTokenSource cancellationToken;
+        private bool disposed;
     }
 }