private async Task CloseAsyncCore(WebSocketCloseStatus closeStatus,
string statusDescription,
CancellationToken cancellationToken)
{
string inputParameter = string.Empty;
if (NetEventSource.IsEnabled)
{
inputParameter = string.Format(CultureInfo.InvariantCulture,
"closeStatus: {0}, statusDescription: {1}",
closeStatus,
statusDescription);
NetEventSource.Enter(this, inputParameter);
}
try
{
ThrowIfPendingException();
if (IsStateTerminal(State))
{
return;
}
ThrowIfDisposed();
bool lockTaken = false;
Monitor.Enter(_thisLock, ref lockTaken);
bool ownsCloseCancellationTokenSource = false;
CancellationToken linkedCancellationToken = CancellationToken.None;
try
{
ThrowIfPendingException();
if (IsStateTerminal(State))
{
return;
}
ThrowIfDisposed();
ThrowOnInvalidState(State,
WebSocketState.Open, WebSocketState.CloseReceived, WebSocketState.CloseSent);
Task closeOutputTask;
ownsCloseCancellationTokenSource = _closeOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken);
if (ownsCloseCancellationTokenSource)
{
closeOutputTask = _closeOutputTask;
if (closeOutputTask == null && State != WebSocketState.CloseSent)
{
if (_closeReceivedTaskCompletionSource == null)
{
_closeReceivedTaskCompletionSource = new TaskCompletionSource<object>();
}
closeOutputTask = CloseOutputAsync(closeStatus,
statusDescription,
linkedCancellationToken);
}
}
else
{
Debug.Assert(_closeReceivedTaskCompletionSource != null,
"'_closeReceivedTaskCompletionSource' MUST NOT be NULL.");
closeOutputTask = _closeReceivedTaskCompletionSource.Task;
}
if (closeOutputTask != null)
{
ReleaseLock(_thisLock, ref lockTaken);
try
{
await closeOutputTask.SuppressContextFlow();
}
catch (Exception closeOutputError)
{
Monitor.Enter(_thisLock, ref lockTaken);
if (!CanHandleExceptionDuringClose(closeOutputError))
{
ThrowIfConvertibleException(nameof(CloseOutputAsync),
closeOutputError,
cancellationToken,
linkedCancellationToken.IsCancellationRequested);
throw;
}
}
// When closeOutputTask != null and an exception thrown from await closeOutputTask is handled,
// the lock will be taken in the catch-block. So the logic here avoids taking the lock twice.
if (!lockTaken)
{
Monitor.Enter(_thisLock, ref lockTaken);
}
}
if (OnCloseOutputCompleted())
{
bool callCompleteOnCloseCompleted = false;
try
{
// linkedCancellationToken can be CancellationToken.None if ownsCloseCancellationTokenSource==false
// This is still ok because OnCloseOutputCompleted won't start any IO operation in this case
callCompleteOnCloseCompleted = await StartOnCloseCompleted(
lockTaken, false, linkedCancellationToken).SuppressContextFlow();
}
catch (Exception)
{
// If an exception is thrown we know that the locks have been released,
// because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield
ResetFlagAndTakeLock(_thisLock, ref lockTaken);
throw;
}
if (callCompleteOnCloseCompleted)
{
ResetFlagAndTakeLock(_thisLock, ref lockTaken);
FinishOnCloseCompleted();
}
}
if (IsStateTerminal(State))
{
return;
}
linkedCancellationToken = CancellationToken.None;
bool ownsReceiveCancellationTokenSource = _receiveOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken);
if (ownsReceiveCancellationTokenSource)
{
_closeAsyncStartedReceive = true;
ArraySegment<byte> closeMessageBuffer =
new ArraySegment<byte>(new byte[WebSocketBuffer.MinReceiveBufferSize]);
EnsureReceiveOperation();
Task<WebSocketReceiveResult> receiveAsyncTask = _receiveOperation.Process(closeMessageBuffer,
linkedCancellationToken);
ReleaseLock(_thisLock, ref lockTaken);
WebSocketReceiveResult receiveResult = null;
try
{
receiveResult = await receiveAsyncTask.SuppressContextFlow();
}
catch (Exception receiveException)
{
Monitor.Enter(_thisLock, ref lockTaken);
if (!CanHandleExceptionDuringClose(receiveException))
{
ThrowIfConvertibleException(nameof(CloseAsync),
receiveException,
cancellationToken,
linkedCancellationToken.IsCancellationRequested);
throw;
}
}
// receiveResult is NEVER NULL if WebSocketBase.ReceiveOperation.Process completes successfully
// - but in the close code path we handle some exception if another thread was able to tranistion
// the state into Closed successfully. In this case receiveResult can be NULL and it is safe to
// skip the statements in the if-block.
if (receiveResult != null)
{
if (NetEventSource.IsEnabled && receiveResult.Count > 0)
{
NetEventSource.DumpBuffer(this, closeMessageBuffer.Array, closeMessageBuffer.Offset, receiveResult.Count);
}
if (receiveResult.MessageType != WebSocketMessageType.Close)
{
throw new WebSocketException(WebSocketError.InvalidMessageType,
SR.Format(SR.net_WebSockets_InvalidMessageType,
typeof(WebSocket).Name + "." + nameof(CloseAsync),
typeof(WebSocket).Name + "." + nameof(CloseOutputAsync),
receiveResult.MessageType));
}
}
}
else
{
_receiveOutstandingOperationHelper.CompleteOperation(ownsReceiveCancellationTokenSource);
ReleaseLock(_thisLock, ref lockTaken);
await _closeReceivedTaskCompletionSource.Task.SuppressContextFlow();
}
// When ownsReceiveCancellationTokenSource is true and an exception is thrown, the lock will be taken.
// So this logic here is to avoid taking the lock twice.
if (!lockTaken)
{
Monitor.Enter(_thisLock, ref lockTaken);
}
if (!IsStateTerminal(State))
{
bool ownsSendCancellationSource = false;
try
{
// We know that the CloseFrame has been sent at this point. So no Send-operation is allowed anymore and we
// can hijack the _SendOutstandingOperationHelper to create a linkedCancellationToken
ownsSendCancellationSource = _sendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken);
Debug.Assert(ownsSendCancellationSource, "'ownsSendCancellationSource' MUST be 'true' at this point.");
bool callCompleteOnCloseCompleted = false;
try
{
// linkedCancellationToken can be CancellationToken.None if ownsCloseCancellationTokenSource==false
// This is still ok because OnCloseOutputCompleted won't start any IO operation in this case
callCompleteOnCloseCompleted = await StartOnCloseCompleted(
lockTaken, false, linkedCancellationToken).SuppressContextFlow();
}
catch (Exception)
{
// If an exception is thrown we know that the locks have been released,
// because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield
ResetFlagAndTakeLock(_thisLock, ref lockTaken);
throw;
}
if (callCompleteOnCloseCompleted)
{
ResetFlagAndTakeLock(_thisLock, ref lockTaken);
FinishOnCloseCompleted();
}
}
finally
{
_sendOutstandingOperationHelper.CompleteOperation(ownsSendCancellationSource);
}
}
}
catch (Exception exception)
{
bool aborted = linkedCancellationToken.IsCancellationRequested;
Abort();
ThrowIfConvertibleException(nameof(CloseAsync), exception, cancellationToken, aborted);
throw;
}
finally
{
_closeOutstandingOperationHelper.CompleteOperation(ownsCloseCancellationTokenSource);
ReleaseLock(_thisLock, ref lockTaken);
}
}
finally
{
if (NetEventSource.IsEnabled)
{
NetEventSource.Exit(this, inputParameter);
}
}
}