internal async Task<WebSocketReceiveResult> Process(Nullable<ArraySegment<byte>> buffer,
CancellationToken cancellationToken)
{
Debug.Assert(BufferCount >= 1 && BufferCount <= 2, "'bufferCount' MUST ONLY BE '1' or '2'.");
bool sessionHandleLockTaken = false;
AsyncOperationCompleted = false;
ReceiveResult = null;
try
{
Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
_webSocket.ThrowIfPendingException();
Initialize(buffer, cancellationToken);
while (ShouldContinue(cancellationToken))
{
WebSocketProtocolComponent.Action action;
WebSocketProtocolComponent.BufferType bufferType;
bool completed = false;
while (!completed)
{
Interop.WebSocket.Buffer[] dataBuffers =
new Interop.WebSocket.Buffer[BufferCount];
uint dataBufferCount = (uint)BufferCount;
IntPtr actionContext;
_webSocket.ThrowIfDisposed();
WebSocketProtocolComponent.WebSocketGetAction(_webSocket,
ActionQueue,
dataBuffers,
ref dataBufferCount,
out action,
out bufferType,
out actionContext);
switch (action)
{
case WebSocketProtocolComponent.Action.NoAction:
if (ProcessAction_NoAction())
{
// A close frame was received
Debug.Assert(ReceiveResult.Count == 0, "'receiveResult.Count' MUST be 0.");
Debug.Assert(ReceiveResult.CloseStatus != null, "'receiveResult.CloseStatus' MUST NOT be NULL for message type 'Close'.");
bool thisLockTaken = false;
try
{
if (_webSocket.StartOnCloseReceived(ref thisLockTaken))
{
// If StartOnCloseReceived returns true the WebSocket close handshake has been completed
// so there is no need to retake the SessionHandle-lock.
// _ThisLock lock is guaranteed to be taken by StartOnCloseReceived when returning true
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
bool callCompleteOnCloseCompleted = false;
try
{
callCompleteOnCloseCompleted = await _webSocket.StartOnCloseCompleted(
thisLockTaken, sessionHandleLockTaken, cancellationToken).SuppressContextFlow();
}
catch (Exception)
{
// If an exception is thrown we know that the locks have been released,
// because we enforce IWebSocketStream.CloseNetworkConnectionAsync to yield
_webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken);
throw;
}
if (callCompleteOnCloseCompleted)
{
_webSocket.ResetFlagAndTakeLock(_webSocket._thisLock, ref thisLockTaken);
_webSocket.FinishOnCloseCompleted();
}
}
_webSocket.FinishOnCloseReceived(ReceiveResult.CloseStatus.Value, ReceiveResult.CloseStatusDescription);
}
finally
{
if (thisLockTaken)
{
ReleaseLock(_webSocket._thisLock, ref thisLockTaken);
}
}
}
completed = true;
break;
case WebSocketProtocolComponent.Action.IndicateReceiveComplete:
ProcessAction_IndicateReceiveComplete(buffer,
bufferType,
action,
dataBuffers,
dataBufferCount,
actionContext);
break;
case WebSocketProtocolComponent.Action.ReceiveFromNetwork:
int count = 0;
try
{
ArraySegment<byte> payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType);
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
WebSocketValidate.ThrowIfConnectionAborted(_webSocket._innerStream, true);
try
{
Task<int> readTask = _webSocket._innerStream.ReadAsync(payload.Array,
payload.Offset,
payload.Count,
cancellationToken);
count = await readTask.SuppressContextFlow();
_webSocket._keepAliveTracker.OnDataReceived();
}
catch (ObjectDisposedException objectDisposedException)
{
throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException);
}
catch (NotSupportedException notSupportedException)
{
throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException);
}
Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
_webSocket.ThrowIfPendingException();
// If the client unexpectedly closed the socket we throw an exception as we didn't get any close message
if (count == 0)
{
throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely);
}
}
finally
{
WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket,
actionContext,
count);
}
break;
case WebSocketProtocolComponent.Action.IndicateSendComplete:
WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket, actionContext, 0);
AsyncOperationCompleted = true;
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
await _webSocket._innerStream.FlushAsync().SuppressContextFlow();
Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
break;
case WebSocketProtocolComponent.Action.SendToNetwork:
int bytesSent = 0;
try
{
if (_webSocket.State != WebSocketState.CloseSent ||
(bufferType != WebSocketProtocolComponent.BufferType.PingPong &&
bufferType != WebSocketProtocolComponent.BufferType.UnsolicitedPong))
{
if (dataBufferCount == 0)
{
break;
}
List<ArraySegment<byte>> sendBuffers = new List<ArraySegment<byte>>((int)dataBufferCount);
int sendBufferSize = 0;
ArraySegment<byte> framingBuffer = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType);
sendBuffers.Add(framingBuffer);
sendBufferSize += framingBuffer.Count;
// There can be at most 2 dataBuffers
// - one for the framing header and one for the payload
if (dataBufferCount == 2)
{
ArraySegment<byte> payload;
// The second buffer might be from the pinned send payload buffer (1) or from the
// internal native buffer (2). In the case of a PONG response being generated, the buffer
// would be from (2). Even if the payload is from a WebSocketSend operation, the buffer
// might be (1) only if no buffer copies were needed (in the case of no masking, for example).
// Or it might be (2). So, we need to check.
if (_webSocket._internalBuffer.IsPinnedSendPayloadBuffer(dataBuffers[1], bufferType))
{
payload = _webSocket._internalBuffer.ConvertPinnedSendPayloadFromNative(dataBuffers[1], bufferType);
}
else
{
payload = _webSocket._internalBuffer.ConvertNativeBuffer(action, dataBuffers[1], bufferType);
}
sendBuffers.Add(payload);
sendBufferSize += payload.Count;
}
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
WebSocketValidate.ThrowIfConnectionAborted(_webSocket._innerStream, false);
await _webSocket.SendFrameAsync(sendBuffers, cancellationToken).SuppressContextFlow();
Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
_webSocket.ThrowIfPendingException();
bytesSent += sendBufferSize;
_webSocket._keepAliveTracker.OnDataSent();
}
}
finally
{
WebSocketProtocolComponent.WebSocketCompleteAction(_webSocket,
actionContext,
bytesSent);
}
break;
default:
string assertMessage = string.Format(CultureInfo.InvariantCulture,
"Invalid action '{0}' returned from WebSocketGetAction.",
action);
Debug.Assert(false, assertMessage);
throw new InvalidOperationException();
}
}
// WebSocketGetAction has returned NO_ACTION. In general, WebSocketGetAction will return
// NO_ACTION if there is no work item available to process at the current moment. But
// there could be work items on the queue still. Those work items can't be returned back
// until the current work item (being done by another thread) is complete.
//
// It's possible that another thread might be finishing up an async operation and needs
// to call WebSocketCompleteAction. Once that happens, calling WebSocketGetAction on this
// thread might return something else to do. This happens, for example, if the RECEIVE thread
// ends up having to begin sending out a PONG response (due to it receiving a PING) and the
// current SEND thread has posted a WebSocketSend but it can't be processed yet until the
// RECEIVE thread has finished sending out the PONG response.
//
// So, we need to release the lock briefly to give the other thread a chance to finish
// processing. We won't actually exit this outter loop and return from this async method
// until the caller's async operation has been fully completed.
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
Monitor.Enter(_webSocket.SessionHandle, ref sessionHandleLockTaken);
}
}
finally
{
Cleanup();
ReleaseLock(_webSocket.SessionHandle, ref sessionHandleLockTaken);
}
return ReceiveResult;
}