/// <summary> /// Retrieves the next reachable <see cref="RemoteHost"/>. /// </summary> /// <param name="affinityToken"> /// A string to generate a consistent affinity to a specific host within the set of available hosts. /// Identical strings will return the same host for a given pool of reachable hosts. A request ID is usually provided. /// </param> /// <returns>A reachable <see cref="RemoteHost"/>.</returns> /// <exception cref="EnvironmentException">Thrown when there is no reachable <see cref="RemoteHost"/> available.</exception> public IEndPointHandle GetNextHost(string affinityToken = null) { LastEndpointRequest = DateTime.UtcNow; var hostOverride = TracingContext.GetHostOverride(DeploymentIdentifier.ServiceName); if (hostOverride != null) { return(new OverriddenRemoteHost(DeploymentIdentifier.ServiceName, hostOverride.Hostname, hostOverride.Port ?? GetConfig().DefaultPort)); } lock (_lock) { Health.Activate(); if (ReachableHosts.Count == 0) { var lastExceptionEndPoint = UnreachableHosts.FirstOrDefault(); // TODO: Exception throwing code should be in this class, not in another. throw DiscoverySource.AllEndpointsUnreachable(EndPointsResult, lastExceptionEndPoint?.LastException, lastExceptionEndPoint == null ? null : $"{lastExceptionEndPoint.HostName}:{lastExceptionEndPoint.Port}", string.Join(", ", UnreachableHosts)); } Counter++; ulong hostId = affinityToken == null ? Counter : (ulong)affinityToken.GetHashCode(); return(ReachableHosts[(int)(hostId % (ulong)ReachableHosts.Count)]); } }
/// <summary> /// Loads the specified settings, overwriting existing settings. /// </summary> /// <param name="updatedEndPointsResult"></param> /// this <see cref="RemoteHostPool"/>. /// <exception cref="ArgumentNullException">Thrown when </exception> /// <exception cref="EnvironmentException"></exception> private void ReloadEndpoints(EndPointsResult updatedEndPointsResult) { lock (_lock) { try { var updatedEndPoints = updatedEndPointsResult.EndPoints; if (updatedEndPoints.Any() == false) { Health.SetHealthFunction(() => { var config = GetConfig(); if (IsHealthCheckSuppressed(config)) { return(HealthCheckResult.Healthy($"No endpoints were discovered from source '{config.Source}' but the remote service was not in use for more than {config.SuppressHealthCheckAfterServiceUnused?.TotalSeconds} seconds.")); } else { return(HealthCheckResult.Unhealthy($"No endpoints were discovered from source '{config.Source}'.")); } }); EndPointsResult = updatedEndPointsResult; ReachableHosts = new List <RemoteHost>(); UnreachableHosts = new List <RemoteHost>(); } else { if (EndPoints != null) { foreach (var removedEndPoint in EndPoints.Except(updatedEndPoints)) { ReachableHosts.SingleOrDefault(h => h.Equals(removedEndPoint))?.StopMonitoring(); ReachableHosts.RemoveAll(h => h.Equals(removedEndPoint)); UnreachableHosts.RemoveAll(h => h.Equals(removedEndPoint)); } } var newHosts = updatedEndPoints .Except(EndPoints ?? Enumerable.Empty <EndPoint>()) .Select(ep => new RemoteHost(ep.HostName, this, _lock, ep.Port)); ReachableHosts.AddRange(newHosts); EndPointsResult = updatedEndPointsResult; Counter = (ulong)_random.Next(0, ReachableHosts.Count); Health.SetHealthFunction(CheckHealth); } } catch (Exception ex) { Log.Warn("Failed to process newly discovered endpoints", exception: ex); Health.SetHealthFunction(() => HealthCheckResult.Unhealthy("Failed to process newly discovered endpoints: " + HealthMonitor.GetMessages(ex))); } } }
/// <summary> /// Resets the state of all hosts as reachable. /// </summary> public void MarkAllAsReachable() { lock (_lock) { foreach (var unreachableHost in UnreachableHosts.ToArray()) { unreachableHost.ReportSuccess(); MarkReachable(unreachableHost); } } }
private Dictionary <string, string> HealthData() { lock (_lock) { return(new Dictionary <string, string> { { "ReachableHosts", string.Join(",", ReachableHosts.Select(_ => _.HostName)) }, { "UnreachableHosts", string.Join(",", UnreachableHosts.Select(_ => _.HostName)) } }); } }
internal bool MarkUnreachable(RemoteHost remoteHost) { lock (_lock) { if (ReachableHosts.Remove(remoteHost)) { if (ReachableHosts.Count == 0) { ReachabilityBroadcaster.Post(new ServiceReachabilityStatus { IsReachable = false }); } UnreachableHosts.Add(remoteHost); return(true); } return(false); } }
private ValueTask <HealthCheckResult> CheckHealth() { var config = GetConfig(); if (IsHealthCheckSuppressed(config)) { return(new ValueTask <HealthCheckResult>(HealthCheckResult.Healthy($"Health check suppressed because service was not in use for more than {config.SuppressHealthCheckAfterServiceUnused.TotalSeconds} seconds."))); } int reachableCount; int unreachableCount; Exception exception; string[] unreachableHosts; lock (_lock) { reachableCount = ReachableHosts.Count; unreachableHosts = UnreachableHosts.Select(x => $"{x.HostName}:{x.Port}").ToArray(); unreachableCount = unreachableHosts.Length; exception = UnreachableHosts.FirstOrDefault()?.LastException; } if (reachableCount == 0) { return(new ValueTask <HealthCheckResult>(HealthCheckResult.Unhealthy($"All of the {unreachableCount} hosts are unreachable: " + $"{string.Join(",", unreachableHosts)}. Last exception: {HealthMonitor.GetMessages(exception)}"))); } else { if (unreachableCount > 0) { return(new ValueTask <HealthCheckResult>(HealthCheckResult.Unhealthy($"The following {unreachableCount} hosts " + $"(out of {unreachableCount + reachableCount}) are unreachable: {string.Join(",", unreachableHosts)}. " + $"Last exception: {HealthMonitor.GetMessages(exception)}"))); } else { return(new ValueTask <HealthCheckResult>(HealthCheckResult.Healthy($"All {reachableCount} hosts are reachable."))); } } }
internal bool MarkReachable(RemoteHost remoteHost) { lock (_lock) { if (UnreachableHosts.Remove(remoteHost)) { ReachableHosts.Add(remoteHost); if (ReachableHosts.Count == 1) { ReachabilityBroadcaster.Post(new ServiceReachabilityStatus { IsReachable = true }); } FirstAvailableHostCompletionSource?.SetResult(remoteHost); FirstAvailableHostCompletionSource = null; return(true); } return(false); } }