public void FlushAsyncNotCompletedAfterCancellation() { var onCompletedCalled = false; PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); PipeAwaiter <FlushResult> awaitable = writableBuffer.FlushAsync(); Assert.False(awaitable.IsCompleted); awaitable.OnCompleted( () => { onCompletedCalled = true; Assert.True(awaitable.IsCompleted); FlushResult flushResult = awaitable.GetResult(); Assert.True(flushResult.IsCanceled); awaitable = writableBuffer.FlushAsync(); Assert.False(awaitable.IsCompleted); }); Pipe.Writer.CancelPendingFlush(); Assert.True(onCompletedCalled); }
private async ValueTask WriteBlockSignatures(CancellationToken ct) { FlushResult flushResult = default; int writtenSinceFlush = new SignatureHeader().Size; while (!flushResult.IsCompleted) { var readResult = await _reader.Buffer(_options.BlockLength, ct).ConfigureAwait(false); if (readResult.Buffer.IsEmpty) { return; } var sig = ComputeSignature(readResult.Buffer); _reader.AdvanceTo(readResult.Buffer.End); writtenSinceFlush += _writer.Write(sig, _options); if (writtenSinceFlush >= _flushThreshhold) { flushResult = await _writer.FlushAsync(ct).ConfigureAwait(false); writtenSinceFlush = 0; } } }
public async Task CancelPendingFlush() { var streamMock = new Mock <Stream>(MockBehavior.Strict); streamMock.SetupGet(s => s.CanWrite).Returns(true); var writeCompletedSource = new TaskCompletionSource <object?>(); // Set up for either WriteAsync method to be called. We expect it will be Memory<T> on .NET Core 2.1 and byte[] on all the others. #if SPAN_BUILTIN streamMock.Setup(s => s.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>())).Returns(new ValueTask(writeCompletedSource.Task)); #else streamMock.Setup(s => s.WriteAsync(It.IsAny <byte[]>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <CancellationToken>())).Returns(writeCompletedSource.Task); #endif Stream? stream = streamMock.Object; PipeWriter?writer = this.CreatePipeWriter(stream); writer.GetMemory(1); writer.Advance(1); ValueTask <FlushResult> flushTask = writer.FlushAsync(); writer.CancelPendingFlush(); writeCompletedSource.SetResult(null); FlushResult flushResult = await flushTask; Assert.True(flushResult.IsCanceled); }
public async Task ReaderOnly() { var pipe = new Pipe(); var duplex = new DuplexPipe(pipe.Reader); // Our assert strategy is to verify our no-op writer behaves the same way as a completed writer. pipe.Writer.Complete(); Assert.Throws <InvalidOperationException>(() => pipe.Writer.GetMemory()); Assert.Throws <InvalidOperationException>(() => duplex.Output.GetMemory()); Assert.Throws <InvalidOperationException>(() => pipe.Writer.GetSpan()); Assert.Throws <InvalidOperationException>(() => duplex.Output.GetSpan()); // System.IO.Pipelines stopped throwing when Advance(0) is called after completion, // But we still feel it's useful to throw since it's a read-only pipe. pipe.Writer.Advance(0); Assert.Throws <InvalidOperationException>(() => duplex.Output.Advance(0)); FlushResult flushResult = await pipe.Writer.FlushAsync(); Assert.False(flushResult.IsCompleted); flushResult = await duplex.Output.FlushAsync(); Assert.False(flushResult.IsCompleted); pipe.Writer.CancelPendingFlush(); duplex.Output.CancelPendingFlush(); #pragma warning disable CS0618 // Type or member is obsolete pipe.Writer.OnReaderCompleted((ex, s) => { }, null); duplex.Output.OnReaderCompleted((ex, s) => { }, null); #pragma warning restore CS0618 // Type or member is obsolete pipe.Writer.Complete(); duplex.Output.Complete(); }
public async Task ProcessAsync(CancellationToken cancellationToken = default) { var pipe = new Pipe(); var writer = pipe.Writer; ValueWebSocketReceiveResult receiveresult; do { // Allocate at least 512 bytes from the PipeWriter Memory <byte> memory = writer.GetMemory(512); try { receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); } catch (WebSocketException ex) { _logger.LogWarning("WS {IP} error receiving data: {Message}", RemoteEndPoint, ex.Message); break; } int bytesRead = receiveresult.Count; if (bytesRead == 0) { break; } // Tell the PipeWriter how much was read from the Socket writer.Advance(bytesRead); // Make the data available to the PipeReader FlushResult flushResult = await writer.FlushAsync(); if (flushResult.IsCompleted) { // The PipeReader stopped reading break; } LastActivityDate = DateTime.UtcNow; if (receiveresult.EndOfMessage) { await ProcessInternal(pipe.Reader).ConfigureAwait(false); } } while ( (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting) && receiveresult.MessageType != WebSocketMessageType.Close); Closed?.Invoke(this, EventArgs.Empty); if (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseReceived || _socket.State == WebSocketState.CloseSent) { await _socket.CloseAsync( WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); } }
private async Task ProcessEventStream(IDuplexPipe application, HttpResponseMessage response, CancellationToken cancellationToken) { Log.StartReceive(_logger); using (response) using (var stream = await response.Content.ReadAsStreamAsync()) { var options = new PipeOptions(pauseWriterThreshold: 0, resumeWriterThreshold: 0); var reader = PipeReaderFactory.CreateFromStream(options, stream, cancellationToken); try { while (true) { var result = await reader.ReadAsync(); var buffer = result.Buffer; var consumed = buffer.Start; var examined = buffer.End; try { if (result.IsCanceled) { Log.ReceiveCanceled(_logger); break; } if (!buffer.IsEmpty) { Log.ParsingSSE(_logger, buffer.Length); var parseResult = _parser.ParseMessage(buffer, out consumed, out examined, out var message); FlushResult flushResult = default; switch (parseResult) { case ServerSentEventsMessageParser.ParseResult.Completed: Log.MessageToApplication(_logger, message.Length); flushResult = await _application.Output.WriteAsync(message); _parser.Reset(); break; case ServerSentEventsMessageParser.ParseResult.Incomplete: if (result.IsCompleted) { throw new FormatException("Incomplete message."); } break; } // We canceled in the middle of applying back pressure // or if the consumer is done if (flushResult.IsCanceled || flushResult.IsCompleted) { Log.EventStreamEnded(_logger); break; } } else if (result.IsCompleted) { break; } } finally { reader.AdvanceTo(consumed, examined); } } } catch (Exception ex) { _error = ex; } finally { _application.Output.Complete(_error); Log.ReceiveStopped(_logger); reader.Complete(); } } }
private static async Task AcceptAsync(Socket socket) { Console.WriteLine($"[{socket.RemoteEndPoint}]: connected"); var pipe = new Pipe(); Task writing = ReadFromSocketAsync(socket, pipe.Writer); Task reading = ReadFromPipeAsync(socket, pipe.Reader); async Task ReadFromSocketAsync(Socket s, PipeWriter writer) { const int minimumBufferSize = 512; while (true) { Memory <byte> memory = writer.GetMemory(minimumBufferSize); int read = await socket.ReceiveAsync(memory, SocketFlags.None); if (read == 0) { break; } writer.Advance(read); FlushResult result = await writer.FlushAsync(); if (result.IsCompleted) { break; } } writer.Complete(); } async Task ReadFromPipeAsync(Socket s, PipeReader reader) { while (true) { ReadResult result = await reader.ReadAsync(); ReadOnlySequence <byte> buffer = result.Buffer; SequencePosition? position = buffer.PositionOf((byte)'\n'); if (position != null) { ProcessLine(s, buffer.Slice(0, position.Value)); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); } reader.AdvanceTo(buffer.Start, buffer.End); if (result.IsCompleted) { break; } } reader.Complete(); } await reading; await writing; Console.WriteLine($"[{socket.RemoteEndPoint}]: disconnected"); }
/// <summary> /// Enables efficiently reading a stream using <see cref="PipeReader"/>. /// </summary> /// <param name="stream">The stream to read from using a pipe.</param> /// <param name="sizeHint">A hint at the size of messages that are commonly transferred. Use 0 for a commonly reasonable default.</param> /// <param name="pipeOptions">Optional pipe options to use.</param> /// <param name="disposeWhenReaderCompleted">A task which, when complete, signals that this method should dispose of the <paramref name="stream"/>.</param> /// <param name="cancellationToken">A cancellation token that aborts reading from the <paramref name="stream"/>.</param> /// <returns>A <see cref="PipeReader"/>.</returns> /// <remarks> /// When the caller invokes <see cref="PipeReader.Complete(Exception)"/> on the result value, /// this leads to the associated <see cref="PipeWriter.Complete(Exception)"/> to be automatically called as well. /// </remarks> private static PipeReader UsePipeReader(this Stream stream, int sizeHint = 0, PipeOptions?pipeOptions = null, Task?disposeWhenReaderCompleted = null, CancellationToken cancellationToken = default) { Requires.NotNull(stream, nameof(stream)); Requires.Argument(stream.CanRead, nameof(stream), "Stream must be readable."); var pipe = new Pipe(pipeOptions ?? PipeOptions.Default); // Notice when the pipe reader isn't listening any more, and terminate our loop that reads from the stream. // OBSOLETE API USAGE NOTICE: If at some point we need to stop relying on PipeWriter.OnReaderCompleted (since it is deprecated and may be removed later), // we can return a decorated PipeReader that calls us from its Complete method directly. var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); #pragma warning disable CS0618 // Type or member is obsolete pipe.Writer.OnReaderCompleted((ex, state) => ((CancellationTokenSource)state).Cancel(), combinedTokenSource); // When this argument is provided, it provides a means to ensure we don't hang while reading from an I/O pipe // that doesn't respect the CancellationToken. Disposing a Stream while reading is a means to terminate the ReadAsync operation. if (disposeWhenReaderCompleted is object) { disposeWhenReaderCompleted.ContinueWith( (_, s1) => { var tuple = (Tuple <Pipe, Stream>)s1; tuple.Item1.Writer.OnReaderCompleted((ex, s2) => ((Stream)s2).Dispose(), tuple.Item2); }, Tuple.Create(pipe, stream), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Forget(); } #pragma warning restore CS0618 // Type or member is obsolete Task.Run(async delegate { while (!combinedTokenSource.Token.IsCancellationRequested) { Memory <byte> memory = pipe.Writer.GetMemory(sizeHint); try { int bytesRead = await stream.ReadAsync(memory, combinedTokenSource.Token).ConfigureAwait(false); if (bytesRead == 0) { break; } pipe.Writer.Advance(bytesRead); } catch (OperationCanceledException) { break; } catch (ObjectDisposedException) { break; } catch (Exception ex) { // Propagate the exception to the reader. await pipe.Writer.CompleteAsync(ex).ConfigureAwait(false); return; } FlushResult result = await pipe.Writer.FlushAsync().ConfigureAwait(false); if (result.IsCompleted) { break; } } // Tell the PipeReader that there's no more data coming await pipe.Writer.CompleteAsync().ConfigureAwait(false); }).Forget(); return(pipe.Reader); }
private static async Task ProcessProtocolAsync(Socket socket) { var pipe = new Pipe(); const int minimumBufferSize = 1024; Memory <byte> memory = pipe.Writer.GetMemory(minimumBufferSize); int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None); pipe.Writer.Advance(bytesRead); FlushResult r = await pipe.Writer.FlushAsync(); ReadResult result = await pipe.Reader.ReadAsync(); ReadOnlySequence <byte> buffer = result.Buffer; var line = buffer.Slice(0, buffer.Length); var bs = line.ToArray(); switch (bs[3]) { case 0x03: var domainLength = bs[4]; string domainName = System.Text.Encoding.ASCII.GetString(bs, 5, domainLength); Console.WriteLine($"{domainName}"); var ipadderss = Dns.GetHostAddresses(domainName).FirstOrDefault(); Array.Reverse(bs, bytesRead - 2, 2); var port = BitConverter.ToInt16(bs, bytesRead - 2); Socket remote = new Socket(SocketType.Stream, ProtocolType.Tcp); var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2)); var rec = remote.ConnectAsync(ipadderss, port); var completedTask = await Task.WhenAny(timeoutTask, rec); if (completedTask == timeoutTask) { socket.Send(new byte[] { 5, 4 }); return; } List <byte> res = new List <byte>(); res.Add(0x05); res.Add(0x00); res.Add(0x00); res.Add(0x01); IPEndPoint localEP = (IPEndPoint)socket.LocalEndPoint; res.AddRange(localEP.Address.MapToIPv4().GetAddressBytes()); res.AddRange(BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder(localEP.Port))); socket.Send(res.ToArray()); try { Task.WaitAny(ReplayToServer(socket, remote), ReplayToLcoal(remote, socket)); } catch { } break; default: break; } }
public void Start() { _processIncoming = Task.Run(async() => { try { while (true) { var result = _lastReadResult = await MockServicePipe.Input.ReadAsync(); if (result.IsCanceled || result.IsCompleted) { break; } var buffer = result.Buffer; try { if (!buffer.IsEmpty) { while (_servicePro.TryParseMessage(ref buffer, out var message)) { // always enqueue so tests can peek and analyze any of these messages EnqueueMessage(message); // now react to some of the connection related stuff if (message is HandshakeRequestMessage) { var handshakeResponse = new HandshakeResponseMessage(""); _servicePro.WriteMessage(handshakeResponse, MockServicePipe.Output); var flushResult = _lastFlushResult = await MockServicePipe.Output.FlushAsync(); if (flushResult.IsCanceled || flushResult.IsCompleted) { _completedHandshake.TrySetResult(false); } else { // sending ack merely allows SDK side to proceed with establishing the connection // for this service connection to become available for hubs to send messages // we'd need to wait for SDK side service connection to change its status _completedHandshake.TrySetResult(true); } continue; } else if (message is ConnectionDataMessage cdm) { var payload = cdm.Payload; // do we know this client? var clientConnection = ClientConnections.Where(c => c.ConnectionId == cdm.ConnectionId).FirstOrDefault(); if (clientConnection != null) { // is this client expecting handshake response? if (clientConnection.ExpectsClientHandshake) { // todo: maybe try parse first and then check if handshake is expected? if (HandshakeProtocol.TryParseResponseMessage(ref payload, out var response)) { clientConnection.ExpectsClientHandshake = false; clientConnection.HandshakeCompleted.TrySetResult(response.Error); } } // There is no such goal to provide full message parsing capabilities here // But it is useful to know the hub invocation return result in some tests so there we have it. while (_signalRPro.TryParseMessage(ref payload, MockSvc.CurrentInvocationBinder, out HubMessage hubMessage)) { clientConnection.EnqueueMessage(hubMessage); if (hubMessage is CloseMessage closeMsg) { clientConnection.CloseMessageReceivedFromSdk = true; } } } } else if (message is ServicePingMessage ping && ping.IsFin()) { var pong = RuntimeServicePingMessage.GetFinAckPingMessage(); _servicePro.WriteMessage(pong, MockServicePipe.Output); var flushResult = _lastFlushResult = await MockServicePipe.Output.FlushAsync(); //todo: do we care about this flush result? } } } } finally { MockServicePipe.Input.AdvanceTo(buffer.Start, buffer.End); } } } catch (Exception e) { _processIncomingException = e; } }); }
async Task FillPipeAsync(Socket socket, PipeWriter writer, ClientManager clientManager) { try { const int minimumBufferSize = 4 * 1024; while (true) { // Allocate at least 512 bytes from the PipeWriter Memory <byte> memory = writer.GetMemory(minimumBufferSize); if (MemoryMarshal.TryGetArray(memory, out ArraySegment <byte> arraySegment)) { if (!socket.Connected) { break; } int bytesRead = await socket.ReceiveAsync(arraySegment, SocketFlags.None); clientManager.AddToClientsBytesRecieved(bytesRead); if (SocketServer.IsServerCounterEnabled) { PerfStatsColl.IncrementBytesReceivedPerSecStats(bytesRead); } if (bytesRead == 0) { DisposeClient(clientManager); break; } // Tell the PipeWriter how much was read from the Socket writer.Advance(bytesRead); } // Make the data available to the PipeReader FlushResult result = await writer.FlushAsync(); if (result.IsCompleted) { break; } } // Tell the PipeReader that there's no more data coming writer.Complete(); DisposeClient(clientManager); } catch (SocketException so_ex) { if (ServerMonitor.MonitorActivity) { ServerMonitor.LogClientActivity("ConMgr.RecvClbk", "Error :" + so_ex.ToString()); } DisposeClient(clientManager); } catch (Exception e) { var clientIsDisposed = clientManager.IsDisposed; DisposeClient(clientManager); if (!clientIsDisposed) { AppUtil.LogEvent(e.ToString(), EventLogEntryType.Error); } if (ServerMonitor.MonitorActivity) { ServerMonitor.LogClientActivity("ConMgr.RecvClbk", "Error :" + e.ToString()); } if (SocketServer.Logger.IsErrorLogsEnabled) { SocketServer.Logger.NCacheLog.Error("ConnectionManager.ReceiveCallback", clientManager.ToString() + " Error " + e.ToString()); } try { if (Management.APILogging.APILogManager.APILogManger != null && Management.APILogging.APILogManager.EnableLogging) { APILogItemBuilder log = new APILogItemBuilder(); log.GenerateConnectionManagerLog(clientManager, e.ToString()); } } catch { } } finally { // clientManager.StopCommandExecution(); if (ServerMonitor.MonitorActivity) { ServerMonitor.StopClientActivity(clientManager.ClientID); } } }
public async Task RespondToWebSocketRequestAsync(WebSocket webSocket, CancellationToken token, PipeWriter pipeWriter) { if (!myIsPipelineImplementation) { ArraySegment <byte> buffer = new ArraySegment <byte>(new byte[myBufferSize]); try { while (true) { WebSocketReceiveResult result = await webSocket.ReceiveAsync(buffer, token); if (receivedMessages == 0) { Console.WriteLine($"Started!!"); stopwatch = Stopwatch.StartNew(); stopwatch.Start(); } receivedMessages++; if (myIsProtobufSerializationEnabled) { using (var stream = new MemoryStream(buffer.Array, 0, result.Count)) { var person = Serializer.Deserialize <Person>(stream); } } if (result.MessageType == WebSocketMessageType.Close) { _logger.LogInformation($"Client initiated close. Status: {result.CloseStatus} Description: {result.CloseStatusDescription}"); break; } if (result.Count > myBufferSize) { await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"Web socket frame cannot exceed buffer size of {myBufferSize:#,##0} bytes. Send multiple frames instead.", token); break; } if (receivedMessages == Constants.NoOfIterations) { Console.WriteLine($"Completed in {stopwatch.Elapsed.TotalMilliseconds:#,##0.00} ms"); } ArraySegment <byte> toSend = new ArraySegment <byte>(buffer.Array, buffer.Offset, result.Count); await webSocket.SendAsync(toSend, WebSocketMessageType.Binary, true, token); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } else { while (true) { try { Memory <byte> memory = pipeWriter.GetMemory(myBufferSize); WebSocketReceiveResult result = await webSocket.ReceiveAsync(memory); if (receivedMessages == 0) { Console.WriteLine($"Started!!"); stopwatch = Stopwatch.StartNew(); stopwatch.Start(); receivedMessages++; } if (result.MessageType == WebSocketMessageType.Close) { _logger.LogInformation($"Client initiated close. Status: {result.CloseStatus} Description: {result.CloseStatusDescription}"); break; } if (result.Count > myBufferSize) { await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"Web socket frame cannot exceed buffer size of {myBufferSize:#,##0} bytes. Send multiple frames instead.", token); break; } pipeWriter.Advance(result.Count); } catch (Exception ex) { Console.WriteLine(ex.ToString()); break; } // Make the data available to the PipeReader FlushResult flushResult = await pipeWriter.FlushAsync(); if (flushResult.IsCompleted) { break; } } // Signal to the reader that we're done writing pipeWriter.Complete(); } }
/// <summary> /// Forwards all bytes coming from a <see cref="PipeReader"/> to the specified <see cref="PipeWriter"/>. /// </summary> /// <param name="reader">The reader to get bytes from.</param> /// <param name="writer">The writer to copy bytes to. <see cref="PipeWriter.CompleteAsync(Exception)"/> will be called on this object when the reader completes or an error occurs.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns> /// A <see cref="Task"/> that completes when the <paramref name="reader"/> has finished producing bytes, or an error occurs. /// This <see cref="Task"/> never faults, since any exceptions are used to complete the <paramref name="writer"/>. /// </returns> /// <remarks> /// If an error occurs during reading or writing, the <paramref name="writer"/> and <paramref name="reader"/> are completed with the exception. /// </remarks> internal static Task LinkToAsync(this PipeReader reader, PipeWriter writer, CancellationToken cancellationToken = default) { Requires.NotNull(reader, nameof(reader)); Requires.NotNull(writer, nameof(writer)); if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled(cancellationToken)); } return(Task.Run(async delegate { try { if (DuplexPipe.IsDefinitelyCompleted(reader)) { await writer.CompleteAsync().ConfigureAwait(false); return; } while (true) { cancellationToken.ThrowIfCancellationRequested(); ReadResult result = await reader.ReadAsync(cancellationToken).ConfigureAwait(false); if (result.IsCanceled) { cancellationToken.ThrowIfCancellationRequested(); throw new OperationCanceledException(Strings.PipeReaderCanceled); } writer.Write(result.Buffer); reader.AdvanceTo(result.Buffer.End); result.ScrubAfterAdvanceTo(); FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); if (flushResult.IsCanceled) { cancellationToken.ThrowIfCancellationRequested(); throw new OperationCanceledException(Strings.PipeWriterFlushCanceled); } if (flushResult.IsCompleted) { // Break out of copy loop. The receiver doesn't care any more. break; } if (result.IsCompleted) { await writer.CompleteAsync().ConfigureAwait(false); break; } } await reader.CompleteAsync().ConfigureAwait(false); } catch (Exception ex) { await writer.CompleteAsync(ex).ConfigureAwait(false); await reader.CompleteAsync(ex).ConfigureAwait(false); } })); }
private Task WrapAsync(IShadowsocksCrypto decryptor, CancellationToken cancellationToken) { return(Task.Run( async() => { try { while (true) { ReadResult result = await InternalReader.ReadAndCheckIsCanceledAsync(cancellationToken); ReadOnlySequence <byte> buffer = result.Buffer; try { while (!buffer.IsEmpty) { long oldLength = buffer.Length; Memory <byte> memory = Writer.GetMemory(BufferSize); int outLength = decryptor.DecryptTCP(ref buffer, memory.Span); Writer.Advance(outLength); if (outLength > 0) { FlushResult writerFlushResult = await Writer.FlushAsync(cancellationToken); if (writerFlushResult.IsCompleted) { goto NoData; } } if (oldLength == buffer.Length) { break; } } if (result.IsCompleted) { break; } } finally { InternalReader.AdvanceTo(buffer.Start, buffer.End); } } NoData: await Writer.CompleteAsync(); } catch (Exception ex) { await Writer.CompleteAsync(ex); } finally { decryptor.Dispose(); } }, default )); }
internal static bool HasMore( this FlushResult readResult) => readResult.IsCanceled == false && readResult.IsCompleted == false;
private async Task OpenConnection(IDuplexPipe application, Uri url, TaskCompletionSource <object> startTcs, CancellationToken cancellationToken) { Log.StartReceive(_logger); var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); HttpResponseMessage response = null; try { response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); startTcs.TrySetResult(null); } catch (Exception ex) { response?.Dispose(); Log.TransportStopping(_logger); startTcs.TrySetException(ex); return; } using (response) using (var stream = await response.Content.ReadAsStreamAsync()) { var pipeOptions = new PipeOptions(pauseWriterThreshold: 0, resumeWriterThreshold: 0); var pipelineReader = StreamPipeConnection.CreateReader(pipeOptions, stream); var readCancellationRegistration = cancellationToken.Register( reader => ((PipeReader)reader).CancelPendingRead(), pipelineReader); try { while (true) { var result = await pipelineReader.ReadAsync(); var input = result.Buffer; if (result.IsCanceled || (input.IsEmpty && result.IsCompleted)) { Log.EventStreamEnded(_logger); break; } var consumed = input.Start; var examined = input.End; try { Log.ParsingSSE(_logger, input.Length); var parseResult = _parser.ParseMessage(input, out consumed, out examined, out var buffer); FlushResult flushResult = default; switch (parseResult) { case ServerSentEventsMessageParser.ParseResult.Completed: Log.MessageToApp(_logger, buffer.Length); flushResult = await _application.Output.WriteAsync(buffer); _parser.Reset(); break; case ServerSentEventsMessageParser.ParseResult.Incomplete: if (result.IsCompleted) { throw new FormatException("Incomplete message."); } break; } // We canceled in the middle of applying back pressure // or if the consumer is done if (flushResult.IsCanceled || flushResult.IsCompleted) { break; } } finally { pipelineReader.AdvanceTo(consumed, examined); } } } catch (OperationCanceledException) { Log.ReceiveCanceled(_logger); } catch (Exception ex) { _error = ex; } finally { _application.Output.Complete(_error); readCancellationRegistration.Dispose(); Log.ReceiveStopped(_logger); } } }