private async Task DoSend() { Exception shutdownReason = null; Exception unexpectedError = null; try { await ProcessSends(); } catch (SocketException ex) when(IsConnectionResetError(ex.SocketErrorCode)) { shutdownReason = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(ConnectionId); } catch (Exception ex) when((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This should always be ignored since Shutdown() must have already been called by Abort(). shutdownReason = ex; } catch (Exception ex) { shutdownReason = ex; unexpectedError = ex; _trace.ConnectionError(ConnectionId, unexpectedError); } finally { Shutdown(shutdownReason); // Complete the output after disposing the socket Output.Complete(unexpectedError); // Cancel any pending flushes so that the input loop is un-paused Input.CancelPendingFlush(); } }
private async Task DoReceive() { Exception error = null; try { await ProcessReceives().ConfigureAwait(false); } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.ConnectionReset) { error = new ConnectionResetException(ex.Message, ex); } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted || ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument) { if (!_aborted) { // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. error = new ConnectionAbortedException(); } } catch (ObjectDisposedException) { if (!_aborted) { error = new ConnectionAbortedException(); } } catch (IOException ex) { error = ex; } catch (Exception ex) { error = new IOException(ex.Message, ex); } finally { if (_aborted) { error ??= new ConnectionAbortedException(); } await _application.Output.CompleteAsync(error).ConfigureAwait(false); } }
// Returns whether the read handles are still needed private bool HandleCompleteReadError(Ring ring, int result) { if (result == 0) { // EOF CompleteInbound(ring, null); return(false); } var err = -result; if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) { Read(ring); return(true); } if (HasFlag(ConnectionState.ReadCancelled) && err == ECANCELED) { return(false); } Exception ex; if (err == ECONNRESET) { ex = new ErrnoException(ECONNRESET); ex = new ConnectionResetException(ex.Message, ex); } else { ex = new ErrnoException(err); } CompleteInbound(ring, ex); return(false); }
private void OnRead(UvStreamHandle handle, int status) { if (status == 0) { // EAGAIN/EWOULDBLOCK so just return the buffer. // http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb Debug.Assert(_currentWritableBuffer != null); _currentWritableBuffer.Value.Commit(); } else if (status > 0) { Log.ConnectionRead(ConnectionId, status); Debug.Assert(_currentWritableBuffer != null); var currentWritableBuffer = _currentWritableBuffer.Value; currentWritableBuffer.Advance(status); var flushTask = currentWritableBuffer.FlushAsync(); if (!flushTask.IsCompleted) { // We wrote too many bytes to the reader, so pause reading and resume when // we hit the low water mark. _ = ApplyBackpressureAsync(flushTask); } } else { // Given a negative status, it's possible that OnAlloc wasn't called. _currentWritableBuffer?.Commit(); _socket.ReadStop(); IOException error = null; if (status == LibuvConstants.EOF) { Log.ConnectionReadFin(ConnectionId); } else { handle.Libuv.Check(status, out var uvError); // Log connection resets at a lower (Debug) level. if (LibuvConstants.IsConnectionReset(status)) { Log.ConnectionReset(ConnectionId); error = new ConnectionResetException(uvError.Message, uvError); } else { Log.ConnectionError(ConnectionId, uvError); error = new IOException(uvError.Message, uvError); } } // Complete after aborting the connection Input.Complete(error); } // Cleanup state from last OnAlloc. This is safe even if OnAlloc wasn't called. _currentWritableBuffer = null; _bufferHandle.Dispose(); }
private async Task StartCore() { try { OutputConsumer = new LibuvOutputConsumer(Output, Thread, _socket, ConnectionId, Log); StartReading(); Exception inputError = null; Exception outputError = null; try { // This *must* happen after socket.ReadStart // The socket output consumer is the only thing that can close the connection. If the // output pipe is already closed by the time we start then it's fine since, it'll close gracefully afterwards. await OutputConsumer.WriteOutputAsync(); } catch (UvException ex) { // The connection reset/error has already been logged by LibuvOutputConsumer if (ex.StatusCode == LibuvConstants.ECANCELED) { // Connection was aborted. } else if (LibuvConstants.IsConnectionReset(ex.StatusCode)) { // Don't cause writes to throw for connection resets. inputError = new ConnectionResetException(ex.Message, ex); } else { // This is unexpected. Log.ConnectionError(ConnectionId, ex); inputError = ex; outputError = ex; } } finally { inputError ??= _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully."); // Now, complete the input so that no more reads can happen Input.Complete(inputError); Output.Complete(outputError); // Make sure it isn't possible for a paused read to resume reading after calling uv_close // on the stream handle Input.CancelPendingFlush(); // Send a FIN Log.ConnectionWriteFin(ConnectionId, inputError.Message); // We're done with the socket now _socket.Dispose(); // Ensure this always fires FireConnectionClosed(); await _waitForConnectionClosedTcs.Task; } } catch (Exception e) { Log.LogCritical(0, e, $"{nameof(LibuvConnection)}.{nameof(Start)}() {ConnectionId}"); } }
private async Task DoReceive() { Exception error = null; try { while (true) { // Ensure we have some reasonable amount of buffer space var buffer = Input.GetMemory(MinAllocBufferSize); var bytesReceived = await _receiver.ReceiveAsync(buffer); if (bytesReceived == 0) { // FIN _trace.ConnectionReadFin(ConnectionId); break; } Input.Advance(bytesReceived); var flushTask = Input.FlushAsync(); if (!flushTask.IsCompleted) { _trace.ConnectionPause(ConnectionId); await flushTask; _trace.ConnectionResume(ConnectionId); } var result = flushTask.GetAwaiter().GetResult(); if (result.IsCompleted) { // Pipe consumer is shut down, do we stop writing break; } } } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.ConnectionReset) { error = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(ConnectionId); } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted || ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument) { if (!_aborted) { // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. error = new ConnectionAbortedException(); _trace.ConnectionError(ConnectionId, error); } } catch (ObjectDisposedException) { if (!_aborted) { error = new ConnectionAbortedException(); _trace.ConnectionError(ConnectionId, error); } } catch (IOException ex) { error = ex; _trace.ConnectionError(ConnectionId, error); } catch (Exception ex) { error = new IOException(ex.Message, ex); _trace.ConnectionError(ConnectionId, error); } finally { if (_aborted) { error = error ?? new ConnectionAbortedException(); } Input.Complete(error); } }
private async Task DoSend() { Exception?shutdownReason = null; Exception?unexpectedError = null; try { while (true) { var result = await Output.ReadAsync(); if (result.IsCanceled) { break; } var buffer = result.Buffer; if (!buffer.IsEmpty) { _sender = _socketSenderPool.Rent(); await _sender.SendAsync(_socket, buffer); // We don't return to the pool if there was an exception, and // we keep the _sender assigned so that we can dispose it in StartAsync. _socketSenderPool.Return(_sender); _sender = null; } Output.AdvanceTo(buffer.End); if (result.IsCompleted) { break; } } } catch (SocketException ex) when(IsConnectionResetError(ex.SocketErrorCode)) { shutdownReason = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(this); } catch (Exception ex) when((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This should always be ignored since Shutdown() must have already been called by Abort(). shutdownReason = ex; } catch (Exception ex) { shutdownReason = ex; unexpectedError = ex; _trace.ConnectionError(this, unexpectedError); } finally { Shutdown(shutdownReason); // Complete the output after disposing the socket Output.Complete(unexpectedError); // Cancel any pending flushes so that the input loop is un-paused Input.CancelPendingFlush(); } }
private async Task DoReceive() { Exception?error = null; try { while (true) { if (_waitForData) { // Wait for data before allocating a buffer. await _receiver.WaitForDataAsync(_socket); } // Ensure we have some reasonable amount of buffer space var buffer = Input.GetMemory(MinAllocBufferSize); var bytesReceived = await _receiver.ReceiveAsync(_socket, buffer); if (bytesReceived == 0) { // FIN _trace.ConnectionReadFin(this); break; } Input.Advance(bytesReceived); var flushTask = Input.FlushAsync(); var paused = !flushTask.IsCompleted; if (paused) { _trace.ConnectionPause(this); } var result = await flushTask; if (paused) { _trace.ConnectionResume(this); } if (result.IsCompleted || result.IsCanceled) { // Pipe consumer is shut down, do we stop writing break; } } } catch (SocketException ex) when(IsConnectionResetError(ex.SocketErrorCode)) { // This could be ignored if _shutdownReason is already set. error = new ConnectionResetException(ex.Message, ex); // There's still a small chance that both DoReceive() and DoSend() can log the same connection reset. // Both logs will have the same ConnectionId. I don't think it's worthwhile to lock just to avoid this. if (!_socketDisposed) { _trace.ConnectionReset(this); } } catch (Exception ex) when((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This exception should always be ignored because _shutdownReason should be set. error = ex; if (!_socketDisposed) { // This is unexpected if the socket hasn't been disposed yet. _trace.ConnectionError(this, error); } } catch (Exception ex) { // This is unexpected. error = ex; _trace.ConnectionError(this, error); } finally { // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. Input.Complete(_shutdownReason ?? error); FireConnectionClosed(); await _waitForConnectionClosedTcs.Task; } }
private async Task DoSendAsync() { Exception shutdownReason = null; Exception unexpectedError = null; try { RegisteredOperationContext operationContext = _socket.CreateOperationContext(); var sendBuffers = new Operation[1]; while (true) { ReadResult readResult = await _socketInput.ReadAsync(); if (readResult.IsCanceled) { break; } ReadOnlySequence <byte> buffer = readResult.Buffer; SequencePosition next = buffer.Start, end = buffer.End; int sendCount = 0; while (buffer.TryGet(ref next, out ReadOnlyMemory <byte> segment)) { if (sendCount == sendBuffers.Length) { Array.Resize(ref sendBuffers, sendBuffers.Length * 2); } RegisteredOperationContext ctx = sendBuffers[sendCount].Context ??= _socket.CreateOperationContext(); sendBuffers[sendCount++].Task = ctx.SendAsync(segment); } for (int i = 0; i < sendCount; ++i) { await sendBuffers[i].Task; } _socketInput.AdvanceTo(end); if (readResult.IsCompleted) { break; } } } catch (SocketException ex) when(IsConnectionResetError(ex.SocketErrorCode)) { shutdownReason = new ConnectionResetException(ex.Message, ex); } catch (SocketException ex) when(IsConnectionAbortError(ex.SocketErrorCode)) { shutdownReason = ex; } catch (ObjectDisposedException ex) { shutdownReason = ex; } catch (Exception ex) { shutdownReason = ex; unexpectedError = ex; } Shutdown(shutdownReason); _socketInput.Complete(unexpectedError); _socketOutput.CancelPendingFlush(); }
private async Task DoReceive() { Exception error = null; try { while (true) { // Ensure we have some reasonable amount of buffer space var buffer = _input.Alloc(MinAllocBufferSize); try { var bytesReceived = await _socket.ReceiveAsync(GetArraySegment(buffer.Buffer), SocketFlags.None); if (bytesReceived == 0) { // FIN break; } buffer.Advance(bytesReceived); } finally { buffer.Commit(); } var result = await buffer.FlushAsync(); if (result.IsCompleted) { // Pipe consumer is shut down, do we stop writing break; } } } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.ConnectionReset) { error = new ConnectionResetException(ex.Message, ex); } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.OperationAborted) { error = new ConnectionAbortedException(); } catch (ObjectDisposedException) { error = new ConnectionAbortedException(); } catch (IOException ex) { error = ex; } catch (Exception ex) { error = new IOException(ex.Message, ex); } finally { _connectionContext.Abort(error); _input.Complete(error); } }