Beispiel #1
0
        private void Shutdown(Exception?shutdownReason)
        {
            lock (_shutdownLock)
            {
                if (_socketDisposed)
                {
                    return;
                }

                // Make sure to close the connection only after the _aborted flag is set.
                // Without this, the RequestsCanBeAbortedMidRead test will sometimes fail when
                // a BadHttpRequestException is thrown instead of a TaskCanceledException.
                _socketDisposed = true;

                // shutdownReason should only be null if the output was completed gracefully, so no one should ever
                // ever observe the nondescript ConnectionAbortedException except for connection middleware attempting
                // to half close the connection which is currently unsupported.
                _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
                SocketsLog.ConnectionWriteFin(_logger, this, _shutdownReason.Message);

                try
                {
                    // Try to gracefully close the socket even for aborts to match libuv behavior.
                    _socket.Shutdown(SocketShutdown.Both);
                }
                catch
                {
                    // Ignore any errors from Socket.Shutdown() since we're tearing down the connection anyway.
                }

                _socket.Dispose();
            }
        }
Beispiel #2
0
        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();
            }
        }
Beispiel #3
0
        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;
            }
        }