private void AddClientConnection(ClientConnectionContext connection, OpenConnectionMessage message) { var instanceId = GetInstanceId(message.Headers); _clientConnectionManager.TryAddClientConnection(connection); _connectionIds.TryAdd(connection.ConnectionId, instanceId); }
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); } } }
private Task ProcessClientConnectionAsync(ClientConnectionContext connection) { // Writing from the application to the service var outgoing = ProcessOutgoingMessagesAsync(connection, connection.OutgoingAborted); // Waiting for the application to shutdown so we can clean up the connection _ = ProcessIncomingMessageAsync(connection); // TODO: add more details // Current clean up is inside outgoing task when outgoing task completes return(outgoing); }
private async Task ProcessApplicationTaskAsync(ClientConnectionContext connection) { // Wait for the application task to complete // application task can end when exception, or Context.Abort() from hub var app = ProcessApplicationTaskAsyncCore(connection); var cancelTask = connection.ApplicationAborted.AsTask(); var task = await Task.WhenAny(app, cancelTask); if (task != app) { // cancel the application task, to end the outgoing task connection.Application.Input.CancelPendingRead(); Log.ApplicationTaskCancelled(Logger); } }
private async Task ProcessIncomingMessageAsync(ClientConnectionContext connection) { // Wait for the application task to complete // application task can end when exception, or Context.Abort() from hub var app = ProcessApplicationTaskAsyncCore(connection); var cancelTask = connection.ApplicationAborted.AsTask(); var task = await Task.WhenAny(app, cancelTask); if (task == app) { await task; } else { // cancel the application task, to end the outgoing task connection.Application.Input.CancelPendingRead(); throw new AzureSignalRException("Cancelled running application task, probably caused by time out."); } }
private async Task ProcessApplicationTaskAsyncCore(ClientConnectionContext connection) { Exception exception = null; try { // Wait for the application task to complete // application task can end when exception, or Context.Abort() from hub await _connectionDelegate(connection); } catch (Exception ex) { // Capture the exception to communicate it to the transport (this isn't strictly required) exception = ex; throw; } finally { // Close the transport side since the application is no longer running connection.Transport.Output.Complete(exception); connection.Transport.Input.Complete(); } }
private async Task <bool> SkipHandshakeResponse(ClientConnectionContext connection, CancellationToken token) { try { while (true) { var result = await connection.Application.Input.ReadAsync(token); if (result.IsCanceled || token.IsCancellationRequested) { return(false); } var buffer = result.Buffer; if (buffer.IsEmpty) { continue; } if (SignalRProtocol.HandshakeProtocol.TryParseResponseMessage(ref buffer, out var message)) { connection.Application.Input.AdvanceTo(buffer.Start); return(true); } if (result.IsCompleted) { return(false); } } } catch (Exception ex) { Log.ErrorSkippingHandshakeResponse(Logger, ex); } return(false); }
public void AddClientConnection(ClientConnectionContext clientConnection) { _clientConnections[clientConnection.ConnectionId] = clientConnection; }
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(); } }
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(); } }
private ServiceMessage CreateMessage(string connectionId, string methodName, object[] args, ClientConnectionContext serviceConnectionContext) { if (serviceConnectionContext.Protocol != null) { var message = new ConnectionDataMessage(connectionId, SerializeProtocol(serviceConnectionContext.Protocol, methodName, args)).WithTracingId(); if (message.TracingId != null) { MessageLog.StartToSendMessageToConnection(Logger, message); } return(message); } else { var message = new MultiConnectionDataMessage(new[] { connectionId }, SerializeAllProtocols(methodName, args)).WithTracingId(); if (message.TracingId != null) { MessageLog.StartToSendMessageToConnections(Logger, message); } return(message); } }
private ServiceMessage CreateMessage(string connectionId, string methodName, object[] args, ClientConnectionContext serviceConnectionContext) { IDictionary <string, ReadOnlyMemory <byte> > payloads; if (serviceConnectionContext.Protocol != null) { payloads = new Dictionary <string, ReadOnlyMemory <byte> >() { { serviceConnectionContext.Protocol, SerializeProtocol(serviceConnectionContext.Protocol, methodName, args) } }; } else { payloads = SerializeAllProtocols(methodName, args); } // don't use ConnectionDataMessage here, since handshake message is also wrapped into ConnectionDataMessage. // otherwise it may cause the handshake failure due to hub invocation message is sent to client before handshake message, when there's high preasure on server. // do use ConnectionDataMessage when the message is sent from client. var message = new MultiConnectionDataMessage(new[] { connectionId }, payloads).WithTracingId(); if (message.TracingId != null) { MessageLog.StartToSendMessageToConnections(Logger, message); } return(message); }
public bool TryRemoveClientConnection(string connectionId, out ClientConnectionContext connection) { return(_clientConnections.TryRemove(connectionId, out connection)); }
public bool TryAddClientConnection(ClientConnectionContext connection) { return(_clientConnections.TryAdd(connection.ConnectionId, connection)); }