diff --git a/VSRAD.DebugServer/Logging/ClientLogger.cs b/VSRAD.DebugServer/Logging/ClientLogger.cs index 2f7066218..e1f12ed96 100644 --- a/VSRAD.DebugServer/Logging/ClientLogger.cs +++ b/VSRAD.DebugServer/Logging/ClientLogger.cs @@ -35,7 +35,7 @@ public void FatalClientException(Exception e) => Print("An exception has occurred while processing the command. Connection has been terminated." + Environment.NewLine + e.ToString()); public void CliendDisconnected() => - Print("client has been disconnected"); + Console.WriteLine($"{Environment.NewLine}client #{_clientId} has been disconnected"); public void ExecutionStarted() { @@ -98,6 +98,16 @@ public void ConnectionTimeoutOnHandShake() Console.WriteLine($"{Environment.NewLine}Connection timeout on handshake attempt"); } + public void LockAcquired() + { + Console.WriteLine($"{Environment.NewLine}client#{_clientId} acquired lock"); + } + + public void LockReleased() + { + Console.WriteLine($"{Environment.NewLine}client#{_clientId} released lock"); + } + private void Print(string message) => Console.WriteLine("===" + Environment.NewLine + $"[Client #{_clientId}] {message}"); } diff --git a/VSRAD.DebugServer/NetworkClient.cs b/VSRAD.DebugServer/NetworkClient.cs index 80db5a15d..57b9f4566 100644 --- a/VSRAD.DebugServer/NetworkClient.cs +++ b/VSRAD.DebugServer/NetworkClient.cs @@ -38,6 +38,10 @@ public async Task ReceiveCommandAsync() } } + public NetworkStream GetStream() + { + return _socket.GetStream(); + } public Task SendResponseAsync(IPC.Responses.IResponse response) => _socket.GetStream().WriteSerializedMessageAsync(response); diff --git a/VSRAD.DebugServer/Server.cs b/VSRAD.DebugServer/Server.cs index 84405a4a6..afb56101f 100644 --- a/VSRAD.DebugServer/Server.cs +++ b/VSRAD.DebugServer/Server.cs @@ -12,6 +12,7 @@ namespace VSRAD.DebugServer public sealed class Server { private readonly SemaphoreSlim _commandExecutionLock = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _actionExecutionLock = new SemaphoreSlim(1, 1); private readonly TcpListener _listener; private readonly bool _verboseLogging; private static Version _serverVersion = typeof(Server).Assembly.GetName().Version; @@ -43,6 +44,11 @@ public enum HandShakeStatus server_not_accepted } + public enum LockStatus + { + lock_not_ackquired, + lock_acquired + } public async Task LoopAsync() { /* disable Quick Edit cmd feature to prevent server hanging */ @@ -79,6 +85,27 @@ private void TcpClientConnected(TcpClient tcpClient, uint clientId) Task.Run(() => BeginClientLoopAsync(networkClient, clientLog)); } + private async Task AcquireLock(NetworkClient client, ClientLogger clientLog) + { + try + { + await _actionExecutionLock.WaitAsync(); + clientLog.LockAcquired(); + StreamWriter writer = new StreamWriter(client.GetStream(), Encoding.UTF8) { AutoFlush = true }; + await writer.WriteLineAsync(LockStatus.lock_acquired.ToString()); + } + catch (Exception e) + { + _actionExecutionLock.Release(); + clientLog.LockReleased(); + clientLog.FatalClientException(e); + client.Disconnect(); + clientLog.CliendDisconnected(); + return false; + } + return true; + } + private async Task TryProcessServerHandshake(TcpClient client, ClientLogger clientLog) { try @@ -134,17 +161,18 @@ private async Task TryProcessServerHandshake(TcpClient client, ClientLogge private async Task BeginClientLoopAsync(NetworkClient client, ClientLogger clientLog) { - while (true) + // Client closed connection during acquire lock phase + // + if (!await AcquireLock(client, clientLog)) + return; + + try { - bool lockAcquired = false; - try + while (true) { var command = await client.ReceiveCommandAsync().ConfigureAwait(false); clientLog.CommandReceived(command); - await _commandExecutionLock.WaitAsync(); - lockAcquired = true; - var response = await Dispatcher.DispatchAsync(command, clientLog).ConfigureAwait(false); if (response != null) // commands like Deploy do not return a response { @@ -153,23 +181,20 @@ private async Task BeginClientLoopAsync(NetworkClient client, ClientLogger clien } clientLog.CommandProcessed(); } - catch (ConnectionFailedException) - { - client.Disconnect(); - clientLog.CliendDisconnected(); - break; - } - catch (Exception e) - { - client.Disconnect(); - clientLog.FatalClientException(e); - break; - } - finally - { - if (lockAcquired) - _commandExecutionLock.Release(); - } + } + catch (ConnectionFailedException) + { + clientLog.CliendDisconnected(); + } + catch (Exception e) + { + clientLog.FatalClientException(e); + } + finally + { + client.Disconnect(); + _actionExecutionLock.Release(); + clientLog.LockReleased(); } } } diff --git a/VSRAD.Package/Server/ActionRunner.cs b/VSRAD.Package/Server/ActionRunner.cs index 06ae00a2c..c150f8dd7 100644 --- a/VSRAD.Package/Server/ActionRunner.cs +++ b/VSRAD.Package/Server/ActionRunner.cs @@ -21,6 +21,7 @@ public sealed class ActionRunner private readonly Dictionary _initialTimestamps = new Dictionary(); private readonly ActionEnvironment _environment; private readonly IProject _project; + private readonly VsStatusBarWriter _statusBar; public ActionRunner(ICommunicationChannel channel, SVsServiceProvider serviceProvider, ActionEnvironment environment, IProject project) { @@ -28,6 +29,7 @@ public ActionRunner(ICommunicationChannel channel, SVsServiceProvider servicePro _serviceProvider = serviceProvider; _environment = environment; _project = project; + _statusBar = new VsStatusBarWriter(serviceProvider); } public DateTime GetInitialFileTimestamp(string file) => @@ -67,6 +69,7 @@ public async Task RunAsync(string actionName, IReadOnlyList.*)\/RadeonAsmDebugger\.dll", RegexOptions.Compiled); private readonly OutputWindowWriter _outputWindowWriter; + private readonly VsStatusBarWriter _statusBar; private readonly IProject _project; private TcpClient _connection; @@ -104,6 +122,7 @@ public CommunicationChannel(SVsServiceProvider provider, IProject project) { _outputWindowWriter = new OutputWindowWriter(provider, Constants.OutputPaneServerGuid, Constants.OutputPaneServerTitle); + _statusBar = new VsStatusBarWriter(provider); _project = project; _project.RunWhenLoaded((options) => options.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(options.ActiveProfile)) ForceDisconnect(); }); @@ -114,7 +133,70 @@ public CommunicationChannel(SVsServiceProvider provider, IProject project) } public Task SendWithReplyAsync(ICommand command) where T : IResponse => - SendWithReplyAsync(command, tryReconnect: true); + SendWithReplyAsync(command, tryReconnect: false); + + private async Task TryAcquireServerLock(TcpClient client) { + StreamReader reader = new StreamReader(client.GetStream(), Encoding.UTF8); + await _statusBar.SetTextAsync("Acquiring lock on the server"); + using (var cts = new CancellationTokenSource(_lockTimeout)) + using (cts.Token.Register(() => client.Dispose())) + try + { + + if (await reader.ReadLineAsync() != LockStatus.lock_acquired.ToString()) + { + throw new UnaquiredLockException(ConnectionOptions); + } + await _statusBar.SetTextAsync("Acquired Lock"); + } + catch (Exception e) + { + MessageBox.Show("Unable to acquire lock, try again."); + return false; + } + return true; + } + + private async Task TryProcessClientHandshake(TcpClient client) + { + StreamWriter writer = new StreamWriter(client.GetStream(), Encoding.UTF8) { AutoFlush = true }; + StreamReader reader = new StreamReader(client.GetStream(), Encoding.UTF8); + + // Send client version to server + // + await writer.WriteLineAsync(_extensionVersion.ToString()).ConfigureAwait(false); + // Obtain server version + // + String serverResponse = await reader.ReadLineAsync().ConfigureAwait(false); + Version serverVersion = null; + if (!Version.TryParse(serverResponse, out serverVersion)) + { + // Inform server that client declines serve's version + // + await writer.WriteLineAsync(HandShakeStatus.client_not_accepted.ToString()).ConfigureAwait(false); + throw new UnsupportedServerVersionException(ConnectionOptions, Constants.MinimalRequiredServerVersion); + } + + if (serverVersion.CompareTo(Constants.MinimalRequiredServerVersion) < 0) + { + // Inform client that server declines client's version + // + await writer.WriteLineAsync(HandShakeStatus.client_not_accepted.ToString()).ConfigureAwait(false); + throw new UnsupportedServerVersionException(ConnectionOptions, Constants.MinimalRequiredServerVersion); + } + + // Inform client that server accepts client's version + // + await writer.WriteLineAsync(HandShakeStatus.client_accepted.ToString()).ConfigureAwait(false); + + // Check if client accepts server version + // + if (await reader.ReadLineAsync() != HandShakeStatus.server_accepted.ToString()) + { + throw new UnsupportedDebuggerVersionException(ConnectionOptions, serverVersion); + } + return true; + } private async Task TryProcessClientHandshake(TcpClient client) { @@ -202,6 +284,7 @@ public async Task> GetRemoteEnvironmentAsync await EstablishServerConnectionAsync().ConfigureAwait(false); var environment = await SendWithReplyAsync(new ListEnvironmentVariables()); _remoteEnvironment = environment.Variables; + ForceDisconnect(); } return _remoteEnvironment; } @@ -223,14 +306,15 @@ private async Task EstablishServerConnectionAsync() var client = new TcpClient(); try { - using (var cts = new CancellationTokenSource(_connectionTimeout)) - using (cts.Token.Register(() => client.Dispose())) { + using (var cts = new CancellationTokenSource(_connectionTimeout)) + using (cts.Token.Register(() => client.Dispose())) await client.ConnectAsync(ConnectionOptions.RemoteMachine, ConnectionOptions.Port); await TryProcessClientHandshake(client); _connection = client; ConnectionState = ClientState.Connected; } + await TryAcquireServerLock(client); } catch (Exception e) when (!(e is UnsupportedDebuggerVersionException) && !(e is UnsupportedServerVersionException)) {