Browse Source

Fix ansi multi-thread requests handling in the NetDriver.

BDisp 9 months ago
parent
commit
1ecff5e5da

+ 11 - 3
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs

@@ -8,6 +8,8 @@ namespace Terminal.Gui;
 /// </summary>
 /// </summary>
 public class AnsiEscapeSequenceRequest
 public class AnsiEscapeSequenceRequest
 {
 {
+    internal readonly object _responseLock = new (); // Per-instance lock
+
     /// <summary>
     /// <summary>
     ///     Request to send e.g. see
     ///     Request to send e.g. see
     ///     <see>
     ///     <see>
@@ -89,12 +91,14 @@ public class AnsiEscapeSequenceRequest
 
 
             if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1]))
             if (!ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1]))
             {
             {
-                throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'");
+                char resp = string.IsNullOrEmpty (ansiRequest.Response) ? ' ' : ansiRequest.Response.Last ();
+
+                throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{ansiRequest.Terminator [^1]}'");
             }
             }
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
-            error.AppendLine ($"Error executing ANSI request: {ex.Message}");
+            error.AppendLine ($"Error executing ANSI request:\n{ansiRequest.Response}\n{ex.Message}");
         }
         }
         finally
         finally
         {
         {
@@ -105,7 +109,7 @@ public class AnsiEscapeSequenceRequest
 
 
             if (savedIsReportingMouseMoves)
             if (savedIsReportingMouseMoves)
             {
             {
-                driver.StartReportingMouseMoves ();
+                driver?.StartReportingMouseMoves ();
             }
             }
         }
         }
 
 
@@ -132,4 +136,8 @@ public class AnsiEscapeSequenceRequest
     ///     different value.
     ///     different value.
     /// </summary>
     /// </summary>
     public string? Value { get; init; }
     public string? Value { get; init; }
+
+    internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string response) { ResponseFromInput?.Invoke (ansiRequest, response); }
+
+    internal event EventHandler<string>? ResponseFromInput;
 }
 }

+ 6 - 1
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs

@@ -77,7 +77,12 @@ public class EscSeqRequests
     {
     {
         lock (Statuses)
         lock (Statuses)
         {
         {
-            Statuses.Dequeue ();
+            Statuses.TryDequeue (out var request);
+
+            if (request != seqReqStatus)
+            {
+                throw new InvalidOperationException ("Both EscSeqReqStatus objects aren't equals.");
+            }
         }
         }
     }
     }
 
 

+ 56 - 19
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -2,6 +2,7 @@
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
 //
 
 
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
@@ -275,17 +276,29 @@ internal class NetEvents : IDisposable
                         }
                         }
 
 
                         _isEscSeq = true;
                         _isEscSeq = true;
-                        newConsoleKeyInfo = consoleKeyInfo;
-                        _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
 
 
-                        if (Console.KeyAvailable)
+                        if (consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
                         {
                         {
-                            continue;
+                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                            _cki = null;
+                            _isEscSeq = false;
+
+                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
                         }
                         }
+                        else
+                        {
+                            newConsoleKeyInfo = consoleKeyInfo;
+                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
 
 
-                        ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
-                        _cki = null;
-                        _isEscSeq = false;
+                            if (Console.KeyAvailable)
+                            {
+                                continue;
+                            }
+
+                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                            _cki = null;
+                            _isEscSeq = false;
+                        }
 
 
                         break;
                         break;
                     }
                     }
@@ -454,9 +467,11 @@ internal class NetEvents : IDisposable
                 sb.Append (keyChar.KeyChar);
                 sb.Append (keyChar.KeyChar);
             }
             }
 
 
-            seqReqStatus.AnsiRequest.Response = sb.ToString ();
-
-            ((NetDriver)_consoleDriver)._waitAnsiResponse.Set ();
+            lock (seqReqStatus.AnsiRequest._responseLock)
+            {
+                seqReqStatus.AnsiRequest.Response = sb.ToString ();
+                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, sb.ToString ());
+            }
 
 
             return;
             return;
         }
         }
@@ -1424,22 +1439,41 @@ internal class NetDriver : ConsoleDriver
         }
         }
     }
     }
 
 
-    internal ManualResetEventSlim _waitAnsiResponse = new (false);
+    private readonly ManualResetEventSlim _waitAnsiResponse = new (false);
     private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
     private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
     public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
     public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
     {
     {
-        _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest);
+        var response = string.Empty;
 
 
         try
         try
         {
         {
+            lock (ansiRequest._responseLock)
+            {
+                ansiRequest.ResponseFromInput += (s, e) =>
+                                                 {
+                                                     Debug.Assert (s == ansiRequest);
+
+                                                     ansiRequest.Response = response = e;
+
+                                                     _waitAnsiResponse.Set ();
+                                                 };
+
+                _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest);
+            }
+
             if (!_ansiResponseTokenSource.IsCancellationRequested && Console.KeyAvailable)
             if (!_ansiResponseTokenSource.IsCancellationRequested && Console.KeyAvailable)
             {
             {
                 _mainLoopDriver._netEvents._forceRead = true;
                 _mainLoopDriver._netEvents._forceRead = true;
 
 
                 _mainLoopDriver._netEvents._waitForStart.Set ();
                 _mainLoopDriver._netEvents._waitForStart.Set ();
 
 
+                if (!_mainLoopDriver._waitForProbe.IsSet)
+                {
+                    _mainLoopDriver._waitForProbe.Set ();
+                }
+
                 _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
                 _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
             }
             }
         }
         }
@@ -1454,17 +1488,20 @@ internal class NetDriver : ConsoleDriver
                 _mainLoopDriver._netEvents._forceRead = false;
                 _mainLoopDriver._netEvents._forceRead = false;
             }
             }
 
 
-            if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0
-                && string.IsNullOrEmpty (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Peek ().AnsiRequest.Response))
+            if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request))
             {
             {
-                // Bad request or no response at all
-                _mainLoopDriver._netEvents.EscSeqRequests.Statuses.Dequeue ();
+                if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0
+                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
+                {
+                    // Bad request or no response at all
+                    _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _);
+                }
             }
             }
 
 
             _waitAnsiResponse.Reset ();
             _waitAnsiResponse.Reset ();
         }
         }
 
 
-        return ansiRequest.Response;
+        return response;
     }
     }
 
 
     private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me)
     private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me)
@@ -1756,7 +1793,7 @@ internal class NetMainLoop : IMainLoopDriver
     private readonly ManualResetEventSlim _eventReady = new (false);
     private readonly ManualResetEventSlim _eventReady = new (false);
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
     private readonly Queue<InputResult?> _resultQueue = new ();
     private readonly Queue<InputResult?> _resultQueue = new ();
-    private readonly ManualResetEventSlim _waitForProbe = new (false);
+    internal readonly ManualResetEventSlim _waitForProbe = new (false);
     private readonly CancellationTokenSource _eventReadyTokenSource = new ();
     private readonly CancellationTokenSource _eventReadyTokenSource = new ();
     private MainLoop _mainLoop;
     private MainLoop _mainLoop;
 
 
@@ -1856,7 +1893,7 @@ internal class NetMainLoop : IMainLoopDriver
         {
         {
             try
             try
             {
             {
-                if (!_inputHandlerTokenSource.IsCancellationRequested)
+                if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested)
                 {
                 {
                     _waitForProbe.Wait (_inputHandlerTokenSource.Token);
                     _waitForProbe.Wait (_inputHandlerTokenSource.Token);
                 }
                 }