// runs once on the whole cluster private async Task RunAsync(CancellationToken cancellationToken) { _logger.LogDebug("Run heartbeat"); var now = DateTime.UtcNow; var connections = _clusterMembers.SnapshotConnections(true); // start one task per member // TODO: throttle? var tasks = connections .Select(x => RunAsync(x, now, cancellationToken)) .ToList(); await Task.WhenAll(tasks).CAF(); // may throw in case of cancellation }
// runs once on the whole cluster private async Task RunAsync(CancellationToken cancellationToken) { _logger.LogDebug("Run heartbeat"); var now = DateTime.Now; // now, or utcNow, but *must* be same as what is used in socket connection base! var connections = _clusterMembers.SnapshotConnections(true); // start one task per member // TODO: throttle? var tasks = connections .Select(x => RunAsync(x, now, cancellationToken)) .ToList(); await Task.WhenAll(tasks).CfAwait(); // may throw in case of cancellation }
/// <summary> /// Installs a subscription on the cluster, i.e. on each member. /// </summary> /// <param name="subscription">The subscription.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that will complete when the subscription has been installed.</returns> public async Task InstallSubscriptionAsync(ClusterSubscription subscription, CancellationToken cancellationToken = default) { if (subscription == null) { throw new ArgumentNullException(nameof(subscription)); } // capture active clients, and adds the subscription - atomically. List <MemberConnection> connections; lock (_clusterState.Mutex) { connections = _clusterMembers.SnapshotConnections(true); if (!_subscriptions.TryAdd(subscription.Id, subscription)) { throw new InvalidOperationException("A subscription with the same identifier already exists."); } } // from now on, // - if new clients are added, we won't deal with them here, but they will // subscribe on their own since the subscription is now listed. // - if a captured client goes away while we install subscriptions, we // will just ignore the associated errors and skip it entirely. // subscribe each captured client // TODO: could we install in parallel? // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var connection in connections) { // don't even try clients that became inactive if (!connection.Active) { continue; } // this never throws var attempt = await InstallSubscriptionAsync(subscription, connection, cancellationToken).CfAwait(); switch (attempt.Value) { case InstallResult.Success: case InstallResult.ClientNotActive: continue; case InstallResult.SubscriptionNotActive: case InstallResult.ConfusedServer: // not active: some other code must have // - removed the subscriptions from _subscriptions // - dealt with its existing clients // nothing left to do here throw new HazelcastException(attempt.Value == InstallResult.SubscriptionNotActive ? "Failed to install the subscription because it was removed." : "Failed to install the subscription because it was removed (and the server may be confused).", attempt.Exception); case InstallResult.Failed: // failed: client is active but installing the subscription failed // however, we might have installed it on other clients await RemoveSubscriptionAsync(subscription, cancellationToken).CfAwait(); throw new HazelcastException("Failed to install subscription (see inner exception).", attempt.Exception); default: throw new NotSupportedException(); } } }