Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion VSRAD.DebugServer/Logging/ClientLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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}");
}
Expand Down
4 changes: 4 additions & 0 deletions VSRAD.DebugServer/NetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public async Task<ICommand> ReceiveCommandAsync()
}
}

public NetworkStream GetStream()
{
return _socket.GetStream();
}

public Task<int> SendResponseAsync(IPC.Responses.IResponse response) =>
_socket.GetStream().WriteSerializedMessageAsync(response);
Expand Down
71 changes: 48 additions & 23 deletions VSRAD.DebugServer/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -79,6 +85,27 @@ private void TcpClientConnected(TcpClient tcpClient, uint clientId)
Task.Run(() => BeginClientLoopAsync(networkClient, clientLog));
}

private async Task<bool> 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<bool> TryProcessServerHandshake(TcpClient client, ClientLogger clientLog)
{
try
Expand Down Expand Up @@ -134,17 +161,18 @@ private async Task<bool> 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
{
Expand All @@ -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();
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions VSRAD.Package/Server/ActionRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ public sealed class ActionRunner
private readonly Dictionary<string, DateTime> _initialTimestamps = new Dictionary<string, DateTime>();
private readonly ActionEnvironment _environment;
private readonly IProject _project;
private readonly VsStatusBarWriter _statusBar;

public ActionRunner(ICommunicationChannel channel, SVsServiceProvider serviceProvider, ActionEnvironment environment, IProject project)
{
_channel = channel;
_serviceProvider = serviceProvider;
_environment = environment;
_project = project;
_statusBar = new VsStatusBarWriter(serviceProvider);
}

public DateTime GetInitialFileTimestamp(string file) =>
Expand Down Expand Up @@ -67,6 +69,7 @@ public async Task<ActionRunResult> RunAsync(string actionName, IReadOnlyList<IAc
if (!result.Successful && !continueOnError)
break;
}
_channel.ForceDisconnect();

runStats.FinishRun();
return runStats;
Expand Down
90 changes: 87 additions & 3 deletions VSRAD.Package/Server/CommunicationChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using VSRAD.Package.Utils;
using System.Windows;

namespace VSRAD.Package.Server
{
Expand Down Expand Up @@ -54,6 +56,13 @@ public UnsupportedDebuggerVersionException(ServerConnectionOptions connection, V
{ }
}

public sealed class UnaquiredLockException : System.IO.IOException
{
public UnaquiredLockException(ServerConnectionOptions connection) :
base($"Unable to acquire lock at {connection}")
{ }
}

public enum ClientState
{
Disconnected,
Expand All @@ -69,6 +78,12 @@ public enum HandShakeStatus
server_not_accepted
}

public enum LockStatus
{
lock_not_ackquired,
lock_acquired
}

[Export(typeof(ICommunicationChannel))]
[AppliesTo(Constants.RadOrVisualCProjectCapability)]
public sealed class CommunicationChannel : ICommunicationChannel
Expand All @@ -89,9 +104,12 @@ public ClientState ConnectionState
}

private static readonly TimeSpan _connectionTimeout = new TimeSpan(hours: 0, minutes: 0, seconds: 5);
private static readonly TimeSpan _lockTimeout = new TimeSpan(hours: 0, minutes: 0, seconds: 10);

private static readonly Regex _extensionVersionRegex = new Regex(@".*\/(?<version>.*)\/RadeonAsmDebugger\.dll", RegexOptions.Compiled);

private readonly OutputWindowWriter _outputWindowWriter;
private readonly VsStatusBarWriter _statusBar;
private readonly IProject _project;

private TcpClient _connection;
Expand All @@ -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(); });
Expand All @@ -114,7 +133,70 @@ public CommunicationChannel(SVsServiceProvider provider, IProject project)
}

public Task<T> SendWithReplyAsync<T>(ICommand command) where T : IResponse =>
SendWithReplyAsync<T>(command, tryReconnect: true);
SendWithReplyAsync<T>(command, tryReconnect: false);

private async Task<bool> 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<bool> 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<bool> TryProcessClientHandshake(TcpClient client)
{
Expand Down Expand Up @@ -202,6 +284,7 @@ public async Task<IReadOnlyDictionary<string, string>> GetRemoteEnvironmentAsync
await EstablishServerConnectionAsync().ConfigureAwait(false);
var environment = await SendWithReplyAsync<EnvironmentVariablesListed>(new ListEnvironmentVariables());
_remoteEnvironment = environment.Variables;
ForceDisconnect();
}
return _remoteEnvironment;
}
Expand All @@ -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))
{
Expand Down