internal static void UnwrapWebSocketBuffer(WebSocketProtocolComponent.Buffer buffer, WebSocketProtocolComponent.BufferType bufferType, out IntPtr bufferData, out uint bufferLength) { bufferData = IntPtr.Zero; bufferLength = 0; switch (bufferType) { case WebSocketProtocolComponent.BufferType.Close: bufferData = buffer.CloseStatus.ReasonData; bufferLength = buffer.CloseStatus.ReasonLength; break; case WebSocketProtocolComponent.BufferType.None: case WebSocketProtocolComponent.BufferType.BinaryFragment: case WebSocketProtocolComponent.BufferType.BinaryMessage: case WebSocketProtocolComponent.BufferType.UTF8Fragment: case WebSocketProtocolComponent.BufferType.UTF8Message: case WebSocketProtocolComponent.BufferType.PingPong: case WebSocketProtocolComponent.BufferType.UnsolicitedPong: bufferData = buffer.Data.BufferData; bufferLength = buffer.Data.BufferLength; break; default: Contract.Assert(false, string.Format(CultureInfo.InvariantCulture, "BufferType '{0}' is invalid/unknown.", bufferType)); break; } }
internal ArraySegment <byte> ConvertNativeBuffer(WebSocketProtocolComponent.Action action, WebSocketProtocolComponent.Buffer buffer, WebSocketProtocolComponent.BufferType bufferType) { ThrowIfDisposed(); IntPtr bufferData; uint bufferLength; UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferLength); if (bufferData == IntPtr.Zero) { return(WebSocketHelpers.EmptyPayload); } if (this.IsNativeBuffer(bufferData, bufferLength)) { return(new ArraySegment <byte>(m_InternalBuffer.Array, this.GetOffset(bufferData), (int)bufferLength)); } Contract.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer."); // Indicates a violation in the contract with native Websocket.dll and could indicate // memory corruption because the internal buffer is shared between managed and native code throw new AccessViolationException(); }
// This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation private bool IsPinnedSendPayloadBuffer(WebSocketProtocolComponent.Buffer buffer, WebSocketProtocolComponent.BufferType bufferType) { if (m_SendBufferState != SendBufferState.SendPayloadSpecified) { return(false); } IntPtr bufferData; uint bufferSize; UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize); long nativeBufferStartAddress = bufferData.ToInt64(); long nativeBufferEndAddress = nativeBufferStartAddress + bufferSize; return(nativeBufferStartAddress >= m_PinnedSendBufferStartAddress && nativeBufferEndAddress >= m_PinnedSendBufferStartAddress && nativeBufferStartAddress <= m_PinnedSendBufferEndAddress && nativeBufferEndAddress <= m_PinnedSendBufferEndAddress); }
internal void ConvertCloseBuffer(WebSocketProtocolComponent.Action action, WebSocketProtocolComponent.Buffer buffer, out WebSocketCloseStatus closeStatus, out string reason) { ThrowIfDisposed(); IntPtr bufferData; uint bufferLength; closeStatus = (WebSocketCloseStatus)buffer.CloseStatus.CloseStatus; UnwrapWebSocketBuffer(buffer, WebSocketProtocolComponent.BufferType.Close, out bufferData, out bufferLength); if (bufferData == IntPtr.Zero) { reason = null; } else { ArraySegment <byte> reasonBlob; if (this.IsNativeBuffer(bufferData, bufferLength)) { reasonBlob = new ArraySegment <byte>(m_InternalBuffer.Array, this.GetOffset(bufferData), (int)bufferLength); } else { Contract.Assert(false, "'buffer' MUST reference a memory segment within the pinned InternalBuffer."); // Indicates a violation in the contract with native Websocket.dll and could indicate // memory corruption because the internal buffer is shared between managed and native code throw new AccessViolationException(); } // No need to wrap DecoderFallbackException for invalid UTF8 chacters, because // Encoding.UTF8 will not throw but replace invalid characters instead. reason = Encoding.UTF8.GetString(reasonBlob.Array, reasonBlob.Offset, reasonBlob.Count); } }
// This method is not thread safe. It must only be called after enforcing at most 1 outstanding send operation internal ArraySegment <byte> ConvertPinnedSendPayloadFromNative(WebSocketProtocolComponent.Buffer buffer, WebSocketProtocolComponent.BufferType bufferType) { if (!IsPinnedSendPayloadBuffer(buffer, bufferType)) { // Indicates a violation in the API contract that could indicate // memory corruption because the pinned sendbuffer is shared between managed and native code throw new AccessViolationException(); } Contract.Assert(Marshal.UnsafeAddrOfPinnedArrayElement(m_PinnedSendBuffer.Array, m_PinnedSendBuffer.Offset).ToInt64() == m_PinnedSendBufferStartAddress, "'m_PinnedSendBuffer.Array' MUST be pinned during the entire send operation."); IntPtr bufferData; uint bufferSize; UnwrapWebSocketBuffer(buffer, bufferType, out bufferData, out bufferSize); int internalOffset = (int)(bufferData.ToInt64() - m_PinnedSendBufferStartAddress); return(new ArraySegment <byte>(m_PinnedSendBuffer.Array, m_PinnedSendBuffer.Offset + internalOffset, (int)bufferSize)); }
internal void ValidateNativeBuffers(WebSocketProtocolComponent.Action action, WebSocketProtocolComponent.BufferType bufferType, WebSocketProtocolComponent.Buffer[] dataBuffers, uint dataBufferCount) { Contract.Assert(dataBufferCount <= (uint)int.MaxValue, "'dataBufferCount' MUST NOT be bigger than Int32.MaxValue."); Contract.Assert(dataBuffers != null, "'dataBuffers' MUST NOT be NULL."); ThrowIfDisposed(); if (dataBufferCount > dataBuffers.Length) { Contract.Assert(false, "'dataBufferCount' MUST NOT be bigger than 'dataBuffers.Length'."); // Indicates a violation in the contract with native Websocket.dll and could indicate // memory corruption because the internal buffer is shared between managed and native code throw new AccessViolationException(); } int count = dataBuffers.Length; bool isSendActivity = action == WebSocketProtocolComponent.Action.IndicateSendComplete || action == WebSocketProtocolComponent.Action.SendToNetwork; if (isSendActivity) { count = (int)dataBufferCount; } bool nonZeroBufferFound = false; for (int i = 0; i < count; i++) { WebSocketProtocolComponent.Buffer dataBuffer = dataBuffers[i]; IntPtr bufferData; uint bufferLength; UnwrapWebSocketBuffer(dataBuffer, bufferType, out bufferData, out bufferLength); if (bufferData == IntPtr.Zero) { continue; } nonZeroBufferFound = true; bool isPinnedSendPayloadBuffer = IsPinnedSendPayloadBuffer(dataBuffer, bufferType); if (bufferLength > GetMaxBufferSize()) { if (!isSendActivity || !isPinnedSendPayloadBuffer) { Contract.Assert(false, "'dataBuffer.BufferLength' MUST NOT be bigger than 'm_ReceiveBufferSize' and 'm_SendBufferSize'."); // Indicates a violation in the contract with native Websocket.dll and could indicate // memory corruption because the internal buffer is shared between managed and native code throw new AccessViolationException(); } } if (!isPinnedSendPayloadBuffer && !IsNativeBuffer(bufferData, bufferLength)) { Contract.Assert(false, "WebSocketGetAction MUST return a pointer within the pinned internal buffer."); // Indicates a violation in the contract with native Websocket.dll and could indicate // memory corruption because the internal buffer is shared between managed and native code throw new AccessViolationException(); } } if (!nonZeroBufferFound && action != WebSocketProtocolComponent.Action.NoAction && action != WebSocketProtocolComponent.Action.IndicateReceiveComplete && action != WebSocketProtocolComponent.Action.IndicateSendComplete) { Contract.Assert(false, "At least one 'dataBuffer.Buffer' MUST NOT be NULL."); } }
protected override Nullable<WebSocketProtocolComponent.Buffer> CreateBuffer(Nullable<ArraySegment<byte>> buffer) { Contract.Assert(buffer == null, "'buffer' MUST BE NULL."); m_WebSocket.ThrowIfDisposed(); m_WebSocket.ThrowIfPendingException(); if (CloseStatus == WebSocketCloseStatus.Empty) { return null; } WebSocketProtocolComponent.Buffer payloadBuffer = new WebSocketProtocolComponent.Buffer(); if (CloseReason != null) { byte[] blob = UTF8Encoding.UTF8.GetBytes(CloseReason); Contract.Assert(blob.Length <= WebSocketHelpers.MaxControlFramePayloadLength, "The close reason is too long."); ArraySegment<byte> closeBuffer = new ArraySegment<byte>(blob, 0, Math.Min(WebSocketHelpers.MaxControlFramePayloadLength, blob.Length)); m_WebSocket.m_InternalBuffer.PinSendBuffer(closeBuffer, out m_BufferHasBeenPinned); payloadBuffer.CloseStatus.ReasonData = m_WebSocket.m_InternalBuffer.ConvertPinnedSendPayloadToNative(closeBuffer); payloadBuffer.CloseStatus.ReasonLength = (uint)closeBuffer.Count; } payloadBuffer.CloseStatus.CloseStatus = (ushort)CloseStatus; return payloadBuffer; }
protected virtual Nullable<WebSocketProtocolComponent.Buffer> CreateBuffer(Nullable<ArraySegment<byte>> buffer) { if (buffer == null) { return null; } WebSocketProtocolComponent.Buffer payloadBuffer; payloadBuffer = new WebSocketProtocolComponent.Buffer(); m_WebSocket.m_InternalBuffer.PinSendBuffer(buffer.Value, out m_BufferHasBeenPinned); payloadBuffer.Data.BufferData = m_WebSocket.m_InternalBuffer.ConvertPinnedSendPayloadToNative(buffer.Value); payloadBuffer.Data.BufferLength = (uint)buffer.Value.Count; return payloadBuffer; }
internal async Task<WebSocketReceiveResult> Process(Nullable<ArraySegment<byte>> buffer, CancellationToken cancellationToken) { Contract.Assert(BufferCount >= 1 && BufferCount <= 2, "'bufferCount' MUST ONLY BE '1' or '2'."); bool sessionHandleLockTaken = false; AsyncOperationCompleted = false; ReceiveResult = null; try { Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_WebSocket.ThrowIfPendingException(); Initialize(buffer, cancellationToken); while (ShouldContinue(cancellationToken)) { WebSocketProtocolComponent.Action action; WebSocketProtocolComponent.BufferType bufferType; bool completed = false; while (!completed) { WebSocketProtocolComponent.Buffer[] dataBuffers = new WebSocketProtocolComponent.Buffer[BufferCount]; uint dataBufferCount = (uint)BufferCount; IntPtr actionContext; m_WebSocket.ThrowIfDisposed(); WebSocketProtocolComponent.WebSocketGetAction(m_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 Contract.Assert(ReceiveResult.Count == 0, "'receiveResult.Count' MUST be 0."); Contract.Assert(ReceiveResult.CloseStatus != null, "'receiveResult.CloseStatus' MUST NOT be NULL for message type 'Close'."); bool thisLockTaken = false; try { if (m_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. // m_ThisLock lock is guaranteed to be taken by StartOnCloseReceived when returning true ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); bool callCompleteOnCloseCompleted = false; try { callCompleteOnCloseCompleted = await m_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 m_WebSocket.ResetFlagAndTakeLock(m_WebSocket.m_ThisLock, ref thisLockTaken); throw; } if (callCompleteOnCloseCompleted) { m_WebSocket.ResetFlagAndTakeLock(m_WebSocket.m_ThisLock, ref thisLockTaken); m_WebSocket.FinishOnCloseCompleted(); } } m_WebSocket.FinishOnCloseReceived(ReceiveResult.CloseStatus.Value, ReceiveResult.CloseStatusDescription); } finally { if (thisLockTaken) { ReleaseLock(m_WebSocket.m_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 = m_WebSocket.m_InternalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType); ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); WebSocketHelpers.ThrowIfConnectionAborted(m_WebSocket.m_InnerStream, true); try { Task<int> readTask = m_WebSocket.m_InnerStream.ReadAsync(payload.Array, payload.Offset, payload.Count, cancellationToken); count = await readTask.SuppressContextFlow(); m_WebSocket.m_KeepAliveTracker.OnDataReceived(); } catch (ObjectDisposedException objectDisposedException) { throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException); } catch (NotSupportedException notSupportedException) { throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException); } Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_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(m_WebSocket, actionContext, count); } break; case WebSocketProtocolComponent.Action.IndicateSendComplete: WebSocketProtocolComponent.WebSocketCompleteAction(m_WebSocket, actionContext, 0); AsyncOperationCompleted = true; ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); await m_WebSocket.m_InnerStream.FlushAsync().SuppressContextFlow(); Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); break; case WebSocketProtocolComponent.Action.SendToNetwork: int bytesSent = 0; try { if (m_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 = m_WebSocket.m_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 (m_WebSocket.m_InternalBuffer.IsPinnedSendPayloadBuffer(dataBuffers[1], bufferType)) { payload = m_WebSocket.m_InternalBuffer.ConvertPinnedSendPayloadFromNative(dataBuffers[1], bufferType); } else { payload = m_WebSocket.m_InternalBuffer.ConvertNativeBuffer(action, dataBuffers[1], bufferType); } sendBuffers.Add(payload); sendBufferSize += payload.Count; } ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); WebSocketHelpers.ThrowIfConnectionAborted(m_WebSocket.m_InnerStream, false); await m_WebSocket.SendFrameAsync(sendBuffers, cancellationToken).SuppressContextFlow(); Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_WebSocket.ThrowIfPendingException(); bytesSent += sendBufferSize; m_WebSocket.m_KeepAliveTracker.OnDataSent(); } } finally { WebSocketProtocolComponent.WebSocketCompleteAction(m_WebSocket, actionContext, bytesSent); } break; default: string assertMessage = string.Format(CultureInfo.InvariantCulture, "Invalid action '{0}' returned from WebSocketGetAction.", action); Contract.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(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); } } finally { Cleanup(); ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); } return ReceiveResult; }
internal async Task<WebSocketReceiveResult> Process(Nullable<ArraySegment<byte>> buffer, CancellationToken cancellationToken) { Contract.Assert(BufferCount >= 1 && BufferCount <= 2, "'bufferCount' MUST ONLY BE '1' or '2'."); bool sessionHandleLockTaken = false; ReceiveResult = null; try { Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_WebSocket.ThrowIfPendingException(); Initialize(buffer, cancellationToken); while (ShouldContinue(cancellationToken)) { WebSocketProtocolComponent.Action action; WebSocketProtocolComponent.BufferType bufferType; bool completed = false; while (!completed) { WebSocketProtocolComponent.Buffer[] dataBuffers = new WebSocketProtocolComponent.Buffer[BufferCount]; uint dataBufferCount = (uint)BufferCount; IntPtr actionContext; m_WebSocket.ThrowIfDisposed(); WebSocketProtocolComponent.WebSocketGetAction(m_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 Contract.Assert(ReceiveResult.Count == 0, "'receiveResult.Count' MUST be 0."); Contract.Assert(ReceiveResult.CloseStatus != null, "'receiveResult.CloseStatus' MUST NOT be NULL for message type 'Close'."); bool thisLockTaken = false; try { if (m_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. // m_ThisLock lock is guaranteed to be taken by StartOnCloseReceived when returning true ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); bool callCompleteOnCloseCompleted = false; try { callCompleteOnCloseCompleted = await m_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 m_WebSocket.ResetFlagAndTakeLock(m_WebSocket.m_ThisLock, ref thisLockTaken); throw; } if (callCompleteOnCloseCompleted) { m_WebSocket.ResetFlagAndTakeLock(m_WebSocket.m_ThisLock, ref thisLockTaken); m_WebSocket.FinishOnCloseCompleted(); } } m_WebSocket.FinishOnCloseReceived(ReceiveResult.CloseStatus.Value, ReceiveResult.CloseStatusDescription); } finally { if (thisLockTaken) { ReleaseLock(m_WebSocket.m_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 = m_WebSocket.m_InternalBuffer.ConvertNativeBuffer(action, dataBuffers[0], bufferType); ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); WebSocketHelpers.ThrowIfConnectionAborted(m_WebSocket.m_InnerStream, true); try { Task<int> readTask = m_WebSocket.m_InnerStream.ReadAsync(payload.Array, payload.Offset, payload.Count, cancellationToken); count = await readTask.SuppressContextFlow(); m_WebSocket.m_KeepAliveTracker.OnDataReceived(); } catch (ObjectDisposedException objectDisposedException) { throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, objectDisposedException); } catch (NotSupportedException notSupportedException) { throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, notSupportedException); } Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_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(m_WebSocket, actionContext, count); } break; case WebSocketProtocolComponent.Action.IndicateSendComplete: WebSocketProtocolComponent.WebSocketCompleteAction(m_WebSocket, actionContext, 0); ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); await m_WebSocket.m_InnerStream.FlushAsync().SuppressContextFlow(); Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); break; case WebSocketProtocolComponent.Action.SendToNetwork: int bytesSent = 0; try { if (m_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 = m_WebSocket.m_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 = m_WebSocket.m_InternalBuffer.ConvertPinnedSendPayloadFromNative(dataBuffers[1], bufferType); sendBuffers.Add(payload); sendBufferSize += payload.Count; } ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); WebSocketHelpers.ThrowIfConnectionAborted(m_WebSocket.m_InnerStream, false); await m_WebSocket.SendFrameAsync(sendBuffers, cancellationToken).SuppressContextFlow(); Monitor.Enter(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); m_WebSocket.ThrowIfPendingException(); bytesSent += sendBufferSize; m_WebSocket.m_KeepAliveTracker.OnDataSent(); } } finally { WebSocketProtocolComponent.WebSocketCompleteAction(m_WebSocket, actionContext, bytesSent); } break; default: string assertMessage = string.Format(CultureInfo.InvariantCulture, "Invalid action '{0}' returned from WebSocketGetAction.", action); Contract.Assert(false, assertMessage); throw new InvalidOperationException(); } } } } finally { Cleanup(); ReleaseLock(m_WebSocket.SessionHandle, ref sessionHandleLockTaken); } return ReceiveResult; }