Give Client a chance to receive Close Frame from Server (#730)

This commit is contained in:
BrennanConroy 2017-08-18 09:56:40 -07:00 committed by GitHub
parent e349329dc7
commit a4053acd06
3 changed files with 39 additions and 10 deletions

View File

@ -62,6 +62,9 @@ namespace Microsoft.AspNetCore.Sockets.Client.Internal
private static readonly Action<ILogger, DateTime, string, Exception> _closingWebSocketFailed = private static readonly Action<ILogger, DateTime, string, Exception> _closingWebSocketFailed =
LoggerMessage.Define<DateTime, string>(LogLevel.Information, 16, "{time}: Connection Id {connectionId}: Closing webSocket failed."); LoggerMessage.Define<DateTime, string>(LogLevel.Information, 16, "{time}: Connection Id {connectionId}: Closing webSocket failed.");
private static readonly Action<ILogger, DateTime, string, Exception> _cancelMessage =
LoggerMessage.Define<DateTime, string>(LogLevel.Debug, 17, "{time}: Connection Id {connectionId}: Canceled passing message to application.");
// Category: ServerSentEventsTransport and LongPollingTransport // Category: ServerSentEventsTransport and LongPollingTransport
private static readonly Action<ILogger, DateTime, string, int, Uri, Exception> _sendingMessages = private static readonly Action<ILogger, DateTime, string, int, Uri, Exception> _sendingMessages =
LoggerMessage.Define<DateTime, string, int, Uri>(LogLevel.Debug, 9, "{time}: Connection Id {connectionId}: Sending {count} message(s) to the server using url: {url}."); LoggerMessage.Define<DateTime, string, int, Uri>(LogLevel.Debug, 9, "{time}: Connection Id {connectionId}: Sending {count} message(s) to the server using url: {url}.");
@ -283,6 +286,14 @@ namespace Microsoft.AspNetCore.Sockets.Client.Internal
} }
} }
public static void CancelMessage(this ILogger logger, string connectionId)
{
if (logger.IsEnabled(LogLevel.Debug))
{
_cancelMessage(logger, DateTime.Now, connectionId, null);
}
}
public static void SendingMessages(this ILogger logger, string connectionId, int count, Uri url) public static void SendingMessages(this ILogger logger, string connectionId, int count, Uri url)
{ {
if (logger.IsEnabled(LogLevel.Debug)) if (logger.IsEnabled(LogLevel.Debug))

View File

@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
private readonly ClientWebSocket _webSocket = new ClientWebSocket(); private readonly ClientWebSocket _webSocket = new ClientWebSocket();
private Channel<byte[], SendMessage> _application; private Channel<byte[], SendMessage> _application;
private readonly CancellationTokenSource _transportCts = new CancellationTokenSource(); private readonly CancellationTokenSource _transportCts = new CancellationTokenSource();
private readonly CancellationTokenSource _receiveCts = new CancellationTokenSource();
private readonly ILogger _logger; private readonly ILogger _logger;
private string _connectionId; private string _connectionId;
@ -80,7 +81,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
try try
{ {
while (!_transportCts.Token.IsCancellationRequested) while (!_receiveCts.Token.IsCancellationRequested)
{ {
const int bufferSize = 4096; const int bufferSize = 4096;
var totalBytes = 0; var totalBytes = 0;
@ -91,7 +92,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
var buffer = new ArraySegment<byte>(new byte[bufferSize]); var buffer = new ArraySegment<byte>(new byte[bufferSize]);
//Exceptions are handled above where the send and receive tasks are being run. //Exceptions are handled above where the send and receive tasks are being run.
receiveResult = await _webSocket.ReceiveAsync(buffer, _transportCts.Token); receiveResult = await _webSocket.ReceiveAsync(buffer, _receiveCts.Token);
if (receiveResult.MessageType == WebSocketMessageType.Close) if (receiveResult.MessageType == WebSocketMessageType.Close)
{ {
_logger.WebSocketClosed(_connectionId, receiveResult.CloseStatus); _logger.WebSocketClosed(_connectionId, receiveResult.CloseStatus);
@ -129,15 +130,25 @@ namespace Microsoft.AspNetCore.Sockets.Client
Buffer.BlockCopy(incomingMessage[0].Array, incomingMessage[0].Offset, messageBuffer, 0, incomingMessage[0].Count); Buffer.BlockCopy(incomingMessage[0].Array, incomingMessage[0].Offset, messageBuffer, 0, incomingMessage[0].Count);
} }
_logger.MessageToApp(_connectionId, messageBuffer.Length); try
while (await _application.Out.WaitToWriteAsync(_transportCts.Token))
{ {
if (_application.Out.TryWrite(messageBuffer)) if (!_transportCts.Token.IsCancellationRequested)
{ {
incomingMessage.Clear(); _logger.MessageToApp(_connectionId, messageBuffer.Length);
break; while (await _application.Out.WaitToWriteAsync(_transportCts.Token))
{
if (_application.Out.TryWrite(messageBuffer))
{
incomingMessage.Clear();
break;
}
}
} }
} }
catch (OperationCanceledException)
{
_logger.CancelMessage(_connectionId);
}
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -198,7 +209,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
finally finally
{ {
_logger.SendStopped(_connectionId); _logger.SendStopped(_connectionId);
_transportCts.Cancel(); TriggerCancel();
} }
} }
@ -248,7 +259,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
await _webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); await _webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
// shutdown the transport after a timeout in case the server does not send close frame // shutdown the transport after a timeout in case the server does not send close frame
_transportCts.CancelAfter(TimeSpan.FromSeconds(5)); TriggerCancel();
} }
} }
catch (Exception ex) catch (Exception ex)
@ -258,5 +269,12 @@ namespace Microsoft.AspNetCore.Sockets.Client
_logger.ClosingWebSocketFailed(_connectionId, ex); _logger.ClosingWebSocketFailed(_connectionId, ex);
} }
} }
private void TriggerCancel()
{
// Give server 5 seconds to respond with a close frame for graceful close.
_receiveCts.CancelAfter(TimeSpan.FromSeconds(5));
_transportCts.Cancel();
}
} }
} }

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection,
TransferMode.Binary, connectionId: string.Empty); TransferMode.Binary, connectionId: string.Empty);
connectionToTransport.Out.TryComplete(); connectionToTransport.Out.TryComplete();
await webSocketsTransport.Running.OrTimeout(); await webSocketsTransport.Running.OrTimeout(TimeSpan.FromSeconds(10));
} }
} }