From 5ede1ec41b3d584fe4ea81c39d41269036c41ca0 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 8 May 2026 12:31:56 -0700 Subject: [PATCH 1/3] Fix 400/1024 error when container is re-created with same name Reset request context (resolvedCollectionRid, forceNameCacheRefresh, INTENDED_COLLECTION_RID_HEADER) in StaleResourceRetryPolicy before retry so the retry re-resolves the collection and sends the correct RID. Fixes Azure/azure-sdk-for-java#49097 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...StaleResourceExceptionRetryPolicyTest.java | 70 +++++++++++++++++++ .../StaleResourceRetryPolicy.java | 8 +++ 2 files changed, 78 insertions(+) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java index 76a5d3f6bc08..0a7deb618062 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java @@ -98,6 +98,76 @@ public void suppressRetryForExternalCollectionRid() { assertThat(shouldRetryResult.shouldRetry).isFalse(); } + @DataProvider(name = "staleContainerExceptionProvider") + public Object[][] staleContainerExceptionProvider() { + return new Object[][] { + //status code, subStatusCode + { HttpConstants.StatusCodes.GONE, HttpConstants.SubStatusCodes.NAME_CACHE_IS_STALE }, + { HttpConstants.StatusCodes.BADREQUEST, HttpConstants.SubStatusCodes.INCORRECT_CONTAINER_RID_SUB_STATUS }, + }; + } + + @Test(groups = "unit", dataProvider = "staleContainerExceptionProvider") + public void requestContextResetOnRetry(int statusCode, int subStatusCode) { + // Validates that when StaleResourceRetryPolicy retries a stale-container + // exception, it resets the request context so the retry re-resolves the + // collection and sends the updated intended-collection-rid header. + String testCollectionLink = "/dbs/test/colls/contextResetTest"; + + DocumentCollection documentCollection = new DocumentCollection(); + documentCollection.setId("contextResetTest"); + documentCollection.setResourceId("oldRid"); + + DocumentCollection documentCollectionAfterRefresh = new DocumentCollection(); + documentCollectionAfterRefresh.setId("contextResetTest"); + documentCollectionAfterRefresh.setResourceId("newRid"); + + RxCollectionCache rxCollectionCache = Mockito.mock(RxCollectionCache.class); + Mockito + .when(rxCollectionCache.resolveByNameAsync(Mockito.any(), Mockito.any(), Mockito.isNull(), Mockito.isNull(), Mockito.isNull())) + .thenReturn(Mono.just(documentCollection)) + .thenReturn(Mono.just(documentCollectionAfterRefresh)); + doNothing().when(rxCollectionCache).refresh(Mockito.any(), Mockito.any(), Mockito.isNull()); + + ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); + doNothing().when(sessionContainer).clearTokenByResourceId(documentCollection.getResourceId()); + + StaleResourceRetryPolicy staleResourceRetryPolicy = new StaleResourceRetryPolicy( + rxCollectionCache, + null, + testCollectionLink, + null, + null, + sessionContainer, + TestUtils.mockDiagnosticsClientContext(), + null + ); + + // Simulate a request with a stale resolvedCollectionRid and intended header + RxDocumentServiceRequest request = RxDocumentServiceRequest.createFromName( + TestUtils.mockDiagnosticsClientContext(), + OperationType.Read, "/dbs/test/colls/contextResetTest/docs/doc1", ResourceType.Document); + request.requestContext = new DocumentServiceRequestContext(); + request.requestContext.resolvedCollectionRid = "oldRid"; + request.getHeaders().put(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER, "oldRid"); + + staleResourceRetryPolicy.onBeforeSendRequest(request); + + CosmosException exception = BridgeInternal.createCosmosException(statusCode); + BridgeInternal.setSubStatusCode(exception, subStatusCode); + + ShouldRetryResult shouldRetryResult = staleResourceRetryPolicy.shouldRetry(exception).block(); + assertThat(shouldRetryResult.shouldRetry).isTrue(); + + // Verify request context was reset for a clean retry + assertThat(request.requestContext.resolvedCollectionRid).isNull(); + assertThat(request.forceNameCacheRefresh).isTrue(); + assertThat(request.getHeaders().get(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER)).isNull(); + + // Verify session token was cleaned up for the old rid + verify(sessionContainer, Mockito.times(1)).clearTokenByResourceId("oldRid"); + } + @Test(groups = "unit") public void cleanSessionToken() { String testCollectionLink = "/dbs/test/colls/staledExceptionTest"; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java index b3e8d0274a91..c0eb8cb8d0ea 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java @@ -124,6 +124,14 @@ public Mono shouldRetry(Exception e) { this.sessionContainer.clearTokenByResourceId(oldCollectionRid.get()); } + // Reset request context so the retry re-resolves the collection + // and sends the updated intended-collection-rid header. + if (this.request != null) { + this.request.forceNameCacheRefresh = true; + this.request.requestContext.resolvedCollectionRid = null; + this.request.getHeaders().remove(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER); + } + this.retried = true; if (this.shouldSuppressRetry.get()) { return Mono.just(ShouldRetryResult.error(e)); From 2e5daf74422839e322300649d40d63988b780dcd Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 8 May 2026 12:43:03 -0700 Subject: [PATCH 2/3] Address review: use refreshed RID, add null guard, drop forceNameCacheRefresh - Set resolvedCollectionRid to refreshedCollectionRid instead of null - Remove unnecessary forceNameCacheRefresh (cache already refreshed) - Add requestContext null guard to prevent NPE Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../StaleResourceExceptionRetryPolicyTest.java | 5 ++--- .../implementation/StaleResourceRetryPolicy.java | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java index 0a7deb618062..2b1f26de1098 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StaleResourceExceptionRetryPolicyTest.java @@ -159,9 +159,8 @@ public void requestContextResetOnRetry(int statusCode, int subStatusCode) { ShouldRetryResult shouldRetryResult = staleResourceRetryPolicy.shouldRetry(exception).block(); assertThat(shouldRetryResult.shouldRetry).isTrue(); - // Verify request context was reset for a clean retry - assertThat(request.requestContext.resolvedCollectionRid).isNull(); - assertThat(request.forceNameCacheRefresh).isTrue(); + // Verify request context was updated for a clean retry + assertThat(request.requestContext.resolvedCollectionRid).isEqualTo("newRid"); assertThat(request.getHeaders().get(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER)).isNull(); // Verify session token was cleaned up for the old rid diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java index c0eb8cb8d0ea..cb4edc8d2373 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/StaleResourceRetryPolicy.java @@ -124,11 +124,11 @@ public Mono shouldRetry(Exception e) { this.sessionContainer.clearTokenByResourceId(oldCollectionRid.get()); } - // Reset request context so the retry re-resolves the collection - // and sends the updated intended-collection-rid header. - if (this.request != null) { - this.request.forceNameCacheRefresh = true; - this.request.requestContext.resolvedCollectionRid = null; + // Update request context with the refreshed collection rid + // and remove the stale intended-collection-rid header so it + // gets re-populated on retry. + if (this.request != null && this.request.requestContext != null) { + this.request.requestContext.resolvedCollectionRid = refreshedCollectionRid; this.request.getHeaders().remove(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER); } From 76ea7aa862cc02c17818af051bc2ea3477dd6510 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 8 May 2026 12:48:45 -0700 Subject: [PATCH 3/3] Also fix RenameCollectionAwareClientRetryPolicy for stale header Remove INTENDED_COLLECTION_RID_HEADER in RenameCollectionAwareClientRetryPolicy (404/1002 READ_SESSION_NOT_AVAILABLE) to prevent stale RID on retry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RenameCollectionAwareClientRetryPolicyTest.java | 5 +++++ .../RenameCollectionAwareClientRetryPolicy.java | 1 + 2 files changed, 6 insertions(+) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicyTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicyTest.java index 1f1f0a33a7dd..8e6ca063a736 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicyTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicyTest.java @@ -121,6 +121,7 @@ public void shouldRetryWithNotFoundStatusCodeAndReadSessionNotAvailableSubStatus OperationType.Create, "/dbs/db/colls/col/docs/docId", ResourceType.Document); request.requestContext = new DocumentServiceRequestContext(); request.requestContext.resolvedCollectionRid = "rid_0"; + request.getHeaders().put(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER, "rid_0"); renameCollectionAwareClientRetryPolicy.onBeforeSendRequest(request); NotFoundException notFoundException = new NotFoundException(); @@ -138,6 +139,10 @@ public void shouldRetryWithNotFoundStatusCodeAndReadSessionNotAvailableSubStatus .nullException() .shouldRetry(true) .build()); + + // Verify stale intended-collection-rid header was removed so it gets + // re-populated with the new collection rid on retry + assertThat(request.getHeaders().get(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER)).isNull(); } /** diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicy.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicy.java index ad1858673a37..d376a74fb99e 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicy.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RenameCollectionAwareClientRetryPolicy.java @@ -70,6 +70,7 @@ public Mono shouldRetry(Exception e) { request.forceNameCacheRefresh = true; request.requestContext.resolvedCollectionRid = null; + request.getHeaders().remove(HttpConstants.HttpHeaders.INTENDED_COLLECTION_RID_HEADER); Mono> collectionObs = this.collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);