private void FreeReceiveQueue(ReceiveQueueItem item) { while (item != null && item.Buffer != null) { connection.config.BufferPool.Return(item.Buffer); item.Buffer = null; var current = item; item = item.Next; current.Next = null; } }
internal ValueTask <ConnectionWriter.WriteResult> Reset( ErrorCode errorCode, bool fromRemote) { ValueTask <ConnectionWriter.WriteResult> writeResetTask = new ValueTask <ConnectionWriter.WriteResult>( ConnectionWriter.WriteResult.Success); lock (stateMutex) { if (state == StreamState.Reset || state == StreamState.Closed) { return(writeResetTask); } state = StreamState.Reset; var head = receiveQueueHead; receiveQueueHead = null; FreeReceiveQueue(head); } if (!fromRemote) { var fh = new FrameHeader { StreamId = this.Id, Type = FrameType.ResetStream, Flags = 0, }; var resetData = new ResetFrameData { ErrorCode = errorCode }; writeResetTask = connection.writer.WriteResetStream(fh, resetData); } else { connection.writer.RemoveStream(this.Id); } if (!fromRemote) { this.connection.UnregisterStream(this); } readDataPossible.Set(); readTrailersPossible.Set(); readHeadersPossible.Set(); return(writeResetTask); }
private void EnqueueReceiveQueueItem(ReceiveQueueItem newItem) { if (receiveQueueHead == null) { receiveQueueHead = newItem; } else { var current = receiveQueueHead; var next = receiveQueueHead.Next; while (next != null) { current = next; next = current.Next; } current.Next = newItem; } }
/// <summary> /// 处理数据帧的接收。 /// </summary> /// <param name="buffer"></param> /// <param name="endOfStream"></param> /// <param name="tookBufferOwnership"></param> /// <returns></returns> public Http2Error?PushBuffer( ArraySegment <byte> buffer, bool endOfStream, out bool tookBufferOwnership) { tookBufferOwnership = false; var wakeupDataWaiter = false; var wakeupTrailerWaiter = false; var removeStream = false; lock (stateMutex) { switch (state) { case StreamState.ReservedLocal: case StreamState.ReservedRemote: throw new NotImplementedException(); case StreamState.Open: case StreamState.HalfClosedLocal: if (headersReceived != HeaderReceptionState.ReceivedAllHeaders) { return(new Http2Error { StreamId = Id, Code = ErrorCode.ProtocolError, Message = "在所有头之前接收到数据", }); } if (buffer.Count > 0) { if (buffer.Count > receiveWindow) { return(new Http2Error { StreamId = Id, Code = ErrorCode.FlowControlError, Message = "超过接收窗口", }); } receiveWindow -= buffer.Count; var newItem = new ReceiveQueueItem(buffer); EnqueueReceiveQueueItem(newItem); wakeupDataWaiter = true; tookBufferOwnership = true; } dataReceived = true; totalInData += buffer.Count; if (endOfStream && declaredInContentLength >= 0 && declaredInContentLength != totalInData) { return(new Http2Error { StreamId = Id, Code = ErrorCode.ProtocolError, Message = "数据帧的长度与内容长度不匹配", }); } if (endOfStream) { if (state == StreamState.HalfClosedLocal) { state = StreamState.Closed; removeStream = true; } else { state = StreamState.HalfClosedRemote; } wakeupTrailerWaiter = true; wakeupDataWaiter = true; } break; case StreamState.Idle: case StreamState.HalfClosedRemote: case StreamState.Closed: return(new Http2Error { StreamId = Id, Code = ErrorCode.StreamClosed, Message = "已接收封闭流的数据", }); case StreamState.Reset: break; default: throw new Exception("未处理的流状态"); } } if (wakeupDataWaiter) { readDataPossible.Set(); } if (wakeupTrailerWaiter) { readTrailersPossible.Set(); } if (removeStream) { connection.UnregisterStream(this); } return(null); }
public async ValueTask <StreamReadResult> ReadAsync(ArraySegment <byte> buffer) { while (true) { await readDataPossible; int windowUpdateAmount = 0; StreamReadResult result = new StreamReadResult(); bool hasResult = false; lock (stateMutex) { if (state == StreamState.Reset) { throw new StreamResetException(); } var streamClosedFromRemote = state == StreamState.Closed || state == StreamState.HalfClosedRemote; if (receiveQueueHead != null) { var offset = buffer.Offset; var count = buffer.Count; while (receiveQueueHead != null && count > 0) { var toCopy = Math.Min(receiveQueueHead.Count, count); Array.Copy( receiveQueueHead.Buffer, receiveQueueHead.Offset, buffer.Array, offset, toCopy); offset += toCopy; count -= toCopy; if (toCopy == receiveQueueHead.Count) { connection.config.BufferPool.Return( receiveQueueHead.Buffer); receiveQueueHead = receiveQueueHead.Next; } else { receiveQueueHead.Offset += toCopy; receiveQueueHead.Count -= toCopy; break; } } if (!streamClosedFromRemote) { var isFree = totalReceiveWindow - ReceiveQueueLength; var possibleWindowUpdate = isFree - receiveWindow; if (possibleWindowUpdate >= (totalReceiveWindow / 2)) { windowUpdateAmount = possibleWindowUpdate; receiveWindow += windowUpdateAmount; } } result = new StreamReadResult { BytesRead = offset - buffer.Offset, EndOfStream = false, }; hasResult = true; if (receiveQueueHead == null && !streamClosedFromRemote) { readDataPossible.Reset(); } } else if (streamClosedFromRemote) { result = new StreamReadResult { BytesRead = 0, EndOfStream = true, }; hasResult = true; } } if (hasResult) { if (windowUpdateAmount > 0) { await SendWindowUpdate(windowUpdateAmount); } return(result); } } }
/// <summary> /// Processes the reception of a DATA frame. /// The connection is responsible for checking the maximum frame length /// before calling this function. /// </summary> public Http2Error?PushBuffer( ArraySegment <byte> buffer, bool endOfStream, out bool tookBufferOwnership) { tookBufferOwnership = false; var wakeupDataWaiter = false; var wakeupTrailerWaiter = false; var removeStream = false; lock (stateMutex) { // Data frames are not valid in all states switch (state) { case StreamState.ReservedLocal: case StreamState.ReservedRemote: // Push promises are currently not implemented // At the moment these should already been // rejected in the Connection. // This needs to be reviewed later on throw new NotImplementedException(); case StreamState.Open: case StreamState.HalfClosedLocal: if (headersReceived != HeaderReceptionState.ReceivedAllHeaders) { // Received DATA without HEADERS before. // State Open can also mean we only have sent // headers but not received them. // Therefore checking the state alone isn't sufficient. return(new Http2Error { StreamId = Id, Code = ErrorCode.ProtocolError, Message = "Received data before all headers", }); } // Only enqueue DATA frames with a real content length // of at least 1 byte, otherwise the reader is needlessly // woken up. // Empty DATA frames are also not checked against the // flow control window, which means they are valid even // in case of a negative flow control window (which is // not possible in the current state of the implementation). // However we still treat empty DATA frames as a valid // separator between HEADERS and trailing HEADERS. if (buffer.Count > 0) { // Check if the flow control window is exceeded if (buffer.Count > receiveWindow) { return(new Http2Error { StreamId = Id, Code = ErrorCode.FlowControlError, Message = "Received window exceeded", }); } if (connection.logger != null && connection.logger.IsEnabled(LogLevel.Trace)) { connection.logger.LogTrace( "Incoming flow control window update:\n" + " Stream {0} window: {1} -> {2}", Id, receiveWindow, receiveWindow - buffer.Count); } receiveWindow -= buffer.Count; // Enqueue the data at the end of the receive queue // TODO: Instead of appending buffers with only small // content on the end of each other we might to concat // the buffers together, which avoids the worst-case // scenario: The remote side sending us lots of 1byte // DATA frames, where we need a queue item for each // one. var newItem = new ReceiveQueueItem(buffer); EnqueueReceiveQueueItem(newItem); wakeupDataWaiter = true; tookBufferOwnership = true; } // Allow trailing headers afterwards dataReceived = true; // Check if data matches declared content-length // TODO: What should be done if the declared // content-length was invalid (-2)? totalInData += buffer.Count; if (endOfStream && declaredInContentLength >= 0 && declaredInContentLength != totalInData) { return(new Http2Error { StreamId = Id, Code = ErrorCode.ProtocolError, Message = "Length of DATA frames does not match content-length", }); } // All checks OK so far, wakeup waiter and handle state changes // Handle state changes that are caused by DATA frames if (endOfStream) { if (state == StreamState.HalfClosedLocal) { state = StreamState.Closed; removeStream = true; } else // Open { state = StreamState.HalfClosedRemote; } wakeupTrailerWaiter = true; wakeupDataWaiter = true; } break; case StreamState.Idle: case StreamState.HalfClosedRemote: case StreamState.Closed: // Received a DATA frame for a stream that was // already closed or not properly opened from remote side. // That's not valid return(new Http2Error { StreamId = Id, Code = ErrorCode.StreamClosed, Message = "Received data for closed stream", }); case StreamState.Reset: // The stream was already reset // What we really should do here depends on the previous state, // which is not stored for efficiency. If we reset the // stream late headers are ok. If the remote resetted it // this is a protocol error for the stream. // As it does not really matter just ignore the frame. break; default: throw new Exception("Unhandled stream state"); } } // Wakeup any blocked calls that are waiting on data or end of stream if (wakeupDataWaiter) { // Wakeup any blocked call that waits for headers to get available readDataPossible.Set(); } if (wakeupTrailerWaiter) { readTrailersPossible.Set(); } if (removeStream) { connection.UnregisterStream(this); } return(null); }
public async ValueTask <StreamReadResult> ReadAsync(ArraySegment <byte> buffer) { while (true) { await readDataPossible; int windowUpdateAmount = 0; StreamReadResult result = new StreamReadResult(); bool hasResult = false; lock (stateMutex) { if (state == StreamState.Reset) { throw new StreamResetException(); } var streamClosedFromRemote = state == StreamState.Closed || state == StreamState.HalfClosedRemote; if (receiveQueueHead != null) { // Copy as much data as possible from internal queue into // user buffer var offset = buffer.Offset; var count = buffer.Count; while (receiveQueueHead != null && count > 0) { // Copy data from receive buffer to target var toCopy = Math.Min(receiveQueueHead.Count, count); Array.Copy( receiveQueueHead.Buffer, receiveQueueHead.Offset, buffer.Array, offset, toCopy); offset += toCopy; count -= toCopy; if (toCopy == receiveQueueHead.Count) { connection.config.BufferPool.Return( receiveQueueHead.Buffer); receiveQueueHead = receiveQueueHead.Next; } else { receiveQueueHead.Offset += toCopy; receiveQueueHead.Count -= toCopy; break; } } // Calculate whether we should send a window update frame // after the read is complete. // Only need to do this if the stream has not yet ended if (!streamClosedFromRemote) { var isFree = totalReceiveWindow - ReceiveQueueLength; var possibleWindowUpdate = isFree - receiveWindow; if (possibleWindowUpdate >= (totalReceiveWindow / 2)) { windowUpdateAmount = possibleWindowUpdate; receiveWindow += windowUpdateAmount; if (connection.logger != null && connection.logger.IsEnabled(LogLevel.Trace)) { connection.logger.LogTrace( "Incoming flow control window update:\n" + " Stream {0} window: {1} -> {2}", Id, receiveWindow - windowUpdateAmount, receiveWindow); } } } result = new StreamReadResult { BytesRead = offset - buffer.Offset, EndOfStream = false, }; hasResult = true; if (receiveQueueHead == null && !streamClosedFromRemote) { // If all data was consumed the next read must be blocked // until more data comes in or the stream gets closed or reset readDataPossible.Reset(); } } else if (streamClosedFromRemote) { // Deliver a notification that the stream was closed result = new StreamReadResult { BytesRead = 0, EndOfStream = true, }; hasResult = true; } } if (hasResult) { if (windowUpdateAmount > 0) { // We need to send a window update frame before delivering // the result await SendWindowUpdate(windowUpdateAmount); } return(result); } } }
internal ValueTask <ConnectionWriter.WriteResult> Reset( ErrorCode errorCode, bool fromRemote) { ValueTask <ConnectionWriter.WriteResult> writeResetTask = new ValueTask <ConnectionWriter.WriteResult>( ConnectionWriter.WriteResult.Success); lock (stateMutex) { if (state == StreamState.Reset || state == StreamState.Closed) { // Already reset or fully closed return(writeResetTask); } state = StreamState.Reset; // Free the receive queue var head = receiveQueueHead; receiveQueueHead = null; FreeReceiveQueue(head); } if (connection.logger != null) { connection.logger.LogTrace( "Resetted stream {0} with error code {1}", Id, errorCode); } // Remark: Even if we are here in IDLE state we need to send the // RESET frame. The reason for this is that if we receive a header // for a new stream which is invalid a StreamImpl instance will be // created and put into IDLE state. The header processing will fail // and Reset will get called. As the remote thinks we are in Open // state we must send a RST_STREAM. if (!fromRemote) { // Send a reset frame with the given error code var fh = new FrameHeader { StreamId = this.Id, Type = FrameType.ResetStream, Flags = 0, }; var resetData = new ResetFrameData { ErrorCode = errorCode }; writeResetTask = connection.writer.WriteResetStream(fh, resetData); } else { // If we don't send a notification we still have to unregister // from the writer in order to cancel pending writes connection.writer.RemoveStream(this.Id); } // Unregister from the connection // If this has happened from the remote side the connection will // already have performed this if (!fromRemote) { this.connection.UnregisterStream(this); } // Unblock all waiters readDataPossible.Set(); readTrailersPossible.Set(); readHeadersPossible.Set(); return(writeResetTask); }