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); SocketsLog.ConnectionReset(_logger, 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; SocketsLog.ConnectionError(_logger, 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 SocketsLog.ConnectionReadFin(_logger, this); break; } Input.Advance(bytesReceived); var flushTask = Input.FlushAsync(); var paused = !flushTask.IsCompleted; if (paused) { SocketsLog.ConnectionPause(_logger, this); } var result = await flushTask; if (paused) { SocketsLog.ConnectionResume(_logger, 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) { SocketsLog.ConnectionReset(_logger, 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. SocketsLog.ConnectionError(_logger, this, error); } } catch (Exception ex) { // This is unexpected. error = ex; SocketsLog.ConnectionError(_logger, this, error); } finally { // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. Input.Complete(_shutdownReason ?? error); FireConnectionClosed(); await _waitForConnectionClosedTcs.Task; } }