예제 #1
0
        public LibuvTransportFactory(
#pragma warning disable CS0618
            IOptions <LibuvTransportOptions> options,
#pragma warning restore CS0618
            IHostApplicationLifetime applicationLifetime,
            ILoggerFactory loggerFactory)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (applicationLifetime == null)
            {
                throw new ArgumentNullException(nameof(applicationLifetime));
            }
            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv");
            var trace  = new LibuvTrace(logger);

#pragma warning disable CS0618
            var threadCount = options.Value.ThreadCount;
#pragma warning restore CS0618

            if (threadCount <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(threadCount),
                                                      threadCount,
                                                      "ThreadCount must be positive.");
            }

            if (!LibuvConstants.ECONNRESET.HasValue)
            {
                trace.LogWarning("Unable to determine ECONNRESET value on this platform.");
            }

            if (!LibuvConstants.EADDRINUSE.HasValue)
            {
                trace.LogWarning("Unable to determine EADDRINUSE value on this platform.");
            }

            _baseTransportContext = new LibuvTransportContext
            {
                Options     = options.Value,
                AppLifetime = applicationLifetime,
                Log         = trace,
            };
        }
예제 #2
0
        private async Task ApplyBackpressureAsync(ValueTask <FlushResult> flushTask)
        {
            LibuvTrace.ConnectionPause(Log, ConnectionId);
            _socket.ReadStop();

            var result = await flushTask;

            // If the reader isn't complete or cancelled then resume reading
            if (!result.IsCompleted && !result.IsCanceled)
            {
                LibuvTrace.ConnectionResume(Log, ConnectionId);
                StartReading();
            }
        }
예제 #3
0
        private void OnRead(UvStreamHandle handle, int status)
        {
            // Cleanup state from last OnAlloc. This is safe even if OnAlloc wasn't called.
            _bufferHandle.Dispose();
            if (status == 0)
            {
                // EAGAIN/EWOULDBLOCK so just return the buffer.
                // http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb
            }
            else if (status > 0)
            {
                LibuvTrace.ConnectionRead(Log, ConnectionId, status);

                Input.Advance(status);
                var flushTask = Input.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.
                _socket.ReadStop();

                Exception error = null;

                if (status == LibuvConstants.EOF)
                {
                    LibuvTrace.ConnectionReadFin(Log, ConnectionId);
                }
                else
                {
                    handle.Libuv.Check(status, out var uvError);
                    error = LogAndWrapReadError(uvError);
                }

                FireConnectionClosed();

                // Complete after aborting the connection
                Input.Complete(error);
            }
        }
예제 #4
0
 private Exception LogAndWrapReadError(UvException uvError)
 {
     if (uvError.StatusCode == LibuvConstants.ECANCELED)
     {
         // The operation was canceled by the server not the client. No need for additional logs.
         return(new ConnectionAbortedException(uvError.Message, uvError));
     }
     else if (LibuvConstants.IsConnectionReset(uvError.StatusCode))
     {
         // Log connection resets at a lower (Debug) level.
         LibuvTrace.ConnectionReset(Log, ConnectionId);
         return(new ConnectionResetException(uvError.Message, uvError));
     }
     else
     {
         // This is unexpected.
         LibuvTrace.ConnectionError(Log, ConnectionId, uvError);
         return(new IOException(uvError.Message, uvError));
     }
 }
예제 #5
0
        /// <summary>
        /// Handles an incoming connection
        /// </summary>
        /// <param name="listenSocket">Socket being used to listen on</param>
        /// <param name="status">Connection status</param>
        private void OnConnection(UvStreamHandle listenSocket, int status)
        {
            UvStreamHandle acceptSocket = null;

            try
            {
                acceptSocket = CreateAcceptSocket();
                listenSocket.Accept(acceptSocket);
                DispatchConnection(acceptSocket);
            }
            catch (UvException ex) when(LibuvConstants.IsConnectionReset(ex.StatusCode))
            {
                LibuvTrace.ConnectionReset(Log, "(null)");
                acceptSocket?.Dispose();
            }
            catch (UvException ex)
            {
                Log.LogError(0, ex, "Listener.OnConnection");
                acceptSocket?.Dispose();
            }
        }
예제 #6
0
        private void ReadStartCallback(UvStreamHandle handle, int status)
        {
            if (status < 0)
            {
                if (status != LibuvConstants.EOF)
                {
                    Thread.Loop.Libuv.Check(status, out var ex);
                    Log.LogError(0, ex, "DispatchPipe.ReadStart");
                }

                DispatchPipe.Dispose();
                return;
            }

            if (_closed || DispatchPipe.PendingCount() == 0)
            {
                return;
            }

            var acceptSocket = CreateAcceptSocket();

            try
            {
                DispatchPipe.Accept(acceptSocket);

                HandleConnection(acceptSocket);
            }
            catch (UvException ex) when(LibuvConstants.IsConnectionReset(ex.StatusCode))
            {
                LibuvTrace.ConnectionReset(Log, "(null)");
                acceptSocket.Dispose();
            }
            catch (UvException ex)
            {
                Log.LogError(0, ex, "DispatchPipe.Accept");
                acceptSocket.Dispose();
            }
        }
예제 #7
0
 private void LogWriteInfo(int status, Exception error)
 {
     if (error == null)
     {
         LibuvTrace.ConnectionWriteCallback(_log, _connectionId, status);
     }
     else
     {
         // Log connection resets at a lower (Debug) level.
         if (status == LibuvConstants.ECANCELED)
         {
             // Connection was aborted.
         }
         else if (LibuvConstants.IsConnectionReset(status))
         {
             LibuvTrace.ConnectionReset(_log, _connectionId);
         }
         else
         {
             LibuvTrace.ConnectionError(_log, _connectionId, error);
         }
     }
 }
예제 #8
0
        protected internal void HandleConnection(UvStreamHandle socket)
        {
            try
            {
                IPEndPoint remoteEndPoint = null;
                IPEndPoint localEndPoint  = null;

                if (socket is UvTcpHandle tcpHandle)
                {
                    try
                    {
                        remoteEndPoint = tcpHandle.GetPeerIPEndPoint();
                        localEndPoint  = tcpHandle.GetSockIPEndPoint();
                    }
                    catch (UvException ex) when(LibuvConstants.IsConnectionReset(ex.StatusCode))
                    {
                        LibuvTrace.ConnectionReset(TransportContext.Log, "(null)");
                        socket.Dispose();
                        return;
                    }
                }

                var options = TransportContext.Options;
#pragma warning disable CS0618
                var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions, options.MaxReadBufferSize, options.MaxWriteBufferSize);
#pragma warning restore CS0618
                connection.Start();

                bool accepted = _acceptQueue.Writer.TryWrite(connection);
                Debug.Assert(accepted, "The connection was not written to the channel!");
            }
            catch (Exception ex)
            {
                TransportContext.Log.LogCritical(ex, $"Unexpected exception in {nameof(ListenerContext)}.{nameof(HandleConnection)}.");
            }
        }
예제 #9
0
        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.
                        LibuvTrace.ConnectionError(Log, 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
                    LibuvTrace.ConnectionWriteFin(Log, 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}");
            }
        }