From f13fe6c0a9df65ff5f5100964613e74a01b41239 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Thu, 23 May 2024 21:23:08 -0700 Subject: [PATCH 1/5] DebuggingServer refactoring --- .../Connections/SerialConnection.cs | 29 +- .../Debugging/DebuggingServer.ActiveClient.cs | 209 +++++++++++++ .../Meadow.Hcom/Debugging/DebuggingServer.cs | 280 ++---------------- 3 files changed, 244 insertions(+), 274 deletions(-) create mode 100644 Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs index 9a607bd3..94a724b9 100755 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -1,6 +1,5 @@ using System.Buffers; using System.IO.Ports; -using System.Net; using System.Security.Cryptography; namespace Meadow.Hcom; @@ -77,7 +76,6 @@ private bool MaintainConnection Name = "HCOM Connection Manager" }; _connectionManager.Start(); - } } } @@ -185,6 +183,8 @@ public override void Detach() // TODO: close this up } + //State = ConnectionState.Disconnected; + Close(); } @@ -1226,16 +1226,21 @@ public override async Task StartDebuggingSession(int port, ILog throw new DeviceNotFoundException(); } + var debuggingServer = new DebuggingServer(this, port, logger); + + logger?.LogDebug("Tell the Debugging Server to Start Listening"); + _ = debuggingServer.StartListening(cancellationToken); + logger?.LogDebug($"Start Debugging on port: {port}"); await Device.StartDebugging(port, logger, cancellationToken); - await WaitForMeadowAttach(cancellationToken); + // await Task.Delay(2000); - var endpoint = new IPEndPoint(IPAddress.Loopback, port); - var debuggingServer = new DebuggingServer(this, Device, endpoint, logger); + // Device = null; + // State = ConnectionState.Disconnected; + + await WaitForMeadowAttach(cancellationToken); - logger?.LogDebug("Tell the Debugging Server to Start Listening"); - await debuggingServer.StartListening(cancellationToken); return debuggingServer; } @@ -1268,14 +1273,6 @@ public override async Task SendDebuggerData(byte[] debuggerData, uint userData, EnqueueRequest(command); - var success = await WaitForResult(() => - { - if (_lastRequestConcluded != null && _lastRequestConcluded == RequestType.HCOM_MDOW_REQUEST_RTC_SET_TIME_CMD) - { - return true; - } - - return false; - }, cancellationToken); + //await WaitForConcluded(null, cancellationToken); } } \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs new file mode 100644 index 00000000..4e389f1d --- /dev/null +++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs @@ -0,0 +1,209 @@ +using System.Buffers; +using System.Collections.Concurrent; +using System.Net.Sockets; +using System.Security.Cryptography; + +namespace Meadow.Hcom; + +public partial class DebuggingServer +{ + // Embedded class + private class ActiveClient : IDisposable + { + private readonly IMeadowConnection _connection; + private readonly TcpClient _tcpClient; + private readonly NetworkStream _networkStream; + + private readonly CancellationTokenSource _cts; + private readonly Task _receiveVsDebugDataTask; + private readonly Task _receiveMeadowDebugDataTask; + private readonly ILogger? _logger; + public bool Disposed = false; + private readonly BlockingCollection _debuggerMessages = new(); + + // Constructor + internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken) + { + if (cancellationToken != null) + { + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value); + } + else + { + _cts = new CancellationTokenSource(); + } + + _logger = logger; + _connection = connection; + _tcpClient = tcpClient; + _networkStream = tcpClient.GetStream(); + _logger?.LogDebug("Starting receive task"); + + _connection.DebuggerMessageReceived += MeadowConnection_DebuggerMessageReceived; + + _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, TaskCreationOptions.LongRunning); + _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, TaskCreationOptions.LongRunning); + } + + private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e) + { + // _logger?.LogDebug("Debugger Message Received, Adding to collection"); + _debuggerMessages.Add(e); + } + + private const int RECEIVE_BUFFER_SIZE = 256; + + private async Task SendToMeadowAsync() + { + try + { + using var md5 = MD5.Create(); + // Receive from Visual Studio and send to Meadow + var receiveBuffer = ArrayPool.Shared.Rent(RECEIVE_BUFFER_SIZE); + var meadowBuffer = Array.Empty(); + + while (!_cts.IsCancellationRequested) + { + if (_networkStream != null && _networkStream.CanRead) + { + int bytesRead; + do + { + bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, _cts.Token); + + if (bytesRead == 0 || _cts.IsCancellationRequested) + { + continue; + } + + var destIndex = meadowBuffer.Length; + Array.Resize(ref meadowBuffer, destIndex + bytesRead); + Array.Copy(receiveBuffer, 0, meadowBuffer, destIndex, bytesRead); + + // Forward the RECIEVE_BUFFER_SIZE chunk to Meadow immediately + /* + _logger?.LogTrace("Received {count} bytes from VS, will forward to HCOM/Meadow. {hash}", + meadowBuffer.Length, + BitConverter.ToString(md5.ComputeHash(meadowBuffer)) + .Replace("-", string.Empty) + .ToLowerInvariant()); + */ + + await _connection.SendDebuggerData(meadowBuffer, 0, _cts.Token); + meadowBuffer = Array.Empty(); + + //output the data to the console + Debug.WriteLine($"************************** ToMeadow: {BitConverter.ToString(receiveBuffer, 0, bytesRead).Replace("-", string.Empty).ToLowerInvariant()}"); + + // Ensure we read all the data in this message before passing it along + // I'm not sure this is actually needed, the whole message should get read at once. + } while (_networkStream.DataAvailable); + } + else + { + // User probably hit stop + _logger?.LogInformation("Unable to Read Data from Visual Studio"); + _logger?.LogTrace("Unable to Read Data from Visual Studio"); + } + } + } + catch (IOException ioe) + { + // VS client probably died + _logger?.LogInformation("Visual Studio has Disconnected" + Environment.NewLine); + _logger?.LogTrace(ioe, "Visual Studio has Disconnected"); + } + catch (ObjectDisposedException ode) + { + // User probably hit stop + _logger?.LogInformation("Visual Studio has stopped debugging" + Environment.NewLine); + _logger?.LogTrace(ode, "Visual Studio has stopped debugging"); + } + catch (Exception ex) + { + _logger?.LogError($"Error receiving data from Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); + throw; + } + } + + private async Task SendToVisualStudio() + { + try + { + while (!_cts.IsCancellationRequested) + { + if (_networkStream != null && _networkStream.CanWrite) + { + while (_debuggerMessages.Count > 0) + { + var byteData = _debuggerMessages.Take(_cts.Token); + + /* + _logger?.LogTrace("Received {count} bytes from Meadow, will forward to VS", byteData.Length); + if (!_tcpClient.Connected) + { + _logger?.LogDebug("Cannot forward data, Visual Studio is not connected"); + return; + } + + */ + + await _networkStream.WriteAsync(byteData, 0, byteData.Length, _cts.Token); + _logger?.LogTrace("Forwarded {count} bytes to VS", byteData.Length); + + //output the data to the console + Debug.WriteLine($"************************** ToVisStu: {BitConverter.ToString(byteData).Replace("-", string.Empty).ToLowerInvariant()}"); + } + } + else + { + _logger?.LogInformation("Unable to Write Data from Visual Studio"); + } + } + } + catch (OperationCanceledException oce) + { + // User probably hit stop; Removed logging as User doesn't need to see this + // Keeping it as a TODO in case we find a side effect that needs logging. + _logger?.LogInformation("Operation Cancelled"); + _logger?.LogTrace(oce, "Operation Cancelled"); + } + catch (Exception ex) + { + _logger?.LogError($"Error sending data to Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); + + if (_cts.IsCancellationRequested) + { + throw; + } + } + } + + public void Dispose() + { + lock (_tcpClient) + { + if (Disposed) + { + return; + } + + _logger?.LogTrace("Disposing ActiveClient"); + _cts.Cancel(false); + _receiveVsDebugDataTask.Wait(TimeSpan.FromSeconds(10)); + _receiveMeadowDebugDataTask.Wait(TimeSpan.FromSeconds(10)); + _receiveVsDebugDataTask?.Dispose(); + _receiveMeadowDebugDataTask?.Dispose(); + _tcpClient.Dispose(); + _networkStream.Dispose(); + _cts.Dispose(); + + if (_connection != null) + { + _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived; + } + Disposed = true; + } + } + } +} \ No newline at end of file diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs index f23373bf..ce0a097e 100644 --- a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs +++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.cs @@ -1,47 +1,40 @@ -using System.Buffers; -using System.Collections.Concurrent; -using System.Net; +using System.Net; using System.Net.Sockets; -using System.Security.Cryptography; namespace Meadow.Hcom; // This TCP server directly interacts with Visual Studio debugging. // What it receives from Visual Studio it forwards to Meadow. // What it receives from Meadow it forwards to Visual Studio. -public class DebuggingServer : IDisposable +public partial class DebuggingServer : IDisposable { // VS 2019 - 4024 // VS 2017 - 4022 // VS 2015 - 4020 - public IPEndPoint LocalEndpoint { get; private set; } private readonly object _lck = new(); private CancellationTokenSource? _cancellationTokenSource; private readonly ILogger? _logger; - private readonly IMeadowDevice _meadow; private readonly IMeadowConnection _connection; private ActiveClient? _activeClient; - private int _activeClientCount = 0; private readonly TcpListener _listener; - private Task? _listenerTask; - private bool _isReady; + private readonly Task? _listenerTask; public bool Disposed; // Constructor /// /// Create a new DebuggingServer for proxying debug data between VS and Meadow /// - /// The to debug + /// The meadow connection /// The to listen for incoming debugger connections /// The to logging state information - public DebuggingServer(IMeadowConnection connection, IMeadowDevice meadow, IPEndPoint localEndpoint, ILogger? logger) + public DebuggingServer(IMeadowConnection connection, int port, ILogger? logger) { - LocalEndpoint = localEndpoint; + _logger = logger; _connection = connection; - _meadow = meadow; - _logger = logger; - _listener = new TcpListener(LocalEndpoint); + var endPoint = new IPEndPoint(IPAddress.Loopback, port); + + _listener = new TcpListener(endPoint); } /// @@ -51,23 +44,14 @@ public DebuggingServer(IMeadowConnection connection, IMeadowDevice meadow, IPEnd /// A representing the startup operation public async Task StartListening(CancellationToken cancellationToken) { - if (cancellationToken != null) - { - _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _listenerTask = Task.Factory.StartNew(StartListener, TaskCreationOptions.LongRunning); - var startTimeout = DateTime.UtcNow.AddSeconds(60); - while (DateTime.UtcNow < startTimeout) - { - if (_isReady) - { - return; - } + _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - await Task.Delay(100, cancellationToken); - } + _listener.Start(); + _logger?.LogInformation($"Listening for Visual Studio to connect"); - throw new Exception("DebuggingServer did not start listening within the 60 second timeout."); - } + // This call will wait for the client to connect, before continuing. We shouldn't need a loop. + var tcpClient = await _listener.AcceptTcpClientAsync(); + _activeClient = CreateActiveClient(tcpClient); } /// @@ -86,61 +70,29 @@ public async Task StopListening() } } - private async Task StartListener() - { - try - { - _listener.Start(); - LocalEndpoint = (IPEndPoint)_listener.LocalEndpoint; - _logger?.LogInformation($"Listening for Visual Studio to connect on {LocalEndpoint.Address}:{LocalEndpoint.Port}" + Environment.NewLine); - _isReady = true; - - // This call will wait for the client to connect, before continuing. We shouldn't need a loop. - TcpClient tcpClient = await _listener.AcceptTcpClientAsync(); - OnConnect(tcpClient); - } - catch (SocketException soex) - { - _logger?.LogError("A Socket error occurred. The port may already be in use. Try rebooting to free up the port."); - _logger?.LogError($"Error:\n{soex.Message} \nStack Trace:\n{soex.StackTrace}"); - } - catch (Exception ex) - { - _logger?.LogError("An unhandled exception occurred while listening for debugging connections."); - _logger?.LogError($"Error:\n{ex.Message} \nStack Trace:\n{ex.StackTrace}"); - } - } - - private void OnConnect(TcpClient tcpClient) + private ActiveClient? CreateActiveClient(TcpClient tcpClient) { try { lock (_lck) { _logger?.LogInformation("Visual Studio has Connected" + Environment.NewLine); - if (_activeClientCount > 0 && _activeClient?.Disposed == false) + + if (_activeClient != null) { _logger?.LogDebug("Closing active client"); - Debug.Assert(_activeClientCount == 1); - Debug.Assert(_activeClient != null); - CloseActiveClient(); + _activeClient?.Dispose(); + _activeClient = null; } - _activeClient = new ActiveClient(_connection, tcpClient, _logger, _cancellationTokenSource?.Token); - _activeClientCount++; + return new ActiveClient(_connection, tcpClient, _logger, _cancellationTokenSource?.Token); } } catch (Exception ex) { _logger?.LogError(ex, "An error occurred while connecting to Visual Studio"); } - } - - internal void CloseActiveClient() - { - _activeClient?.Dispose(); - _activeClient = null; - _activeClientCount = 0; + return null; } public void Dispose() @@ -157,192 +109,4 @@ public void Dispose() Disposed = true; } } - - // Embedded class - private class ActiveClient : IDisposable - { - private readonly IMeadowConnection _connection; - private readonly TcpClient _tcpClient; - private readonly NetworkStream _networkStream; - - private readonly CancellationTokenSource _cts; - private readonly Task _receiveVsDebugDataTask; - private readonly Task _receiveMeadowDebugDataTask; - private readonly ILogger? _logger; - public bool Disposed = false; - private readonly BlockingCollection _debuggerMessages = new(); - - // Constructor - internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken) - { - if (cancellationToken != null) - { - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value); - } - else - { - _cts = new CancellationTokenSource(); - } - - _logger = logger; - _connection = connection; - _tcpClient = tcpClient; - _networkStream = tcpClient.GetStream(); - _logger?.LogDebug("Starting receive task"); - _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, TaskCreationOptions.LongRunning); - _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, TaskCreationOptions.LongRunning); - - _connection.DebuggerMessageReceived += MeadowConnection_DebuggerMessageReceived; - } - - private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e) - { - _logger?.LogDebug("Debugger Message Received, Adding to collection"); - _debuggerMessages.Add(e); - } - - private const int RECEIVE_BUFFER_SIZE = 256; - - private async Task SendToMeadowAsync() - { - try - { - using var md5 = MD5.Create(); - // Receive from Visual Studio and send to Meadow - var receiveBuffer = ArrayPool.Shared.Rent(RECEIVE_BUFFER_SIZE); - var meadowBuffer = Array.Empty(); - - while (!_cts.IsCancellationRequested) - { - if (_networkStream != null && _networkStream.CanRead) - { - int bytesRead; - do - { - bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, _cts.Token); - - if (bytesRead == 0 || _cts.IsCancellationRequested) - { - continue; - } - - var destIndex = meadowBuffer.Length; - Array.Resize(ref meadowBuffer, destIndex + bytesRead); - Array.Copy(receiveBuffer, 0, meadowBuffer, destIndex, bytesRead); - - // Forward the RECIEVE_BUFFER_SIZE chunk to Meadow immediately - _logger?.LogTrace("Received {count} bytes from VS, will forward to HCOM/Meadow. {hash}", - meadowBuffer.Length, - BitConverter.ToString(md5.ComputeHash(meadowBuffer)) - .Replace("-", string.Empty) - .ToLowerInvariant()); - - await _connection.SendDebuggerData(meadowBuffer, 0, _cts.Token); - meadowBuffer = Array.Empty(); - - // Ensure we read all the data in this message before passing it along - // I'm not sure this is actually needed, the whole message should get read at once. - } while (_networkStream.DataAvailable); - } - else - { - // User probably hit stop - _logger?.LogInformation("Unable to Read Data from Visual Studio"); - _logger?.LogTrace("Unable to Read Data from Visual Studio"); - } - } - } - catch (IOException ioe) - { - // VS client probably died - _logger?.LogInformation("Visual Studio has Disconnected" + Environment.NewLine); - _logger?.LogTrace(ioe, "Visual Studio has Disconnected"); - } - catch (ObjectDisposedException ode) - { - // User probably hit stop - _logger?.LogInformation("Visual Studio has stopped debugging" + Environment.NewLine); - _logger?.LogTrace(ode, "Visual Studio has stopped debugging"); - } - catch (Exception ex) - { - _logger?.LogError($"Error receiving data from Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); - throw; - } - } - - private async Task SendToVisualStudio() - { - try - { - while (!_cts.IsCancellationRequested) - { - if (_networkStream != null && _networkStream.CanWrite) - { - while (_debuggerMessages.Count > 0) - { - var byteData = _debuggerMessages.Take(_cts.Token); - _logger?.LogTrace("Received {count} bytes from Meadow, will forward to VS", byteData.Length); - if (!_tcpClient.Connected) - { - _logger?.LogDebug("Cannot forward data, Visual Studio is not connected"); - return; - } - - await _networkStream.WriteAsync(byteData, 0, byteData.Length, _cts.Token); - _logger?.LogTrace("Forwarded {count} bytes to VS", byteData.Length); - } - } - else - { - // User probably hit stop - _logger?.LogInformation("Unable to Write Data from Visual Studio"); - } - } - } - catch (OperationCanceledException oce) - { - // User probably hit stop; Removed logging as User doesn't need to see this - // Keeping it as a TODO in case we find a side effect that needs logging. - _logger?.LogInformation("Operation Cancelled"); - _logger?.LogTrace(oce, "Operation Cancelled"); - } - catch (Exception ex) - { - _logger?.LogError($"Error sending data to Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); - - if (_cts.IsCancellationRequested) - { - throw; - } - } - } - - public void Dispose() - { - lock (_tcpClient) - { - if (Disposed) - { - return; - } - - _logger?.LogTrace("Disposing ActiveClient"); - _cts.Cancel(false); - _receiveVsDebugDataTask.Wait(TimeSpan.FromSeconds(10)); - _receiveMeadowDebugDataTask.Wait(TimeSpan.FromSeconds(10)); - _receiveVsDebugDataTask?.Dispose(); - _receiveMeadowDebugDataTask?.Dispose(); - _tcpClient.Dispose(); - _networkStream.Dispose(); - _cts.Dispose(); - - if (_connection != null) - { - _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived; - } - Disposed = true; - } - } - } } \ No newline at end of file From 63276bc2d3279ef3384d84b1f9fa6fb48030c866 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 24 May 2024 08:59:36 -0700 Subject: [PATCH 2/5] Loop optimization via AutoResetEvent --- Source/v2/Meadow.Hcom/Connections/SerialConnection.cs | 6 ++++-- .../Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs index 94a724b9..de292e75 100755 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -22,6 +22,7 @@ public partial class SerialConnection : ConnectionBase, IDisposable private ConnectionState _state; private readonly List _listeners = new List(); private readonly Queue _pendingCommands = new Queue(); + private readonly AutoResetEvent _commandEvent = new AutoResetEvent(false); private bool _maintainConnection; private Thread? _connectionManager = null; private readonly List _textList = new List(); @@ -210,6 +211,7 @@ public override void Detach() var count = _messageCount; _pendingCommands.Enqueue(command); + _commandEvent.Set(); while (timeout-- > 0) { @@ -246,6 +248,7 @@ private void CommandManager() { while (!_isDisposed) { + _commandEvent.WaitOne(); while (_pendingCommands.Count > 0) { Debug.WriteLine($"There are {_pendingCommands.Count} pending commands"); @@ -261,8 +264,6 @@ private void CommandManager() // TODO: re-queue on fail? } } - - Thread.Sleep(1000); } } @@ -298,6 +299,7 @@ public void EnqueueRequest(IRequest command) } _pendingCommands.Enqueue(command); + _commandEvent.Set(); } private void EncodeAndSendPacket(byte[] messageBytes, CancellationToken? cancellationToken = null) diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs index 4e389f1d..a26c44c5 100644 --- a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs +++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs @@ -20,6 +20,7 @@ private class ActiveClient : IDisposable private readonly ILogger? _logger; public bool Disposed = false; private readonly BlockingCollection _debuggerMessages = new(); + private readonly AutoResetEvent _vsDebugDataReady = new(false); // Constructor internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken) @@ -49,6 +50,7 @@ private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e) { // _logger?.LogDebug("Debugger Message Received, Adding to collection"); _debuggerMessages.Add(e); + _vsDebugDataReady.Set(); } private const int RECEIVE_BUFFER_SIZE = 256; @@ -134,6 +136,8 @@ private async Task SendToVisualStudio() { if (_networkStream != null && _networkStream.CanWrite) { + _vsDebugDataReady.WaitOne(); + while (_debuggerMessages.Count > 0) { var byteData = _debuggerMessages.Take(_cts.Token); From 38d571fde8947245fecbbf05eca89930c52635a5 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 24 May 2024 23:28:34 -0700 Subject: [PATCH 3/5] More ActiveClient refactoring --- .../Debugging/DebuggingServer.ActiveClient.cs | 102 +++++++----------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs index a26c44c5..5849ab1c 100644 --- a/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs +++ b/Source/v2/Meadow.Hcom/Debugging/DebuggingServer.ActiveClient.cs @@ -7,48 +7,40 @@ namespace Meadow.Hcom; public partial class DebuggingServer { - // Embedded class private class ActiveClient : IDisposable { private readonly IMeadowConnection _connection; private readonly TcpClient _tcpClient; private readonly NetworkStream _networkStream; - private readonly CancellationTokenSource _cts; private readonly Task _receiveVsDebugDataTask; private readonly Task _receiveMeadowDebugDataTask; private readonly ILogger? _logger; - public bool Disposed = false; + private bool _disposed; private readonly BlockingCollection _debuggerMessages = new(); private readonly AutoResetEvent _vsDebugDataReady = new(false); - // Constructor internal ActiveClient(IMeadowConnection connection, TcpClient tcpClient, ILogger? logger, CancellationToken? cancellationToken) { - if (cancellationToken != null) - { - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value); - } - else - { - _cts = new CancellationTokenSource(); - } + _cts = cancellationToken != null + ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value) + : new CancellationTokenSource(); _logger = logger; _connection = connection; _tcpClient = tcpClient; _networkStream = tcpClient.GetStream(); + _logger?.LogDebug("Starting receive task"); _connection.DebuggerMessageReceived += MeadowConnection_DebuggerMessageReceived; - _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, TaskCreationOptions.LongRunning); - _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, TaskCreationOptions.LongRunning); + _receiveVsDebugDataTask = Task.Factory.StartNew(SendToMeadowAsync, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + _receiveMeadowDebugDataTask = Task.Factory.StartNew(SendToVisualStudio, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } private void MeadowConnection_DebuggerMessageReceived(object sender, byte[] e) { - // _logger?.LogDebug("Debugger Message Received, Adding to collection"); _debuggerMessages.Add(e); _vsDebugDataReady.Set(); } @@ -60,11 +52,10 @@ private async Task SendToMeadowAsync() try { using var md5 = MD5.Create(); - // Receive from Visual Studio and send to Meadow var receiveBuffer = ArrayPool.Shared.Rent(RECEIVE_BUFFER_SIZE); var meadowBuffer = Array.Empty(); - while (!_cts.IsCancellationRequested) + while (!_cts.Token.IsCancellationRequested) { if (_networkStream != null && _networkStream.CanRead) { @@ -73,7 +64,7 @@ private async Task SendToMeadowAsync() { bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, _cts.Token); - if (bytesRead == 0 || _cts.IsCancellationRequested) + if (bytesRead == 0 || _cts.Token.IsCancellationRequested) { continue; } @@ -82,28 +73,18 @@ private async Task SendToMeadowAsync() Array.Resize(ref meadowBuffer, destIndex + bytesRead); Array.Copy(receiveBuffer, 0, meadowBuffer, destIndex, bytesRead); - // Forward the RECIEVE_BUFFER_SIZE chunk to Meadow immediately - /* _logger?.LogTrace("Received {count} bytes from VS, will forward to HCOM/Meadow. {hash}", meadowBuffer.Length, BitConverter.ToString(md5.ComputeHash(meadowBuffer)) .Replace("-", string.Empty) .ToLowerInvariant()); - */ await _connection.SendDebuggerData(meadowBuffer, 0, _cts.Token); meadowBuffer = Array.Empty(); - - //output the data to the console - Debug.WriteLine($"************************** ToMeadow: {BitConverter.ToString(receiveBuffer, 0, bytesRead).Replace("-", string.Empty).ToLowerInvariant()}"); - - // Ensure we read all the data in this message before passing it along - // I'm not sure this is actually needed, the whole message should get read at once. } while (_networkStream.DataAvailable); } else { - // User probably hit stop _logger?.LogInformation("Unable to Read Data from Visual Studio"); _logger?.LogTrace("Unable to Read Data from Visual Studio"); } @@ -111,19 +92,17 @@ private async Task SendToMeadowAsync() } catch (IOException ioe) { - // VS client probably died - _logger?.LogInformation("Visual Studio has Disconnected" + Environment.NewLine); + _logger?.LogInformation("Visual Studio has Disconnected"); _logger?.LogTrace(ioe, "Visual Studio has Disconnected"); } catch (ObjectDisposedException ode) { - // User probably hit stop - _logger?.LogInformation("Visual Studio has stopped debugging" + Environment.NewLine); + _logger?.LogInformation("Visual Studio has stopped debugging"); _logger?.LogTrace(ode, "Visual Studio has stopped debugging"); } catch (Exception ex) { - _logger?.LogError($"Error receiving data from Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); + _logger?.LogError($"Error receiving data from Visual Studio.\nError: {ex.Message}\nStackTrace:\n{ex.StackTrace}"); throw; } } @@ -132,7 +111,7 @@ private async Task SendToVisualStudio() { try { - while (!_cts.IsCancellationRequested) + while (!_cts.Token.IsCancellationRequested) { if (_networkStream != null && _networkStream.CanWrite) { @@ -142,7 +121,6 @@ private async Task SendToVisualStudio() { var byteData = _debuggerMessages.Take(_cts.Token); - /* _logger?.LogTrace("Received {count} bytes from Meadow, will forward to VS", byteData.Length); if (!_tcpClient.Connected) { @@ -150,13 +128,8 @@ private async Task SendToVisualStudio() return; } - */ - await _networkStream.WriteAsync(byteData, 0, byteData.Length, _cts.Token); _logger?.LogTrace("Forwarded {count} bytes to VS", byteData.Length); - - //output the data to the console - Debug.WriteLine($"************************** ToVisStu: {BitConverter.ToString(byteData).Replace("-", string.Empty).ToLowerInvariant()}"); } } else @@ -167,16 +140,14 @@ private async Task SendToVisualStudio() } catch (OperationCanceledException oce) { - // User probably hit stop; Removed logging as User doesn't need to see this - // Keeping it as a TODO in case we find a side effect that needs logging. _logger?.LogInformation("Operation Cancelled"); _logger?.LogTrace(oce, "Operation Cancelled"); } catch (Exception ex) { - _logger?.LogError($"Error sending data to Visual Studio.{Environment.NewLine}Error: {ex.Message}{Environment.NewLine}StackTrace:{Environment.NewLine}{ex.StackTrace}"); + _logger?.LogError($"Error sending data to Visual Studio.\nError: {ex.Message}\nStackTrace:\n{ex.StackTrace}"); - if (_cts.IsCancellationRequested) + if (!_cts.Token.IsCancellationRequested) { throw; } @@ -185,29 +156,30 @@ private async Task SendToVisualStudio() public void Dispose() { - lock (_tcpClient) + if (_disposed) { - if (Disposed) - { - return; - } + return; + } - _logger?.LogTrace("Disposing ActiveClient"); - _cts.Cancel(false); - _receiveVsDebugDataTask.Wait(TimeSpan.FromSeconds(10)); - _receiveMeadowDebugDataTask.Wait(TimeSpan.FromSeconds(10)); - _receiveVsDebugDataTask?.Dispose(); - _receiveMeadowDebugDataTask?.Dispose(); - _tcpClient.Dispose(); - _networkStream.Dispose(); - _cts.Dispose(); - - if (_connection != null) - { - _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived; - } - Disposed = true; + _logger?.LogTrace("Disposing ActiveClient"); + _cts.Cancel(); + try + { + Task.WhenAll(_receiveVsDebugDataTask, _receiveMeadowDebugDataTask).Wait(TimeSpan.FromSeconds(10)); + } + catch (AggregateException ex) + { + _logger?.LogError("Error waiting for tasks to complete during dispose", ex); + } + _tcpClient.Dispose(); + _networkStream.Dispose(); + _cts.Dispose(); + + if (_connection != null) + { + _connection.DebuggerMessageReceived -= MeadowConnection_DebuggerMessageReceived; } + _disposed = true; } } -} \ No newline at end of file +} From 4dedf0ed9364760bdc060e1c0d4eacc4adc0dc41 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 24 May 2024 23:31:16 -0700 Subject: [PATCH 4/5] Cleanup --- Source/v2/Meadow.Hcom/Connections/SerialConnection.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs index de292e75..3c094aa4 100755 --- a/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs +++ b/Source/v2/Meadow.Hcom/Connections/SerialConnection.cs @@ -184,8 +184,6 @@ public override void Detach() // TODO: close this up } - //State = ConnectionState.Disconnected; - Close(); } @@ -1236,11 +1234,6 @@ public override async Task StartDebuggingSession(int port, ILog logger?.LogDebug($"Start Debugging on port: {port}"); await Device.StartDebugging(port, logger, cancellationToken); - // await Task.Delay(2000); - - // Device = null; - // State = ConnectionState.Disconnected; - await WaitForMeadowAttach(cancellationToken); return debuggingServer; @@ -1274,7 +1267,5 @@ public override async Task SendDebuggerData(byte[] debuggerData, uint userData, _lastRequestConcluded = null; EnqueueRequest(command); - - //await WaitForConcluded(null, cancellationToken); } } \ No newline at end of file From 30ce107fef5ead2b89f2402182144dd1cd4f5da6 Mon Sep 17 00:00:00 2001 From: Adrian Stevens Date: Fri, 24 May 2024 23:39:13 -0700 Subject: [PATCH 5/5] Bump version to 2.0.46.0 --- Source/v2/Meadow.Cli/Meadow.CLI.csproj | 2 +- Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/v2/Meadow.Cli/Meadow.CLI.csproj b/Source/v2/Meadow.Cli/Meadow.CLI.csproj index 9cee05e5..7f9c4f62 100644 --- a/Source/v2/Meadow.Cli/Meadow.CLI.csproj +++ b/Source/v2/Meadow.Cli/Meadow.CLI.csproj @@ -10,7 +10,7 @@ Wilderness Labs, Inc Wilderness Labs, Inc true - 2.0.44.0 + 2.0.46.0 AnyCPU http://developer.wildernesslabs.co/Meadow/Meadow.CLI/ https://github.com/WildernessLabs/Meadow.CLI diff --git a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs index 9d681764..75a3506c 100644 --- a/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs +++ b/Source/v2/Meadow.Cli/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ namespace Meadow.CLI; public static class Constants { - public const string CLI_VERSION = "2.0.44.0"; + public const string CLI_VERSION = "2.0.46.0"; } \ No newline at end of file