public async Task <PartitionAddressInformation> UpdateAsync( PartitionKeyRangeIdentity partitionKeyRangeIdentity, CancellationToken cancellationToken) { if (partitionKeyRangeIdentity == null) { throw new ArgumentNullException(nameof(partitionKeyRangeIdentity)); } return(await this.serverPartitionAddressCache.GetAsync( partitionKeyRangeIdentity, null, () => this.GetAddressesForRangeIdAsync( null, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh : true), cancellationToken, forceRefresh : true)); }
public GatewayAddressCacheTests() { this.mockTokenProvider = new Mock <IAuthorizationTokenProvider>(); string payload; this.mockTokenProvider.Setup(foo => foo.GetUserAuthorizationToken(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Documents.Collections.INameValueCollection>(), It.IsAny <AuthorizationTokenType>(), out payload)) .Returns("token!"); this.mockServiceConfigReader = new Mock <IServiceConfigurationReader>(); this.mockServiceConfigReader.Setup(foo => foo.SystemReplicationPolicy).Returns(new ReplicationPolicy() { MaxReplicaSetSize = this.targetReplicaSetSize }); this.mockServiceConfigReader.Setup(foo => foo.UserReplicationPolicy).Returns(new ReplicationPolicy() { MaxReplicaSetSize = this.targetReplicaSetSize }); this.testPartitionKeyRangeIdentity = new PartitionKeyRangeIdentity("YxM9ANCZIwABAAAAAAAAAA==", "YxM9ANCZIwABAAAAAAAAAA=="); this.serviceName = new Uri(GatewayAddressCacheTests.DatabaseAccountApiEndpoint); this.serviceIdentity = new ServiceIdentity("federation1", this.serviceName, false); }
public async Task <PartitionAddressInformation> UpdateAsync( PartitionKeyRangeIdentity partitionKeyRangeIdentity, CancellationToken cancellationToken) { if (partitionKeyRangeIdentity == null) { throw new ArgumentNullException(nameof(partitionKeyRangeIdentity)); } cancellationToken.ThrowIfCancellationRequested(); return(await this.serverPartitionAddressCache.GetAsync( key : partitionKeyRangeIdentity, singleValueInitFunc : () => this.GetAddressesForRangeIdAsync( null, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh: true), forceRefresh : true, callBackOnForceRefresh : null)); }
internal virtual Task <QueryResponseCore> ExecuteQueryAsync( SqlQuerySpec querySpecForInit, string continuationToken, PartitionKeyRangeIdentity partitionKeyRange, bool isContinuationExpected, int pageSize, CancellationToken cancellationToken) { QueryRequestOptions requestOptions = this.QueryRequestOptions.Clone(); return(this.QueryClient.ExecuteItemQueryAsync( resourceUri: this.ResourceLink, resourceType: this.ResourceTypeEnum, operationType: this.OperationTypeEnum, requestOptions: requestOptions, sqlQuerySpec: querySpecForInit, continuationToken: continuationToken, partitionKeyRange: partitionKeyRange, isContinuationExpected: isContinuationExpected, pageSize: pageSize, cancellationToken: cancellationToken)); }
private void PopulatePartitionKeyRangeInfo( RequestMessage request, PartitionKeyRangeIdentity partitionKeyRangeIdentity) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (request.ResourceType.IsPartitioned()) { // If the request already has the logical partition key, // then we shouldn't add the physical partition key range id. bool hasPartitionKey = request.Headers.PartitionKey != null; if (!hasPartitionKey) { request .ToDocumentServiceRequest() .RouteTo(partitionKeyRangeIdentity); } } }
internal override Task <TryCatch <QueryPage> > ExecuteQueryAsync( SqlQuerySpec querySpecForInit, QueryRequestOptions queryRequestOptions, string continuationToken, PartitionKeyRangeIdentity partitionKeyRange, bool isContinuationExpected, int pageSize, CancellationToken cancellationToken) { return(this.QueryClient.ExecuteItemQueryAsync( resourceUri: this.ResourceLink, resourceType: this.ResourceTypeEnum, operationType: this.OperationTypeEnum, clientQueryCorrelationId: this.CorrelatedActivityId, requestOptions: queryRequestOptions, sqlQuerySpec: querySpecForInit, continuationToken: continuationToken, partitionKeyRange: partitionKeyRange, isContinuationExpected: isContinuationExpected, pageSize: pageSize, queryPageDiagnostics: this.AddQueryPageDiagnostic, cancellationToken: cancellationToken)); }
internal Tuple <PartitionKeyRangeIdentity, PartitionAddressInformation> ToPartitionAddressAndRange(string collectionRid, IList <Address> addresses) { Address address = addresses.First(); AddressInformation[] addressInfos = addresses.Select( addr => new AddressInformation { IsPrimary = addr.IsPrimary, PhysicalUri = addr.PhysicalUri, Protocol = ProtocolFromString(addr.Protocol), IsPublic = true }).ToArray(); PartitionKeyRangeIdentity partitionKeyRangeIdentity = new PartitionKeyRangeIdentity(collectionRid, address.PartitionKeyRangeId); return(Tuple.Create( partitionKeyRangeIdentity, new PartitionAddressInformation( addressInfos, partitionKeyRangeIdentity.PartitionKeyRangeId == PartitionKeyRange.MasterPartitionKeyRangeId ? null : partitionKeyRangeIdentity, this.serviceEndpoint))); }
/// <summary> /// Resolves the endpoint of the partition for the given request /// </summary> /// <param name="request">Request for which the partition endpoint resolution is to be performed</param> /// <param name="forceRefreshPartitionAddresses">Force refresh the partition's endpoint</param> /// <param name="cancellationToken">Cancellation token</param> /// <returns>An instance of <see cref="ResolutionResult"/>.</returns> private async Task <ResolutionResult> ResolveAddressesAndIdentityAsync( DocumentServiceRequest request, bool forceRefreshPartitionAddresses, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (request.ServiceIdentity != null) { // In this case we don't populate request.RequestContext.ResolvedPartitionKeyRangeId, // which is needed for session token. // The assumption is that: // 1. Master requests never use session consistency. // 2. Service requests (like collection create etc.) don't use session consistency. // 3. Requests which target specific partition of an existing collection will use x-ms-documentdb-partitionkeyrangeid header // to send request to specific partition and will not set request.ServiceIdentity ServiceIdentity identity = request.ServiceIdentity; PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync(request, null, identity, forceRefreshPartitionAddresses, cancellationToken); if (addresses == null) { DefaultTrace.TraceInformation("Could not get addresses for explicitly specified ServiceIdentity {0}", identity); throw new NotFoundException() { ResourceAddress = request.ResourceAddress }; } return(new ResolutionResult(addresses, identity)); } if (ReplicatedResourceClient.IsReadingFromMaster(request.ResourceType, request.OperationType) && request.PartitionKeyRangeIdentity == null) { DefaultTrace.TraceInformation("Resolving Master service address, forceMasterRefresh: {0}, currentMaster: {1}", request.ForceMasterRefresh, this.masterServiceIdentityProvider?.MasterServiceIdentity); // Client implementation, GlobalAddressResolver passes in a null IMasterServiceIdentityProvider, because it doesn't actually use the serviceIdentity // in the addressCache.TryGetAddresses method. In GatewayAddressCache.cs, the master address is resolved by making a call to Gateway AddressFeed, // not using the serviceIdentity that is passed in if (request.ForceMasterRefresh && this.masterServiceIdentityProvider != null) { ServiceIdentity previousMasterService = this.masterServiceIdentityProvider.MasterServiceIdentity; await this.masterServiceIdentityProvider.RefreshAsync(previousMasterService, cancellationToken); } ServiceIdentity serviceIdentity = this.masterServiceIdentityProvider?.MasterServiceIdentity; PartitionKeyRangeIdentity partitionKeyRangeIdentity = this.masterPartitionKeyRangeIdentity; PartitionAddressInformation addresses = await this.addressCache.TryGetAddressesAsync( request, partitionKeyRangeIdentity, serviceIdentity, forceRefreshPartitionAddresses, cancellationToken); if (addresses == null) { // This shouldn't really happen. DefaultTrace.TraceCritical("Could not get addresses for master partition {0}", serviceIdentity); throw new NotFoundException() { ResourceAddress = request.ResourceAddress }; } PartitionKeyRange partitionKeyRange = new PartitionKeyRange { Id = PartitionKeyRange.MasterPartitionKeyRangeId }; return(new ResolutionResult(partitionKeyRange, addresses, serviceIdentity)); } bool collectionCacheIsUptoDate = !request.IsNameBased || (request.PartitionKeyRangeIdentity != null && request.PartitionKeyRangeIdentity.CollectionRid != null); bool collectionRoutingMapCacheIsUptoDate = false; ContainerProperties collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken); CollectionRoutingMap routingMap = await this.collectionRoutingMapCache.TryLookupAsync( collection.ResourceId, null, request, cancellationToken); if (routingMap != null && request.ForceCollectionRoutingMapRefresh) { DefaultTrace.TraceInformation( "AddressResolver.ResolveAddressesAndIdentityAsync ForceCollectionRoutingMapRefresh collection.ResourceId = {0}", collection.ResourceId); routingMap = await this.collectionRoutingMapCache.TryLookupAsync(collection.ResourceId, routingMap, request, cancellationToken); } if (request.ForcePartitionKeyRangeRefresh) { collectionRoutingMapCacheIsUptoDate = true; request.ForcePartitionKeyRangeRefresh = false; if (routingMap != null) { routingMap = await this.collectionRoutingMapCache.TryLookupAsync(collection.ResourceId, routingMap, request, cancellationToken); } } if (routingMap == null && !collectionCacheIsUptoDate) { // Routing map was not found by resolved collection rid. Maybe collection rid is outdated. // Refresh collection cache and reresolve routing map. request.ForceNameCacheRefresh = true; collectionCacheIsUptoDate = true; collectionRoutingMapCacheIsUptoDate = false; collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken); routingMap = await this.collectionRoutingMapCache.TryLookupAsync( collection.ResourceId, previousValue : null, request : request, cancellationToken : cancellationToken); } AddressResolver.EnsureRoutingMapPresent(request, routingMap, collection); // At this point we have both collection and routingMap. ResolutionResult result = await this.TryResolveServerPartitionAsync( request, collection, routingMap, collectionCacheIsUptoDate, collectionRoutingMapCacheIsUptodate : collectionRoutingMapCacheIsUptoDate, forceRefreshPartitionAddresses : forceRefreshPartitionAddresses, cancellationToken : cancellationToken); if (result == null) { // Couldn't resolve server partition or its addresses. // Either collection cache is outdated or routing map cache is outdated. if (!collectionCacheIsUptoDate) { request.ForceNameCacheRefresh = true; collectionCacheIsUptoDate = true; collection = await this.collectionCache.ResolveCollectionAsync(request, cancellationToken); if (collection.ResourceId != routingMap.CollectionUniqueId) { // Collection cache was stale. We resolved to new Rid. routing map cache is potentially stale // for this new collection rid. Mark it as such. collectionRoutingMapCacheIsUptoDate = false; routingMap = await this.collectionRoutingMapCache.TryLookupAsync( collection.ResourceId, previousValue : null, request : request, cancellationToken : cancellationToken); } } if (!collectionRoutingMapCacheIsUptoDate) { collectionRoutingMapCacheIsUptoDate = true; routingMap = await this.collectionRoutingMapCache.TryLookupAsync( collection.ResourceId, previousValue : routingMap, request : request, cancellationToken : cancellationToken); } AddressResolver.EnsureRoutingMapPresent(request, routingMap, collection); result = await this.TryResolveServerPartitionAsync( request, collection, routingMap, collectionCacheIsUptodate : true, collectionRoutingMapCacheIsUptodate : true, forceRefreshPartitionAddresses : forceRefreshPartitionAddresses, cancellationToken : cancellationToken); } if (result == null) { DefaultTrace.TraceInformation("Couldn't route partitionkeyrange-oblivious request after retry/cache refresh. Collection doesn't exist."); // At this point collection cache and routing map caches are refreshed. // The only reason we will get here is if collection doesn't exist. // Case when partitionkeyrange doesn't exist is handled in the corresponding method. throw new NotFoundException() { ResourceAddress = request.ResourceAddress }; } if (request.IsNameBased) { // Append collection rid. // If we resolved collection rid incorrectly because of outdated cache, this can lead // to incorrect routing decisions. But backend will validate collection rid and throw // InvalidPartitionException if we reach wrong collection. // Also this header will be used by backend to inject collection rid into metrics for // throttled requests. request.Headers[WFConstants.BackendHeaders.CollectionRid] = collection.ResourceId; } return(result); }
private static TryCatch <QueryPage> GetCosmosElementResponse( Guid clientQueryCorrelationId, QueryRequestOptions requestOptions, ResourceType resourceType, ResponseMessage cosmosResponseMessage, PartitionKeyRangeIdentity partitionKeyRangeIdentity, Action <QueryPageDiagnostics> queryPageDiagnostics, ITrace trace) { using (ITrace getCosmosElementResponse = trace.StartChild("Get Cosmos Element Response", TraceComponent.Json, Tracing.TraceLevel.Info)) { using (cosmosResponseMessage) { QueryPageDiagnostics queryPage = new QueryPageDiagnostics( clientQueryCorrelationId: clientQueryCorrelationId, partitionKeyRangeId: partitionKeyRangeIdentity.PartitionKeyRangeId, queryMetricText: cosmosResponseMessage.Headers.QueryMetricsText, indexUtilizationText: cosmosResponseMessage.Headers[HttpConstants.HttpHeaders.IndexUtilization], diagnosticsContext: cosmosResponseMessage.DiagnosticsContext); queryPageDiagnostics(queryPage); if ( cosmosResponseMessage.Headers.QueryMetricsText != null && BackendMetricsParser.TryParse(cosmosResponseMessage.Headers.QueryMetricsText, out BackendMetrics backendMetrics)) { QueryMetricsTraceDatum datum = new QueryMetricsTraceDatum( new QueryMetrics(backendMetrics, IndexUtilizationInfo.Empty, ClientSideMetrics.Empty)); trace.AddDatum("Query Metrics", datum); } if (!cosmosResponseMessage.IsSuccessStatusCode) { CosmosException exception; if (cosmosResponseMessage.CosmosException != null) { exception = cosmosResponseMessage.CosmosException; } else { exception = new CosmosException( cosmosResponseMessage.ErrorMessage, cosmosResponseMessage.StatusCode, (int)cosmosResponseMessage.Headers.SubStatusCode, cosmosResponseMessage.Headers.ActivityId, cosmosResponseMessage.Headers.RequestCharge); } return(TryCatch <QueryPage> .FromException(exception)); } if (!(cosmosResponseMessage.Content is MemoryStream memoryStream)) { memoryStream = new MemoryStream(); cosmosResponseMessage.Content.CopyTo(memoryStream); } long responseLengthBytes = memoryStream.Length; CosmosArray documents = CosmosQueryClientCore.ParseElementsFromRestStream( memoryStream, resourceType, requestOptions.CosmosSerializationFormatOptions); CosmosQueryExecutionInfo cosmosQueryExecutionInfo; if (cosmosResponseMessage.Headers.TryGetValue(QueryExecutionInfoHeader, out string queryExecutionInfoString)) { cosmosQueryExecutionInfo = JsonConvert.DeserializeObject <CosmosQueryExecutionInfo>(queryExecutionInfoString); } else { cosmosQueryExecutionInfo = default; } QueryState queryState; if (cosmosResponseMessage.Headers.ContinuationToken != null) { queryState = new QueryState(CosmosString.Create(cosmosResponseMessage.Headers.ContinuationToken)); } else { queryState = default; } QueryPage response = new QueryPage( documents, cosmosResponseMessage.Headers.RequestCharge, cosmosResponseMessage.Headers.ActivityId, responseLengthBytes, cosmosQueryExecutionInfo, disallowContinuationTokenMessage: null, queryState); return(TryCatch <QueryPage> .FromResult(response)); } } }
private async Task <CosmosContainerSettings> ResolveByPartitionKeyRangeIdentityAsync(string apiVersion, PartitionKeyRangeIdentity partitionKeyRangeIdentity, CancellationToken cancellationToken) { // if request is targeted at specific partition using x-ms-documentd-partitionkeyrangeid header, // which contains value "<collectionrid>,<partitionkeyrangeid>", then resolve to collection rid in this header. if (partitionKeyRangeIdentity != null && partitionKeyRangeIdentity.CollectionRid != null) { try { return(await this.ResolveByRidAsync(apiVersion, partitionKeyRangeIdentity.CollectionRid, cancellationToken)); } catch (NotFoundException) { // This is signal to the upper logic either in Gateway or client SDK to refresh // collection cache and retry. throw new InvalidPartitionException(RMResources.InvalidDocumentCollection); } } return(null); }
public async Task <PartitionAddressInformation> TryGetAddressesAsync( DocumentServiceRequest request, PartitionKeyRangeIdentity partitionKeyRangeIdentity, ServiceIdentity serviceIdentity, bool forceRefreshPartitionAddresses, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (partitionKeyRangeIdentity == null) { throw new ArgumentNullException(nameof(partitionKeyRangeIdentity)); } try { if (partitionKeyRangeIdentity.PartitionKeyRangeId == PartitionKeyRange.MasterPartitionKeyRangeId) { return((await this.ResolveMasterAsync(request, forceRefreshPartitionAddresses)).Item2); } DateTime suboptimalServerPartitionTimestamp; if (this.suboptimalServerPartitionTimestamps.TryGetValue(partitionKeyRangeIdentity, out suboptimalServerPartitionTimestamp)) { bool forceRefreshDueToSuboptimalPartitionReplicaSet = DateTime.UtcNow.Subtract(suboptimalServerPartitionTimestamp) > TimeSpan.FromSeconds(this.suboptimalPartitionForceRefreshIntervalInSeconds); if (forceRefreshDueToSuboptimalPartitionReplicaSet && this.suboptimalServerPartitionTimestamps.TryUpdate(partitionKeyRangeIdentity, DateTime.MaxValue, suboptimalServerPartitionTimestamp)) { forceRefreshPartitionAddresses = true; } } PartitionAddressInformation addresses; if (forceRefreshPartitionAddresses || request.ForceCollectionRoutingMapRefresh) { addresses = await this.serverPartitionAddressCache.GetAsync( partitionKeyRangeIdentity, null, () => this.GetAddressesForRangeIdAsync( request, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh : forceRefreshPartitionAddresses), cancellationToken, forceRefresh : true); DateTime ignoreDateTime; this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime); } else { addresses = await this.serverPartitionAddressCache.GetAsync( partitionKeyRangeIdentity, null, () => this.GetAddressesForRangeIdAsync( request, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh : false), cancellationToken); } int targetReplicaSetSize = this.serviceConfigReader.UserReplicationPolicy.MaxReplicaSetSize; if (addresses.AllAddresses.Count() < targetReplicaSetSize) { this.suboptimalServerPartitionTimestamps.TryAdd(partitionKeyRangeIdentity, DateTime.UtcNow); } return(addresses); } catch (DocumentClientException ex) { if ((ex.StatusCode == HttpStatusCode.NotFound) || (ex.StatusCode == HttpStatusCode.Gone && ex.GetSubStatus() == SubStatusCodes.PartitionKeyRangeGone)) { //remove from suboptimal cache in case the the collection+pKeyRangeId combo is gone. DateTime ignoreDateTime; this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime); return(null); } throw; } catch (Exception) { if (forceRefreshPartitionAddresses) { DateTime ignoreDateTime; this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out ignoreDateTime); } throw; } }
private static async Task <Tuple <bool, PartitionKeyRange> > TryResolvePartitionKeyRangeAsync(DocumentServiceRequest request, ISessionContainer sessionContainer, PartitionKeyRangeCache partitionKeyRangeCache, ClientCollectionCache clientCollectionCache, bool refreshCache) { if (refreshCache) { request.ForceMasterRefresh = true; request.ForceNameCacheRefresh = true; } PartitionKeyRange partitonKeyRange = null; ContainerProperties collection = await clientCollectionCache.ResolveCollectionAsync(request, CancellationToken.None, NoOpTrace.Singleton); string partitionKeyString = request.Headers[HttpConstants.HttpHeaders.PartitionKey]; if (partitionKeyString != null) { CollectionRoutingMap collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(collectionRid : collection.ResourceId, previousValue : null, request : request, cancellationToken : CancellationToken.None, NoOpTrace.Singleton); if (refreshCache && collectionRoutingMap != null) { collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(collectionRid : collection.ResourceId, previousValue : collectionRoutingMap, request : request, cancellationToken : CancellationToken.None, NoOpTrace.Singleton); } partitonKeyRange = AddressResolver.TryResolveServerPartitionByPartitionKey(request: request, partitionKeyString: partitionKeyString, collectionCacheUptoDate: false, collection: collection, routingMap: collectionRoutingMap); } else if (request.PartitionKeyRangeIdentity != null) { PartitionKeyRangeIdentity partitionKeyRangeId = request.PartitionKeyRangeIdentity; partitonKeyRange = await partitionKeyRangeCache.TryGetPartitionKeyRangeByIdAsync(collection.ResourceId, partitionKeyRangeId.ToString(), NoOpTrace.Singleton, refreshCache); } if (partitonKeyRange == null) { if (refreshCache) { return(new Tuple <bool, PartitionKeyRange>(false, null)); } // need to refresh cache. Maybe split happened. return(await GatewayStoreModel.TryResolvePartitionKeyRangeAsync(request : request, sessionContainer : sessionContainer, partitionKeyRangeCache : partitionKeyRangeCache, clientCollectionCache : clientCollectionCache, refreshCache : true)); } return(new Tuple <bool, PartitionKeyRange>(true, partitonKeyRange)); }
public async Task <PartitionAddressInformation> TryGetAddressesAsync( DocumentServiceRequest request, PartitionKeyRangeIdentity partitionKeyRangeIdentity, ServiceIdentity serviceIdentity, bool forceRefreshPartitionAddresses, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (partitionKeyRangeIdentity == null) { throw new ArgumentNullException(nameof(partitionKeyRangeIdentity)); } try { if (partitionKeyRangeIdentity.PartitionKeyRangeId == PartitionKeyRange.MasterPartitionKeyRangeId) { return((await this.ResolveMasterAsync(request, forceRefreshPartitionAddresses)).Item2); } if (this.suboptimalServerPartitionTimestamps.TryGetValue(partitionKeyRangeIdentity, out DateTime suboptimalServerPartitionTimestamp)) { bool forceRefreshDueToSuboptimalPartitionReplicaSet = DateTime.UtcNow.Subtract(suboptimalServerPartitionTimestamp) > TimeSpan.FromSeconds(this.suboptimalPartitionForceRefreshIntervalInSeconds); if (forceRefreshDueToSuboptimalPartitionReplicaSet && this.suboptimalServerPartitionTimestamps.TryUpdate(partitionKeyRangeIdentity, DateTime.MaxValue, suboptimalServerPartitionTimestamp)) { forceRefreshPartitionAddresses = true; } } PartitionAddressInformation addresses; PartitionAddressInformation staleAddressInfo = null; if (forceRefreshPartitionAddresses || request.ForceCollectionRoutingMapRefresh) { addresses = await this.serverPartitionAddressCache.GetAsync( key : partitionKeyRangeIdentity, singleValueInitFunc : (currentCachedValue) => { staleAddressInfo = currentCachedValue; GatewayAddressCache.SetTransportAddressUrisToUnhealthy( currentCachedValue, request?.RequestContext?.FailedEndpoints); return(this.GetAddressesForRangeIdAsync( request, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh: forceRefreshPartitionAddresses)); }, forceRefresh : (currentCachedValue) => { int cachedHashCode = request?.RequestContext?.LastPartitionAddressInformationHashCode ?? 0; if (cachedHashCode == 0) { return(true); } // The cached value is different then the previous access hash then assume // another request already updated the cache since there is a new value in the cache return(currentCachedValue.GetHashCode() == cachedHashCode); }); if (staleAddressInfo != null) { GatewayAddressCache.LogPartitionCacheRefresh(request.RequestContext.ClientRequestStatistics, staleAddressInfo, addresses); } this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out DateTime ignoreDateTime); } else { addresses = await this.serverPartitionAddressCache.GetAsync( key : partitionKeyRangeIdentity, singleValueInitFunc : (_) => this.GetAddressesForRangeIdAsync( request, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh: false), forceRefresh : (_) => false); } // Always save the hash code. This is used to determine if another request already updated the cache. // This helps reduce latency by avoiding uncessary cache refreshes. if (request?.RequestContext != null) { request.RequestContext.LastPartitionAddressInformationHashCode = addresses.GetHashCode(); } int targetReplicaSetSize = this.serviceConfigReader.UserReplicationPolicy.MaxReplicaSetSize; if (addresses.AllAddresses.Count() < targetReplicaSetSize) { this.suboptimalServerPartitionTimestamps.TryAdd(partitionKeyRangeIdentity, DateTime.UtcNow); } return(addresses); } catch (DocumentClientException ex) { if ((ex.StatusCode == HttpStatusCode.NotFound) || (ex.StatusCode == HttpStatusCode.Gone && ex.GetSubStatus() == SubStatusCodes.PartitionKeyRangeGone)) { //remove from suboptimal cache in case the the collection+pKeyRangeId combo is gone. this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out _); return(null); } throw; } catch (Exception) { if (forceRefreshPartitionAddresses) { this.suboptimalServerPartitionTimestamps.TryRemove(partitionKeyRangeIdentity, out _); } throw; } }