예제 #1
0
        private async Task ProcessIncomingMessageAsync(ClientConnectionContext connection)
        {
            Exception exception = null;

            try
            {
                await connection.ApplicationTask;
            }
            catch (Exception e)
            {
                exception = e;
            }
            finally
            {
                // 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);
                    await SafeWriteAsync(serviceMessage);

                    Log.CloseConnection(Logger, connection.ConnectionId);
                }
            }
        }
예제 #2
0
        public async Task ClosingConnectionSendsCloseMessage()
        {
            var proxy = new ServiceConnectionProxy(context =>
            {
                // Just let the connection end immediately
                return(Task.CompletedTask);
            });

            var serverTask = proxy.WaitForServerConnectionAsync(1);

            _ = proxy.StartAsync();
            await serverTask.OrTimeout();

            var task             = proxy.WaitForConnectionAsync("1");
            var closeMessageTask = proxy.WaitForApplicationMessageAsync(typeof(CloseConnectionMessage));

            await proxy.WriteMessageAsync(new OpenConnectionMessage("1", null));

            var connection = await task.OrTimeout();

            CloseConnectionMessage message = (CloseConnectionMessage)await closeMessageTask.OrTimeout();

            Assert.Equal(message.ConnectionId, connection.ConnectionId);

            proxy.Stop();
        }
예제 #3
0
        private async Task WaitOnApplicationTask(ServiceConnectionContext connection)
        {
            Exception exception = null;

            try
            {
                // Wait for the application task to complete
                await connection.ApplicationTask;
            }
            catch (Exception ex)
            {
                // Capture the exception to communicate it to the transport (this isn't strictly required)
                exception = ex;
            }
            finally
            {
                // Close the transport side since the application is no longer running
                connection.Transport.Output.Complete(exception);
                connection.Transport.Input.Complete();
            }

            // 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: "");
                await WriteAsync(serviceMessage);

                Log.CloseConnection(_logger, connection.ConnectionId);
            }
        }
예제 #4
0
        public async Task TestServiceConnectionForMigratedOut()
        {
            var factory = new TestClientConnectionFactory();

            CancellationTokenSource cts = new();
            var connection = CreateServiceConnection(clientConnectionFactory: factory, mode: GracefulShutdownMode.MigrateClients);

            // create a connection with migration header.
            await connection.OnClientConnectedAsyncForTest(new OpenConnectionMessage("foo", new Claim[0]));

            var context = factory.Connections[0];

            var closeMessage = new CloseConnectionMessage(context.ConnectionId);

            closeMessage.Headers.Add(Constants.AsrsMigrateTo, "another-server");

            var disconnect = connection.OnClientDisconnectedAsyncForTest(closeMessage);

            var feature = context.Features.Get <IConnectionMigrationFeature>();

            Assert.NotNull(feature);
            Assert.Equal("another-server", feature.MigrateTo);

            cts.Cancel();
            await disconnect.OrTimeout();
        }
예제 #5
0
        protected override async Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            await base.OnDisconnectedAsync(closeConnectionMessage);

            var tcs = _waitForConnectionClose.GetOrAdd(closeConnectionMessage.ConnectionId, i => new TaskCompletionSource <object>());

            tcs.TrySetResult(null);
        }
예제 #6
0
        protected override Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            PerformDisconnectCore(connectionId);

            return(Task.CompletedTask);
        }
        protected override Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            if (_connections.TryRemove(closeConnectionMessage.ConnectionId, out var connection))
            {
                connection.Disconnect();
            }

            return(Task.CompletedTask);
        }
예제 #8
0
        public async Task CloseConnection()
        {
            var closeClientMessage = new CloseConnectionMessage(ConnectionId, "bbb");

            _servicePro.WriteMessage(closeClientMessage, ServiceSideConnection.MockServicePipe.Output);
            var flushResult = await ServiceSideConnection.MockServicePipe.Output.FlushAsync();

            if (flushResult.IsCanceled || flushResult.IsCompleted)
            {
                throw new InvalidOperationException($"CloseConnectionMessage for client connection id {ConnectionId} resulted in FlushResult IsCanceled {flushResult.IsCanceled} IsCompleted {flushResult.IsCompleted}");
            }
        }
예제 #9
0
        public async Task TestCloseConnectionMessageWithMigrateOut()
        {
            var clientConnectionFactory = new TestClientConnectionFactory();

            var connection = CreateServiceConnection(clientConnectionFactory: clientConnectionFactory, handler: new TestConnectionHandler(3000, "foobar"));

            _ = connection.StartAsync();
            await connection.ConnectionInitializedTask.OrTimeout(1000);

            var openConnectionMessage = new OpenConnectionMessage("foo", Array.Empty <Claim>());

            _ = connection.WriteFromServiceAsync(openConnectionMessage);
            await connection.ClientConnectedTask;

            Assert.Equal(1, clientConnectionFactory.Connections.Count);
            var clientConnection = clientConnectionFactory.Connections[0];

            Assert.False(clientConnection.IsMigrated);

            // write a signalr handshake response
            var message = new SignalRProtocol.HandshakeResponseMessage("");

            SignalRProtocol.HandshakeProtocol.WriteResponseMessage(message, clientConnection.Transport.Output);
            await clientConnection.Transport.Output.FlushAsync();

            // write a close connection message with migration header
            var closeMessage = new CloseConnectionMessage(clientConnection.ConnectionId);

            closeMessage.Headers.Add(Constants.AsrsMigrateTo, "another-server");
            await connection.WriteFromServiceAsync(closeMessage);

            // wait until app task completed.
            await Assert.ThrowsAsync <TimeoutException>(async() => await clientConnection.LifetimeTask.OrTimeout(1000));

            await clientConnection.LifetimeTask.OrTimeout(3000);

            // expect a handshake response message.
            await connection.ExpectSignalRMessage(SignalRProtocol.HandshakeResponseMessage.Empty).OrTimeout(3000);

            // signalr close message should be skipped.
            await Assert.ThrowsAsync <TimeoutException>(async() => await connection.ExpectSignalRMessage(SignalRProtocol.CloseMessage.Empty).OrTimeout(1000));

            var feature = clientConnection.Features.Get <IConnectionMigrationFeature>();

            Assert.NotNull(feature);
            Assert.Equal("another-server", feature.MigrateTo);

            await connection.StopAsync();
        }
        protected override async Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            if (_clientConnections.TryGetValue(connectionId, out var clientContext))
            {
                try
                {
                    await clientContext.Output.WriteAsync(closeConnectionMessage);
                }
                catch (Exception e)
                {
                    Log.FailToWriteMessageToApplication(_logger, connectionId, e);
                }
            }
        }
예제 #11
0
        protected override Task OnClientDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            if (_enableConnectionMigration && _clientConnectionManager.ClientConnections.TryGetValue(connectionId, out var context))
            {
                if (!context.HttpContext.Request.Headers.ContainsKey(Constants.AsrsMigrateOut))
                {
                    context.HttpContext.Request.Headers.Add(Constants.AsrsMigrateOut, "");
                }
                // We have to prevent SignalR `{type: 7}` (close message) from reaching our client while doing migration.
                // Since all user-created messages will be sent to `ServiceConnection` directly.
                // We can simply ignore all messages came from the application pipe.
                context.Application.Input.CancelPendingRead();
            }
            return(PerformDisconnectAsyncCore(connectionId, false));
        }
예제 #12
0
        protected override Task OnClientDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            if (_clientConnectionManager.ClientConnections.TryGetValue(connectionId, out var context))
            {
                if (closeConnectionMessage.Headers.TryGetValue(Constants.AsrsMigrateTo, out var to))
                {
                    context.Features.Set <IConnectionMigrationFeature>(new ConnectionMigrationFeature(ServerId, to));
                    // We have to prevent SignalR `{type: 7}` (close message) from reaching our client while doing migration.
                    // Since all user-created messages will be sent to `ServiceConnection` directly.
                    // We can simply ignore all messages came from the application pipe.
                }
            }

            return(PerformDisconnectAsyncCore(connectionId));
        }
예제 #13
0
        protected override Task OnClientDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            if (_clientConnectionManager.ClientConnections.TryGetValue(connectionId, out var context))
            {
                if (closeConnectionMessage.Headers.TryGetValue(Constants.AsrsMigrateTo, out var to))
                {
                    context.AbortOnClose = false;
                    context.Features.Set <IConnectionMigrationFeature>(new ConnectionMigrationFeature(ServerId, to));
                    // We have to prevent SignalR `{type: 7}` (close message) from reaching our client while doing migration.
                    // Since all data messages will be sent to `ServiceConnection` directly.
                    // We can simply ignore all messages came from the application.
                    context.CancelOutgoing();
                    // The close connection message must be the last message, so we could complete the pipe.
                    context.CompleteIncoming();
                }
            }
            return(PerformDisconnectAsyncCore(connectionId));
        }
        public async Task TestServiceConnectionForMigratedOut()
        {
            var factory = new TestClientConnectionFactory();

            var connection = MockServiceConnection(null, factory);

            // create a connection with migration header.
            await connection.OnClientConnectedAsyncForTest(new OpenConnectionMessage("foo", new Claim[0]));

            var context = factory.Connections[0];

            var closeMessage = new CloseConnectionMessage(context.ConnectionId);

            closeMessage.Headers.Add(Constants.AsrsMigrateTo, "another-server");

            await connection.OnClientDisconnectedAsyncForTest(closeMessage);

            var feature = context.Features.Get <IConnectionMigrationFeature>();

            Assert.NotNull(feature);
            Assert.Equal("another-server", feature.MigrateTo);
        }
예제 #15
0
        protected override Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
        {
            var connectionId = closeConnectionMessage.ConnectionId;

            return(PerformDisconnectAsyncCore(connectionId, false));
        }
예제 #16
0
 public static void StartToCloseConnection(ILogger logger, CloseConnectionMessage message)
 {
     _startToCloseConnection(logger, message.TracingId, message.ConnectionId, message.ErrorMessage, null);
 }
예제 #17
0
 protected override Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
 {
     return(Task.CompletedTask);
 }
예제 #18
0
 protected override Task OnClientDisconnectedAsync(CloseConnectionMessage closeConnectionMessage)
 {
     return(ForwardMessageToApplication(closeConnectionMessage.ConnectionId, closeConnectionMessage));
 }
예제 #19
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();
            }
        }
예제 #20
0
 protected abstract Task OnDisconnectedAsync(CloseConnectionMessage closeConnectionMessage);
예제 #21
0
 public Task OnClientDisconnectedAsyncForTest(CloseConnectionMessage message)
 {
     return(base.OnClientDisconnectedAsync(message));
 }
 private bool CloseConnectionMessagesEqual(CloseConnectionMessage x, CloseConnectionMessage y)
 {
     return(StringEqual(x.ConnectionId, y.ConnectionId) && StringEqual(x.ErrorMessage, y.ErrorMessage));
 }
예제 #23
0
            protected override async Task OnClientDisconnectedAsync(CloseConnectionMessage message)
            {
                await base.OnClientDisconnectedAsync(message);

                _clientDisconnectedTcs.TrySetResult();
            }