in Nodejs/Product/WebRole/WebSocketProxyBase.cs [95:196]
private async Task WebSocketRequestHandler(AspNetWebSocketContext context)
{
Log("Accepted web socket request from {0}.", context.UserHostAddress);
TaskCompletionSource<bool> tcs = null;
if (!AllowConcurrentConnections)
{
tcs = new TaskCompletionSource<bool>();
while (true)
{
var currentSession = Interlocked.CompareExchange(ref _currentSession, tcs.Task, null);
if (currentSession == null)
{
break;
}
Log("Another session is active, waiting for completion.");
await currentSession;
Log("The other session completed, proceeding.");
}
}
try
{
var webSocket = context.WebSocket;
using (var tcpClient = new TcpClient("localhost", DebuggerPort))
{
try
{
var stream = tcpClient.GetStream();
var cts = new CancellationTokenSource();
// Start the workers that copy data from one socket to the other in both directions, and wait until either
// completes. The workers are fully async, and so their loops are transparently interleaved when running.
// Usually end of session is caused by VS dropping its connection on detach, and so it will be
// CopyFromWebSocketToStream that returns first; but it can be the other one if debuggee process crashes.
Log("Starting copy workers.");
var copyFromStreamToWebSocketTask = CopyFromStreamToWebSocketWorker(stream, webSocket, cts.Token);
var copyFromWebSocketToStreamTask = CopyFromWebSocketToStreamWorker(webSocket, stream, cts.Token);
Task completedTask = null;
try
{
completedTask = await Task.WhenAny(copyFromStreamToWebSocketTask, copyFromWebSocketToStreamTask);
}
catch (IOException ex)
{
Log(ex);
}
catch (WebSocketException ex)
{
Log(ex);
}
// Now that one worker is done, try to gracefully terminate the other one by issuing a cancellation request.
// it is normally blocked on a read, and this will cancel it if possible, and throw OperationCanceledException.
Log("One of the workers completed, shutting down the remaining one.");
cts.Cancel();
try
{
await Task.WhenAny(Task.WhenAll(copyFromStreamToWebSocketTask, copyFromWebSocketToStreamTask), Task.Delay(1000));
}
catch (OperationCanceledException ex)
{
Log(ex);
}
// Try to gracefully close the websocket if it's still open - this is not necessary, but nice to have.
Log("Both workers shut down, trying to close websocket.");
try
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
catch (WebSocketException ex)
{
Log(ex);
}
}
finally
{
// Gracefully close the TCP socket. This is crucial to avoid "Remote debugger already attached" problems.
Log("Shutting down TCP socket.");
try
{
tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Disconnect(false);
}
catch (SocketException ex)
{
Log(ex);
}
Log("All done!");
}
}
}
finally
{
if (tcs != null)
{
Volatile.Write(ref _currentSession, null);
tcs.SetResult(true);
}
}
}