/// <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;
            }
        }
Ejemplo n.º 2
0
        /// <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
            }
        }