Exemple #1
0
        private async Task ProcessClientConnectionAsync(ClientConnectionContext connection)
        {
            try
            {
                // Writing from the application to the service
                var transport = ProcessOutgoingMessagesAsync(connection, connection.OutgoingAborted);

                // Waiting for the application to shutdown so we can clean up the connection
                var app = ProcessApplicationTaskAsyncCore(connection);

                var task = await Task.WhenAny(app, transport);

                // remove it from the connection list
                RemoveClientConnection(connection.ConnectionId);

                // This is the exception from application
                Exception exception = null;
                if (task == app)
                {
                    exception = app.Exception?.GetBaseException();

                    // there is no need to write to the transport as application is no longer running
                    Log.WaitingForTransport(Logger);

                    // app task completes connection.Transport.Output, which will completes connection.Application.Input and ends the transport
                    // Transports are written by us and are well behaved, wait for them to drain
                    connection.CancelOutgoing(_closeTimeOutMilliseconds);

                    // transport never throws
                    await transport;
                }
                else
                {
                    // transport task ends first, no data will be dispatched out
                    Log.WaitingForApplication(Logger);

                    try
                    {
                        // always wait for the application to complete
                        await app;
                    }
                    catch (Exception e)
                    {
                        exception = e;
                    }
                }

                if (exception != null)
                {
                    Log.ApplicationTaskFailed(Logger, exception);
                }

                // If we aren't already aborted, we send the abort message to the service
                if (connection.AbortOnClose)
                {
                    // Inform the Service that we will remove the client because SignalR told us it is disconnected.
                    var serviceMessage =
                        new CloseConnectionMessage(connection.ConnectionId, errorMessage: exception?.Message);
                    // when it fails, it means the underlying connection is dropped
                    // service is responsible for closing the client connections in this case and there is no need to throw
                    await SafeWriteAsync(serviceMessage);

                    Log.CloseConnection(Logger, connection.ConnectionId);
                }

                Log.ConnectedEnding(Logger, connection.ConnectionId);
            }
            catch (Exception e)
            {
                // When it throws, there must be something wrong
                Log.ProcessConnectionFailed(Logger, connection.ConnectionId, e);
            }
            finally
            {
                connection.OnCompleted();
            }
        }
Exemple #2
0
        private async Task ProcessOutgoingMessagesAsync(ClientConnectionContext connection, CancellationToken token = default)
        {
            try
            {
                if (connection.IsMigrated)
                {
                    using var timeoutToken = new CancellationTokenSource(DefaultHandshakeTimeout);
                    using var source       = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutToken.Token);

                    // A handshake response is not expected to be given
                    // if the connection was migrated from another server,
                    // since the connection hasn't been `dropped` from the client point of view.
                    if (!await SkipHandshakeResponse(connection, source.Token))
                    {
                        return;
                    }
                }

                while (true)
                {
                    var result = await connection.Application.Input.ReadAsync(token);

                    if (result.IsCanceled)
                    {
                        break;
                    }

                    var buffer = result.Buffer;

                    if (!buffer.IsEmpty)
                    {
                        try
                        {
                            // Forward the message to the service
                            await WriteAsync(new ConnectionDataMessage(connection.ConnectionId, buffer));
                        }
                        catch (Exception ex)
                        {
                            Log.ErrorSendingMessage(Logger, ex);
                        }
                    }

                    if (result.IsCompleted)
                    {
                        // This connection ended (the application itself shut down) we should remove it from the list of connections
                        break;
                    }

                    connection.Application.Input.AdvanceTo(buffer.End);
                }
            }
            catch (Exception ex)
            {
                // The exception means application fail to process input anymore
                // Cancel any pending flush so that we can quit and perform disconnect
                // Here is abort close and WaitOnApplicationTask will send close message to notify client to disconnect
                Log.SendLoopStopped(Logger, connection.ConnectionId, ex);
                connection.Application.Output.CancelPendingFlush();
            }
            finally
            {
                connection.Application.Input.Complete();
                await PerformDisconnectAsyncCore(connection.ConnectionId, true);

                connection.OnCompleted();
            }
        }