/// <summary> /// Sets a connection to handle cluster events. /// </summary> /// <param name="connection">An optional candidate connection.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that will complete when a new client has been assigned to handle cluster events.</returns> private async Task SetClusterEventsConnectionAsync(MemberConnection connection, CancellationToken cancellationToken) { // this will only exit once a client is assigned, or the task is // cancelled, when the cluster goes down (and never up again) while (!cancellationToken.IsCancellationRequested) { connection ??= _clusterMembers.GetRandomConnection(false); if (connection == null) { // no clients => wait for clients // TODO: consider IRetryStrategy? await Task.Delay(_clusterState.Options.Networking.WaitForClientMilliseconds, cancellationToken).CAF(); continue; } // try to subscribe, relying on the default invocation timeout, // so this is not going to last forever - we know it will end var correlationId = _clusterState.GetNextCorrelationId(); if (!await SubscribeToClusterEventsAsync(connection, correlationId, cancellationToken).CAF()) // does not throw { // failed => try another client connection = null; continue; } // success! using (await _clusterState.ClusterLock.AcquireAsync(CancellationToken.None).CAF()) { _clusterEventsConnection = connection; _clusterEventsCorrelationId = correlationId; // avoid race conditions, this task is going to end, and if the // client dies we want to be sure we restart the task _clusterEventsTask = null; } break; } }
/// <summary> /// Assigns a connection to support the cluster view event. /// </summary> /// <param name="connection">An optional candidate connection.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that will complete when a connection has been assigned to handle the cluster views event.</returns> private async Task AssignClusterViewsConnectionAsync(MemberConnection connection, CancellationToken cancellationToken) { // TODO: consider throttling ValueTask <MemberConnection> WaitRandomConnection(CancellationToken token) { var c = _clusterMembers.GetRandomConnection(); return(c == null ? WaitRandomConnection2(token) : new ValueTask <MemberConnection>(c)); } async ValueTask <MemberConnection> WaitRandomConnection2(CancellationToken token) { MemberConnection c = null; while (!token.IsCancellationRequested && ((c = _clusterMembers.GetRandomConnection()) == null || !c.Active)) { lock (_mutex) _connectionOpened = new TaskCompletionSource <object>(); using var reg = token.Register(() => _connectionOpened.TrySetCanceled()); await _connectionOpened.Task.CfAwait(); lock (_mutex) _connectionOpened = null; } return(c); } // this will only exit once a connection is assigned, or the task is // cancelled, when the cluster goes down (and never up again) while (!cancellationToken.IsCancellationRequested) { connection ??= await WaitRandomConnection(cancellationToken).CfAwait(); // try to subscribe, relying on the default invocation timeout, // so this is not going to last forever - we know it will end var correlationId = _clusterState.GetNextCorrelationId(); if (!await SubscribeToClusterViewsAsync(connection, correlationId, cancellationToken).CfAwait()) // does not throw { // failed => try another connection connection = null; continue; } // success! lock (_clusterViewsMutex) { if (connection.Active) { _clusterViewsConnection = connection; _clusterViewsCorrelationId = correlationId; _clusterViewsTask = null; HConsole.WriteLine(this, $"ClusterViews: connection {connection.Id.ToShortString()} [{correlationId}]"); break; } } // if the connection was not active anymore, we have rejected it // if the connection was active, and we have accepted it, and it de-activates, // then ClearClusterViewsConnection will deal with it } }