|
@@ -5,7 +5,7 @@ namespace Terminal.Gui;
|
|
|
|
|
|
public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
{
|
|
|
- private readonly List<AnsiEscapeSequenceRequest> _requests = new ();
|
|
|
+ private readonly List<Tuple<AnsiEscapeSequenceRequest,DateTime>> _requests = new ();
|
|
|
|
|
|
/// <summary>
|
|
|
///<para>
|
|
@@ -24,6 +24,13 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
private TimeSpan _throttle = TimeSpan.FromMilliseconds (100);
|
|
|
private TimeSpan _runScheduleThrottle = TimeSpan.FromMilliseconds (100);
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// If console has not responded to a request after this period of time, we assume that it is never going
|
|
|
+ /// to respond. Only affects when we try to send a new request with the same terminator - at which point
|
|
|
+ /// we tell the parser to stop expecting the old request and start expecting the new request.
|
|
|
+ /// </summary>
|
|
|
+ private TimeSpan _staleTimeout = TimeSpan.FromSeconds (5);
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Sends the <paramref name="request"/> immediately or queues it if there is already
|
|
|
/// an outstanding request for the given <see cref="AnsiEscapeSequenceRequest.Terminator"/>.
|
|
@@ -32,17 +39,51 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
/// <returns><see langword="true"/> if request was sent immediately. <see langword="false"/> if it was queued.</returns>
|
|
|
public bool SendOrSchedule (AnsiEscapeSequenceRequest request )
|
|
|
{
|
|
|
- if (CanSend(request))
|
|
|
+
|
|
|
+ if (CanSend(request, out var reason))
|
|
|
{
|
|
|
Send (request);
|
|
|
-
|
|
|
return true;
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ if (reason == ReasonCannotSend.OutstandingRequest)
|
|
|
{
|
|
|
- _requests.Add (request);
|
|
|
- return false;
|
|
|
+ EvictStaleRequests (request.Terminator);
|
|
|
+
|
|
|
+ // Try again after
|
|
|
+ if (CanSend (request, out _))
|
|
|
+ {
|
|
|
+ Send (request);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ _requests.Add (Tuple.Create(request,DateTime.Now));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Looks to see if the last time we sent <paramref name="withTerminator"/>
|
|
|
+ /// is a long time ago. If so we assume that we will never get a response and
|
|
|
+ /// can proceed with a new request for this terminator (returning <see langword="true"/>).
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="withTerminator"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private bool EvictStaleRequests (string withTerminator)
|
|
|
+ {
|
|
|
+ if (_lastSend.TryGetValue (withTerminator, out var dt))
|
|
|
+ {
|
|
|
+ // TODO: If debugging this can cause problem becuase we stop expecting response but one comes in anyway
|
|
|
+ // causing parser to ignore and it to fall through to default console iteration which typically crashes.
|
|
|
+ if (DateTime.Now - dt > _staleTimeout)
|
|
|
+ {
|
|
|
+ parser.StopExpecting (withTerminator);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
private DateTime _lastRun = DateTime.Now;
|
|
@@ -62,12 +103,12 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- var opportunity = _requests.FirstOrDefault (CanSend);
|
|
|
+ var opportunity = _requests.FirstOrDefault (r=>CanSend(r.Item1, out _));
|
|
|
|
|
|
if (opportunity != null)
|
|
|
{
|
|
|
_requests.Remove (opportunity);
|
|
|
- Send (opportunity);
|
|
|
+ Send (opportunity.Item1);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
@@ -82,14 +123,22 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
r.Send ();
|
|
|
}
|
|
|
|
|
|
- public bool CanSend (AnsiEscapeSequenceRequest r)
|
|
|
+ private bool CanSend (AnsiEscapeSequenceRequest r, out ReasonCannotSend reason)
|
|
|
{
|
|
|
if (ShouldThrottle (r))
|
|
|
{
|
|
|
+ reason = ReasonCannotSend.TooManyRequests;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parser.IsExpecting (r.Terminator))
|
|
|
+ {
|
|
|
+ reason = ReasonCannotSend.OutstandingRequest;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- return !parser.IsExpecting (r.Terminator);
|
|
|
+ reason = default;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
private bool ShouldThrottle (AnsiEscapeSequenceRequest r)
|
|
@@ -102,3 +151,22 @@ public class AnsiRequestScheduler(IAnsiResponseParser parser)
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+internal enum ReasonCannotSend
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// No reason given.
|
|
|
+ /// </summary>
|
|
|
+ None = 0,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The parser is already waiting for a request to complete with the given terminator.
|
|
|
+ /// </summary>
|
|
|
+ OutstandingRequest,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// There have been too many requests sent recently, new requests will be put into
|
|
|
+ /// queue to prevent console becoming unresponsive.
|
|
|
+ /// </summary>
|
|
|
+ TooManyRequests
|
|
|
+}
|