From 7d3c8b55261b32462e0ec9094619526de29c1310 Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Wed, 8 Oct 2025 10:57:21 -0400 Subject: [PATCH 1/6] Add a way to skip records in Change Feed Processor. (#26) * Add a way to tune max item count for change feed requests when `StreamsConstrainedException` is hit. * Add a way to tune max item count for change feed requests when `StreamConstraintsException` is hit. * Add a way to tune page size / skip "un"-parseable document. * Add a way to tune page size / skip "un"-parseable document. * Addressing review comments. * Addressing review comments. * Addressing review comments. * Wire eTag to CosmosException. * Enhance tests. * Addressing logging. * Addressing logging. * Addressing NPEs. * Addressing continuation issues. * Fixing compilation errors. * Addressing review comments. * Addressing review comments. * Addressing review comments. --- ...PerPartitionAutomaticFailoverE2ETests.java | 2 +- .../cosmos/RetryContextOnDiagnosticTest.java | 32 +- .../cosmos/implementation/RetryUtilsTest.java | 2 +- .../RxDocumentClientImplTest.java | 7 +- .../implementation/StoreResponseBuilder.java | 6 +- .../batch/CosmosBulkItemResponseTest.java | 10 +- .../TransactionalBatchResponseTests.java | 10 +- .../ConsistencyReaderTest.java | 10 +- .../ConsistencyWriterTest.java | 6 +- .../directconnectivity/EndpointMock.java | 8 +- .../JsonNodeStorePayloadTests.java | 2 +- .../directconnectivity/QuorumReaderTest.java | 12 +- ...licatedResourceClientGoneForWriteTest.java | 2 +- ...catedResourceClientPartitionSplitTest.java | 2 +- ...ReplicatedResourceClientRetryWithTest.java | 2 +- .../StoreReaderDotNetTest.java | 18 +- .../directconnectivity/StoreReaderTest.java | 8 +- .../directconnectivity/StoreResponseTest.java | 8 +- ...StoreResultDiagnosticsSerializerTests.java | 4 +- .../IncrementalChangeFeedProcessorTest.java | 278 +++++++++------ .../IncrementalChangeFeedProcessorTest.java | 256 +++++++++++++- .../implementation/ChangeFeedQueryImpl.java | 8 + .../azure/cosmos/implementation/Configs.java | 15 + .../CosmosChangeFeedRequestOptionsImpl.java | 11 + .../DocumentServiceRequestContext.java | 12 + .../cosmos/implementation/HttpConstants.java | 2 +- .../implementation/RxGatewayStoreModel.java | 317 ++++++++---------- .../implementation/ThinClientStoreModel.java | 2 +- .../changefeed/ProcessorSettings.java | 11 + .../common/ExceptionClassifier.java | 9 + .../common/StatusCodeErrorType.java | 4 +- .../PartitionProcessorFactoryImpl.java | 3 +- .../epkversion/PartitionProcessorImpl.java | 102 +++++- .../PartitionProcessorFactoryImpl.java | 3 +- .../pkversion/PartitionProcessorImpl.java | 102 +++++- .../JsonNodeStorePayload.java | 71 ++++ .../directconnectivity/ResponseUtils.java | 30 +- .../RntbdTransportClient.java | 1 + .../directconnectivity/StoreResponse.java | 40 ++- .../rntbd/RntbdRequestManager.java | 7 +- .../rntbd/RntbdResponse.java | 9 +- .../http/HttpTransportSerializer.java | 2 +- .../query/ChangeFeedFetcher.java | 19 ++ .../cosmos/implementation/query/Fetcher.java | 33 +- ...ServerSideOnlyContinuationFetcherImpl.java | 9 + ...nlyContinuationNonDocumentFetcherImpl.java | 9 +- .../models/ChangeFeedProcessorOptions.java | 26 +- .../CosmosChangeFeedRequestOptions.java | 22 +- ...osmosChangeFeedContinuationTokenUtils.java | 14 + 49 files changed, 1200 insertions(+), 378 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java index 92d21daf488b..c3ef4307cc53 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java @@ -2270,7 +2270,7 @@ private AccountLevelLocationContext getAccountLevelLocationContext(DatabaseAccou regionMap); } - private StoreResponse constructStoreResponse(OperationType operationType, int statusCode) throws JsonProcessingException { + private StoreResponse constructStoreResponse(OperationType operationType, int statusCode) throws Exception { StoreResponseBuilder storeResponseBuilder = StoreResponseBuilder.create() .withContent(OBJECT_MAPPER.writeValueAsString(getTestPojoObject())) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java index ee59f0ac723e..cb00426d6812 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java @@ -117,6 +117,13 @@ public void backoffRetryUtilityExecuteRetry() throws Exception { .withContent(rawJson) .withStatus(200) .build())); + .thenReturn(Mono.just(new StoreResponse( + null, + 200, + new HashMap<>(), + new ByteBufInputStream(buffer, true), + buffer.readableBytes(), + null))); Mono monoResponse = BackoffRetryUtility.executeRetry(callbackMethod, retryPolicy); StoreResponse response = validateSuccess(monoResponse); @@ -148,7 +155,7 @@ public void backoffRetryUtilityExecuteRetryWithFailure() throws Exception { @Test(groups = {"unit"}, timeOut = TIMEOUT * 2) @SuppressWarnings("unchecked") - public void backoffRetryUtilityExecuteAsync() { + public void backoffRetryUtilityExecuteAsync() throws Exception { Function, Mono> inBackoffAlternateCallbackMethod = Mockito.mock(Function.class); Function, Mono> parameterizedCallbackMethod = Mockito.mock(Function.class); @@ -164,6 +171,13 @@ public void backoffRetryUtilityExecuteAsync() { .withContent(rawJson) .withStatus(200) .build())); + .thenReturn(Mono.just(new StoreResponse( + null, + 200, + new HashMap<>(), + new ByteBufInputStream(buffer, true), + buffer.readableBytes(), + null))); Mono monoResponse = BackoffRetryUtility.executeAsync( parameterizedCallbackMethod, retryPolicy, @@ -343,7 +357,7 @@ public void goneExceptionSuccessScenarioFaultInjection() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneExceptionSuccessScenarioQuery() { + public void goneExceptionSuccessScenarioQuery() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -400,7 +414,7 @@ public void goneExceptionSuccessScenarioQuery() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneExceptionSuccessScenario() throws JsonProcessingException { + public void goneExceptionSuccessScenario() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -488,7 +502,7 @@ public void goneExceptionSuccessScenario() throws JsonProcessingException { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneAndThrottlingExceptionSuccessScenario() throws JsonProcessingException { + public void goneAndThrottlingExceptionSuccessScenario() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -557,7 +571,7 @@ public void goneAndThrottlingExceptionSuccessScenario() throws JsonProcessingExc @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneAndThrottlingExceptionSuccessScenarioQuery() { + public void goneAndThrottlingExceptionSuccessScenarioQuery() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -675,7 +689,7 @@ public void goneExceptionFailureScenario() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void sessionNonAvailableExceptionScenario() throws JsonProcessingException { + public void sessionNonAvailableExceptionScenario() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -792,7 +806,7 @@ public void sessionNonAvailableExceptionFailureScenario() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void throttlingExceptionScenario() throws JsonProcessingException { + public void throttlingExceptionScenario() throws Exception { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -1008,14 +1022,14 @@ public RetryContext getRetryContext() { } } - private StoreResponse getStoreResponse(int statusCode) throws JsonProcessingException { + private StoreResponse getStoreResponse(int statusCode) throws Exception { StoreResponseBuilder storeResponseBuilder = StoreResponseBuilder.create().withContent(OBJECT_MAPPER.writeValueAsString(getTestPojoObject())) .withStatus(statusCode); return storeResponseBuilder.build(); } - private StoreResponse getQueryStoreResponse() { + private StoreResponse getQueryStoreResponse() throws Exception { String queryContent = "{\n" + " \"_rid\": \"IaBwAPRwFTg=\",\n" + " \"Documents\": [\n" + diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java index df602b2d287a..860e337e0bd1 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java @@ -147,7 +147,7 @@ public Mono answer(InvocationOnMock invocation) throws Throwable }); } - private StoreResponse getStoreResponse() { + private StoreResponse getStoreResponse() throws Exception { StoreResponseBuilder storeResponseBuilder = new StoreResponseBuilder().withContent("{\"id\":\"Test content\"}") .withStatus(200); return storeResponseBuilder.build(); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java index cbc9301142f4..bb57c57a6b25 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java @@ -113,7 +113,7 @@ public void setUp() { // todo: fix and revert enabled = false when circuit breaker is enabled @Test(groups = {"unit"}, enabled = true) - public void readMany() { + public void readMany() throws Exception { // setup static method mocks MockedStatic httpClientMock = Mockito.mockStatic(HttpClient.class); @@ -441,14 +441,15 @@ public RetryContext getRetryContext() { }; } - private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String content, Map headers) { + private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String content, Map headers) throws Exception { byte[] blob = content.getBytes(StandardCharsets.UTF_8); StoreResponse storeResponse = new StoreResponse( null, HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null); RxDocumentServiceResponse documentServiceResponse = new RxDocumentServiceResponse(new DiagnosticsClientContext() { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java index 96fb55bd0922..262d7a931414 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java @@ -92,11 +92,11 @@ public StoreResponseBuilder withContent(String content) { return this; } - public StoreResponse build() { + public StoreResponse build() throws Exception { ByteBuf buffer = getUTF8BytesOrNull(content); if (buffer == null) { - return new StoreResponse(null, status, headers, null, 0); + return new StoreResponse(null, status, headers, null, 0, null); } - return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); } } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java index 019cb9a5b213..58e8c34fc79c 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java @@ -33,7 +33,7 @@ public class CosmosBulkItemResponseTest { private static final int TIMEOUT = 40000; @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateAllSetValuesInCosmosBulkItemResponse() { + public void validateAllSetValuesInCosmosBulkItemResponse() throws Exception { List results = new ArrayList<>(); ItemBulkOperation[] arrayOperations = new ItemBulkOperation[1]; @@ -83,7 +83,8 @@ public void validateAllSetValuesInCosmosBulkItemResponse() { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -124,7 +125,7 @@ public void validateAllSetValuesInCosmosBulkItemResponse() { } @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateEmptyHeaderInCosmosBulkItemResponse() { + public void validateEmptyHeaderInCosmosBulkItemResponse() throws Exception { List results = new ArrayList<>(); ItemBulkOperation[] arrayOperations = new ItemBulkOperation[1]; @@ -166,7 +167,8 @@ public void validateEmptyHeaderInCosmosBulkItemResponse() { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java index f631d5268f59..cff576ae8403 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java @@ -32,7 +32,7 @@ public class TransactionalBatchResponseTests { private static final int TIMEOUT = 40000; @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateAllSetValuesInResponse() { + public void validateAllSetValuesInResponse() throws Exception { List results = new ArrayList<>(); ItemBatchOperation[] arrayOperations = new ItemBatchOperation[1]; @@ -79,7 +79,8 @@ public void validateAllSetValuesInResponse() { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -105,7 +106,7 @@ public void validateAllSetValuesInResponse() { } @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateEmptyHeaderInResponse() { + public void validateEmptyHeaderInResponse() throws Exception { List results = new ArrayList<>(); ItemBatchOperation[] arrayOperations = new ItemBatchOperation[1]; @@ -143,7 +144,8 @@ public void validateEmptyHeaderInResponse() { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java index 372730278a48..ff320b6e682a 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java @@ -146,7 +146,7 @@ public void replicaSizes(int systemMaxReplicaCount, } @Test(groups = "unit") - public void readAny() { + public void readAny() throws Exception { List secondaries = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); Uri primaryAddress = Uri.create("primary"); AddressSelectorWrapper addressSelectorWrapper = AddressSelectorWrapper.Builder.Simple.create() @@ -231,7 +231,7 @@ public void readAny() { } @Test(groups = "unit") - public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWithLowerLSN_FindAnotherReplica() { + public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWithLowerLSN_FindAnotherReplica() throws Exception { long slowReplicaLSN = 651176; String partitionKeyRangeId = "1"; long fasterReplicaLSN = 651177; @@ -355,7 +355,7 @@ public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWith * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSession() { + public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSession() throws Exception { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; @@ -443,7 +443,7 @@ public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatis * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromAllReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSessionOnRetry() { + public void sessionNotAvailableFromAllReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSessionOnRetry() throws Exception { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; @@ -776,7 +776,7 @@ public Object[][] simpleReadStrongArgProvider() { } @Test(groups = "unit", dataProvider = "simpleReadStrongArgProvider") - public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode) { + public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode) throws Exception { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); Uri primaryReplicaURI = Uri.create("primary"); ImmutableList secondaryReplicaURIs = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java index 9ede1c281219..da6e0f791e4c 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java @@ -208,12 +208,12 @@ public void run() { } @Test(groups = "unit") - public void getLsnAndGlobalCommittedLsn() { + public void getLsnAndGlobalCommittedLsn() throws Exception { Map headers = new HashMap<>(); headers.put(WFConstants.BackendHeaders.LSN, "3"); headers.put(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, "2"); - StoreResponse sr = new StoreResponse(null, 0, headers, null, 0); + StoreResponse sr = new StoreResponse(null, 0, headers, null, 0, null); Utils.ValueHolder lsn = Utils.ValueHolder.initialize(-2l); Utils.ValueHolder globalCommittedLsn = Utils.ValueHolder.initialize(-2l); ConsistencyWriter.getLsnAndGlobalCommittedLsn(sr, lsn, globalCommittedLsn); @@ -314,7 +314,7 @@ public void storeResponseRecordedOnException(Exception ex, StoreResponse storeRe } @DataProvider(name = "globalStrongArgProvider") - public Object[][] globalStrongArgProvider() { + public Object[][] globalStrongArgProvider() throws Exception { return new Object[][]{ { ConsistencyLevel.SESSION, diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java index 8113875998d2..2be5727599ef 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java @@ -56,7 +56,7 @@ public void validate(EndpointMockVerificationBuilder verificationBuilder) { } } - public static Builder.NoSecondaryReplica noSecondaryReplicaBuilder() { + public static Builder.NoSecondaryReplica noSecondaryReplicaBuilder() throws Exception { return new Builder.NoSecondaryReplica(); } @@ -149,6 +149,9 @@ static public class NoSecondaryReplica extends Builder { private StoreResponse readStoreResponse = defaultResponse; private Function1WithCheckedException storeResponseFunc; + public NoSecondaryReplica() throws Exception { + } + public NoSecondaryReplica primaryReplica(Uri primaryReplica) { this.primary = primaryReplica; return this; @@ -217,6 +220,9 @@ static public class NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOn Map> secondaryResponseFunc = new HashMap<>(); + public NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOnPrimary() throws Exception { + } + public NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOnPrimary primaryReplica(Uri primaryReplica) { this.primary = primaryReplica; diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java index 16a15157118d..5ae4d875d640 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java @@ -28,7 +28,7 @@ public void parsingBytesWithInvalidUT8Bytes() { try { byte[] bytes = hexStringToByteArray(invalidHexString); ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); - JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>()); + JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>(), null); jsonNodeStorePayload.getPayload().toString(); } finally { System.clearProperty("COSMOS.CHARSET_DECODER_ERROR_ACTION_ON_MALFORMED_INPUT"); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java index 8a22146d0bf1..1e2046da67c3 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java @@ -56,7 +56,7 @@ public Object[][] simpleReadStrongArgProvider() { }; } - private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) { + private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) throws Exception { StoreResponseBuilder srb = StoreResponseBuilder.create(); if (rc != null) { srb.withRequestCharge(rc); @@ -74,7 +74,7 @@ private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) { } @Test(groups = "unit", dataProvider = "simpleReadStrongArgProvider") - public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode, Long lsn, Long localLSN) { + public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode, Long lsn, Long localLSN) throws Exception { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); Uri primaryReplicaURI = Uri.create("primary"); ImmutableList secondaryReplicaURIs = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); @@ -143,7 +143,7 @@ public Object[][] readStrong_RequestBarrierArgProvider() { @Test(groups = "unit", dataProvider = "readStrong_RequestBarrierArgProvider") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_OnlySecondary_RequestBarrier_Success(int numberOfBarrierRequestTillCatchUp) { + public void readStrong_OnlySecondary_RequestBarrier_Success(int numberOfBarrierRequestTillCatchUp) throws Exception { // scenario: we get lsn l1, l2 where l1 > l2 // we do barrier request and send it to all replicas till we have two replicas with at least l1 lsn @@ -288,7 +288,7 @@ public Object[][] readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_ @Test(groups = "unit", dataProvider = "readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_SuccessArgProvider") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Success(int numberOfHeadBarriersWithPrimaryIncludedTillQuorumMet) { + public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Success(int numberOfHeadBarriersWithPrimaryIncludedTillQuorumMet) throws Exception { // scenario: we exhaust all barrier request retries on secondaries // after that we start barrier requests including the primary @@ -449,7 +449,7 @@ public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Succes @Test(groups = "unit") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_QuorumNotSelected_ReadPrimary() { + public void readStrong_QuorumNotSelected_ReadPrimary() throws Exception { // scenario: attempts to read from secondaries, // only one secondary is available so ends in QuorumNotSelected State // reads from Primary and succeeds @@ -554,7 +554,7 @@ public void readStrong_QuorumNotSelected_ReadPrimary() { } @DataProvider(name = "readPrimaryArgProvider") - public Object[][] readPrimaryArgProvider() { + public Object[][] readPrimaryArgProvider() throws Exception { return new Object[][]{ // endpoint, verifier for endpoint expected result, verifying the StoreResponse returned { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java index dd42eb6eef44..6697a674d758 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java @@ -41,7 +41,7 @@ public Object[][] goneOnWriteRefreshesAddressesArgProvider() { groups = { "unit" }, dataProvider = "goneOnWriteRefreshesAddressesArgProvider", timeOut = ReplicatedResourceClientPartitionSplitTest.TIMEOUT) - public void gone_RefreshCache_Write(ConsistencyLevel consistencyLevel) { + public void gone_RefreshCache_Write(ConsistencyLevel consistencyLevel) throws Exception { Uri primaryAddress = Uri.create("http://primary/"); List secondaryAddresses = new ArrayList<>(); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java index 3d3a49410362..b2d638267398 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java @@ -46,7 +46,7 @@ public Object[][] partitionIsSplittingArgProvider() { } @Test(groups = { "unit" }, dataProvider = "partitionIsSplittingArgProvider", timeOut = TIMEOUT) - public void partitionSplit_RefreshCache_Read(ConsistencyLevel consistencyLevel, int partitionIsSplitting) { + public void partitionSplit_RefreshCache_Read(ConsistencyLevel consistencyLevel, int partitionIsSplitting) throws Exception { Uri secondary1AddressBeforeMove = Uri.create("secondary"); Uri secondary1AddressAfterMove = Uri.create("secondaryNew"); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java index 9fce6118c374..3a075b9d6479 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java @@ -37,7 +37,7 @@ public class ReplicatedResourceClientRetryWithTest { protected static final int TIMEOUT = 120000; @Test(groups = { "unit" }, timeOut = TIMEOUT) - public void retryWith_RetrySucceeds() throws URISyntaxException { + public void retryWith_RetrySucceeds() throws Exception { Uri primaryAddress = Uri.create("http://primary/"); List secondaryAddresses = new ArrayList<>(); secondaryAddresses.add(Uri.create("http://secondary-1/")); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java index c7b33aff323c..2ef731184b83 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java @@ -36,7 +36,6 @@ import org.testng.annotations.Test; import reactor.core.publisher.Mono; -import java.net.URISyntaxException; import java.time.Duration; import java.util.ArrayDeque; import java.util.List; @@ -75,7 +74,7 @@ public void addressCache() { * Tests for TransportClient */ @Test(groups = "unit") - public void transportClient() { + public void transportClient() throws Exception { // create a real document service request RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); @@ -122,7 +121,7 @@ public void transportClient() { validator.validate(response); } - private TransportClient getMockTransportClientDuringUpgrade(AddressInformation[] addressInformation) { + private TransportClient getMockTransportClientDuringUpgrade(AddressInformation[] addressInformation) throws Exception { // create objects for all the dependencies of the StoreReader TransportClient mockTransportClient = Mockito.mock(TransportClient.class); @@ -186,7 +185,7 @@ private enum ReadQuorumResultKind { QuorumNotSelected } - private TransportClient getMockTransportClientForGlobalStrongReads(AddressInformation[] addressInformation, ReadQuorumResultKind result) { + private TransportClient getMockTransportClientForGlobalStrongReads(AddressInformation[] addressInformation, ReadQuorumResultKind result) throws Exception { // create objects for all the dependencies of the StoreReader TransportClient mockTransportClient = Mockito.mock(TransportClient.class); @@ -303,8 +302,7 @@ private TransportClient getMockTransportClientForGlobalStrongWrites( int indexOfCaughtUpReplica, boolean undershootGlobalCommittedLsnDuringBarrier, boolean overshootLsnDuringBarrier, - boolean overshootGlobalCommittedLsnDuringBarrier) - { + boolean overshootGlobalCommittedLsnDuringBarrier) throws Exception { TransportClient mockTransportClient = Mockito.mock(TransportClient.class); // create mock store response object @@ -435,7 +433,7 @@ private IAddressResolver getMockAddressCache(AddressInformation[] addressInforma * Tests for {@link StoreReader} */ @Test(groups = "unit") - public void storeReaderBarrier() { + public void storeReaderBarrier() throws Exception { // create a real document service request RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); @@ -562,7 +560,7 @@ public static void validateException(Mono single, */ @Test(groups = "unit", enabled = false) @SuppressWarnings("unchecked") - public void storeClient() throws URISyntaxException { + public void storeClient() throws Exception { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; @@ -650,7 +648,7 @@ public void storeClient() throws URISyntaxException { */ @Test(groups = "unit") @SuppressWarnings("unchecked") - public void globalStrongConsistentWrite() { + public void globalStrongConsistentWrite() throws Exception { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Create, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; @@ -737,7 +735,7 @@ public void globalStrongConsistentWrite() { */ @Test(groups = "unit", priority = 1) @SuppressWarnings({"unchecked", "rawtypes"}) - public void globalStrongConsistency() { + public void globalStrongConsistency() throws Exception { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java index 27052ab0d7a5..f0f4441a7134 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java @@ -206,7 +206,7 @@ public void exception(Exception ex, Class klass, int expectedStatusCo * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromSomeReplicas_FindReplicaSatisfyingRequestedSession() { + public void sessionNotAvailableFromSomeReplicas_FindReplicaSatisfyingRequestedSession() throws Exception { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; String partitionKeyRangeId = "73"; @@ -614,7 +614,7 @@ public void readPrimaryAsync_Error() { } @Test(groups = "unit") - public void canParseLongLsn() { + public void canParseLongLsn() throws Exception { TransportClient transportClient = Mockito.mock(TransportClient.class); AddressSelector addressSelector = Mockito.mock(AddressSelector.class); ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); @@ -704,7 +704,7 @@ public void readPrimaryAsync_RetryOnPrimaryReplicaMove(Exception firstExceptionF boolean performLocalRefreshOnGoneException, boolean retryWithForceRefreshExpected, FailureValidator failureFromSingle, - boolean expectedStoreResponseInStoredReadResult) { + boolean expectedStoreResponseInStoredReadResult) throws Exception { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); StoreResponse response = StoreResponseBuilder.create().build(); @@ -771,7 +771,7 @@ public Object[][] readMultipleReplicasAsyncArgProvider() { } @Test(groups = "unit", dataProvider = "readMultipleReplicasAsyncArgProvider") - public void readMultipleReplicasAsync(boolean includePrimary, int replicaCountToRead, ReadMode readMode) { + public void readMultipleReplicasAsync(boolean includePrimary, int replicaCountToRead, ReadMode readMode) throws Exception { // This adds basic tests for StoreReader.readMultipleReplicasAsync(.) without failure // TODO: add some tests for readMultipleReplicasAsync which mock behaviour of failure of reading from a replica ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java index 1e6c6cc147f8..c33852b1d48a 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java @@ -14,7 +14,7 @@ public class StoreResponseTest { @Test(groups = { "unit" }) - public void stringContent() { + public void stringContent() throws Exception { String content = "I am body"; String jsonContent = "{\"id\":\"" + content + "\"}"; HashMap headerMap = new HashMap<>(); @@ -22,7 +22,7 @@ public void stringContent() { headerMap.put("key2", "value2"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); @@ -30,7 +30,7 @@ public void stringContent() { } @Test(groups = { "unit" }) - public void headerNamesAreCaseInsensitive() { + public void headerNamesAreCaseInsensitive() throws Exception { String content = "I am body"; String jsonContent = "{\"id\":\"" + content + "\"}"; HashMap headerMap = new HashMap<>(); @@ -39,7 +39,7 @@ public void headerNamesAreCaseInsensitive() { headerMap.put("KEY3", "value3"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java index 18eea0e3cc5e..8e3092ccd976 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java @@ -36,8 +36,8 @@ public StoreResultDiagnosticsSerializerTests() throws IOException { //TODO: add more test cases @Test(groups = "unit") - public void storeResultDiagnosticsSerializerTests() { - StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0); + public void storeResultDiagnosticsSerializerTests() throws Exception { + StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0, null); StoreResult storeResult = new StoreResult( storeResponse, null, diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java index a543309ef37a..63c553db38c7 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java @@ -53,7 +53,10 @@ import com.azure.cosmos.test.faultinjection.FaultInjectionRuleBuilder; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorResult; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorType; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.NullNode; @@ -89,6 +92,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -132,6 +136,58 @@ public Object[] getCurrentStateTestConfigs() { }; } + @DataProvider(name = "parsingErrorArgProvider") + public static Object[][] parsingErrorArgProvider() { + return new Object[][]{ + { + new StreamConstraintsException("A StreamConstraintsException has been hit!"), + -1 + }, + { + new JacksonException("A JacksonException has been hit!") { + @Override + public JsonLocation getLocation() { + return null; + } + + @Override + public String getOriginalMessage() { + return ""; + } + + @Override + public Object getProcessor() { + return null; + } + }, + -1 + }, + { + new StreamConstraintsException("A StreamConstraintsException has been hit!"), + 10 + }, + { + new JacksonException("A JacksonException has been hit!") { + @Override + public JsonLocation getLocation() { + return null; + } + + @Override + public String getOriginalMessage() { + return ""; + } + + @Override + public Object getProcessor() { + return null; + } + }, + 10 + } + }; + } + @DataProvider public Object[] incrementalChangeFeedModeStartFromSetting() { return new Object[] { true, false }; @@ -256,107 +312,6 @@ public void readFeedDocumentsStartFromCustomDate() throws InterruptedException { } } - @Test(groups = { "query" }, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) - public void verifyConsistentTimestamps() throws InterruptedException { - CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); - CosmosAsyncContainer createdLeaseCollection = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); - - try { - List createdDocuments = new ArrayList<>(); - Map receivedDocuments = new ConcurrentHashMap<>(); - ChangeFeedProcessor changeFeedProcessor = new ChangeFeedProcessorBuilder() - .hostName(hostName) - .handleLatestVersionChanges((List docs) -> { - logger.info("START processing from thread {}", Thread.currentThread().getId()); - for (ChangeFeedProcessorItem item : docs) { - processItem(item, receivedDocuments); - } - logger.info("END processing from thread {}", Thread.currentThread().getId()); - }) - .feedContainer(createdFeedCollection) - .leaseContainer(createdLeaseCollection) - .options(new ChangeFeedProcessorOptions() - .setLeaseRenewInterval(Duration.ofSeconds(20)) - .setLeaseAcquireInterval(Duration.ofSeconds(10)) - .setLeaseExpirationInterval(Duration.ofSeconds(30)) - .setFeedPollDelay(Duration.ofSeconds(1)) - .setLeasePrefix("TEST") - .setMaxItemCount(10) - .setMinScaleCount(1) - .setMaxScaleCount(3) - ) - .buildChangeFeedProcessor(); - AtomicReference initialTimestamp = new AtomicReference<>(); - - startChangeFeedProcessor(changeFeedProcessor); - - assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); - - safeStopChangeFeedProcessor(changeFeedProcessor); - - createdLeaseCollection.queryItems("SELECT * FROM c", new CosmosQueryRequestOptions(), JsonNode.class) - .byPage() - .flatMap(feedResponse -> { - for (JsonNode item : feedResponse.getResults()) { - if (item.get("timestamp") != null) { - initialTimestamp.set(item.get("timestamp").asText()); - logger.info("Found timestamp: %s", initialTimestamp); - } - } - return Mono.empty(); - }).blockLast(); - - - - startChangeFeedProcessor(changeFeedProcessor); - - // create a gap between previously written documents - Thread.sleep(3000); - - // process some documents after the CFP is started and stopped - setupReadFeedDocuments(createdDocuments, createdFeedCollection, FEED_COUNT); - - // Wait for the feed processor to receive and process the documents. - waitToReceiveDocuments(receivedDocuments, 40 * CHANGE_FEED_PROCESSOR_TIMEOUT, FEED_COUNT); - safeStopChangeFeedProcessor(changeFeedProcessor); - - logger.info("After processing documents"); - - AtomicReference newTimestamp = new AtomicReference<>(); - - - createdLeaseCollection.queryItems("SELECT * FROM c", new CosmosQueryRequestOptions(), JsonNode.class) - .byPage() - .flatMap(feedResponse -> { - for (JsonNode item : feedResponse.getResults()) { - if (item.get("timestamp") != null) { - newTimestamp.set(item.get("timestamp").asText()); - logger.info("Found timestamp: %s", newTimestamp); - } - } - return Mono.empty(); - }).blockLast(); - - - assertThat(newTimestamp.get()).doesNotContain("[UTC]"); - assertThat(initialTimestamp.get()).doesNotContain("[UTC]"); - - for (InternalObjectNode item : createdDocuments) { - assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); - } - - // Wait for the feed processor to shutdown. - Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); - - } finally { - safeDeleteCollection(createdFeedCollection); - safeDeleteCollection(createdLeaseCollection); - - // Allow some time for the collections to be deleted before exiting. - Thread.sleep(500); - } - } - @Test(groups = {"multi-master"}, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) public void readFeedDocumentsStartFromCustomDateForMultiWrite_test() throws InterruptedException { CosmosClientBuilder clientBuilder = getClientBuilder(); @@ -2029,6 +1984,127 @@ public void incrementalChangeFeedModeToFullFidelityChangeFeedMode(boolean isStar } } + @Test(groups = { "long-emulator" }, dataProvider = "parsingErrorArgProvider", timeOut = 12 * TIMEOUT) + public void readFeedDocumentsStartFromBeginningWithJsonProcessingErrors(Exception exceptionType, int maxItemCount) throws InterruptedException { + + if (BridgeInternal.getContextClient(this.client).getConnectionPolicy().getConnectionMode() + == ConnectionMode.DIRECT) { + throw new SkipException("The fix is only for Gateway mode. Direct mode fix will be followed up on."); + } + + CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); + CosmosAsyncContainer createdLeaseCollection = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); + Callable responseInterceptor = null; + + // Response Interceptor Properties + AtomicInteger pageCounter = new AtomicInteger(0); + AtomicInteger exceptionCounter = new AtomicInteger(0); + AtomicInteger totalExceptionHits = new AtomicInteger(0); + + if (exceptionType instanceof StreamConstraintsException) { + responseInterceptor = () -> { + // inject when certain no. of pages have been processed + if (pageCounter.get() > 1 && pageCounter.get() % 2 == 0) { + if (exceptionCounter.get() < 3) { + exceptionCounter.incrementAndGet(); + totalExceptionHits.incrementAndGet(); + throw exceptionType; + } else { + exceptionCounter.set(0); + } + } + + return null; + }; + } else { + responseInterceptor = () -> { + // inject when certain no. of pages have been processed + if (pageCounter.get() > 1 && pageCounter.get() % 2 == 0) { + if (exceptionCounter.get() < 2) { + exceptionCounter.incrementAndGet(); + totalExceptionHits.incrementAndGet(); + throw exceptionType; + } else { + exceptionCounter.set(0); + } + } + + return null; + }; + } + + try { + List createdDocuments = new ArrayList<>(); + Map receivedDocuments = new ConcurrentHashMap<>(); + setupReadFeedDocuments(createdDocuments, createdFeedCollection, 100); + + changeFeedProcessor = new ChangeFeedProcessorBuilder() + .hostName(hostName) + .handleLatestVersionChanges((docs) -> { + logger.info("START processing from thread {}", Thread.currentThread().getId()); + for (ChangeFeedProcessorItem item : docs) { + processItem(item, receivedDocuments); + } + + pageCounter.incrementAndGet(); + logger.info("END processing from thread {}", Thread.currentThread().getId()); + }) + .feedContainer(createdFeedCollection) + .leaseContainer(createdLeaseCollection) + .options(new ChangeFeedProcessorOptions() + .setLeaseRenewInterval(Duration.ofSeconds(20)) + .setLeaseAcquireInterval(Duration.ofSeconds(10)) + .setLeaseExpirationInterval(Duration.ofSeconds(30)) + .setFeedPollDelay(Duration.ofSeconds(2)) + .setLeasePrefix("TEST") + .setMaxItemCount(maxItemCount) + .setStartFromBeginning(true) + .setMaxScaleCount(0) // unlimited + .setResponseInterceptor(responseInterceptor) + ) + .buildChangeFeedProcessor(); + + startChangeFeedProcessor(changeFeedProcessor); + + for (int i = 0; i < 5; i++) { + setupReadFeedDocuments(createdDocuments, createdFeedCollection, 100); + Thread.sleep(10_000); + } + + // Wait for the feed processor to receive and process the documents. + Thread.sleep(20 * CHANGE_FEED_PROCESSOR_TIMEOUT); + + assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); + + safeStopChangeFeedProcessor(changeFeedProcessor); + + // Wait for the feed processor to shutdown. + Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); + + logger.warn("Total documents received: {}", receivedDocuments.size()); + logger.warn("Total created documents : {}", createdDocuments.size()); + logger.warn("Total exception hits : {}", totalExceptionHits.get()); + + assertThat(totalExceptionHits.get()).isGreaterThan(0); + + if (exceptionType instanceof StreamConstraintsException) { + assertThat(receivedDocuments.size()).isEqualTo(createdDocuments.size()); + + for (InternalObjectNode item : createdDocuments) { + assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); + } + } else { + assertThat(receivedDocuments.size()).isEqualTo(createdDocuments.size() - totalExceptionHits.get() / 2); + } + } finally { + safeDeleteCollection(createdFeedCollection); + safeDeleteCollection(createdLeaseCollection); + + // Allow some time for the collections to be deleted before exiting. + Thread.sleep(500); + } + } + @Test(groups = { "cfp-split" }, timeOut = 160 * CHANGE_FEED_PROCESSOR_TIMEOUT, retryAnalyzer = SplitTestsRetryAnalyzer.class) public void verifyLeasesOnRestart_AfterSplit() throws InterruptedException { CosmosAsyncContainer createdFeedCollectionForSplit = createFeedCollection(FEED_COLLECTION_THROUGHPUT); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java index ad70d2997423..97223ede3db7 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.cosmos.rx.changefeed.pkversion; +import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.ChangeFeedProcessor; import com.azure.cosmos.ChangeFeedProcessorBuilder; import com.azure.cosmos.ConsistencyLevel; @@ -10,6 +11,7 @@ import com.azure.cosmos.CosmosAsyncDatabase; import com.azure.cosmos.CosmosClientBuilder; import com.azure.cosmos.CosmosEndToEndOperationLatencyPolicyConfigBuilder; +import com.azure.cosmos.FlakyTestRetryAnalyzer; import com.azure.cosmos.CosmosNettyLeakDetectorFactory; import com.azure.cosmos.SplitTestsRetryAnalyzer; import com.azure.cosmos.SplitTimeoutException; @@ -39,7 +41,19 @@ import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.models.ThroughputResponse; import com.azure.cosmos.rx.TestSuiteBase; +import com.azure.cosmos.test.faultinjection.CosmosFaultInjectionHelper; +import com.azure.cosmos.test.faultinjection.FaultInjectionCondition; +import com.azure.cosmos.test.faultinjection.FaultInjectionConditionBuilder; +import com.azure.cosmos.test.faultinjection.FaultInjectionOperationType; +import com.azure.cosmos.test.faultinjection.FaultInjectionResultBuilders; +import com.azure.cosmos.test.faultinjection.FaultInjectionRule; +import com.azure.cosmos.test.faultinjection.FaultInjectionRuleBuilder; +import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorResult; +import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorType; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.RandomStringUtils; @@ -72,6 +86,7 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -112,6 +127,58 @@ public static Object[][] throughputControlConfigArgProvider() { }; } + @DataProvider(name = "parsingErrorArgProvider") + public static Object[][] parsingErrorArgProvider() { + return new Object[][]{ + { + new StreamConstraintsException("A StreamConstraintsException has been hit!"), + -1 + }, + { + new JacksonException("A JacksonException has been hit!") { + @Override + public JsonLocation getLocation() { + return null; + } + + @Override + public String getOriginalMessage() { + return ""; + } + + @Override + public Object getProcessor() { + return null; + } + }, + -1 + }, + { + new StreamConstraintsException("A StreamConstraintsException has been hit!"), + 10 + }, + { + new JacksonException("A JacksonException has been hit!") { + @Override + public JsonLocation getLocation() { + return null; + } + + @Override + public String getOriginalMessage() { + return ""; + } + + @Override + public Object getProcessor() { + return null; + } + }, + 10 + } + }; + } + @DataProvider public Object[] incrementalChangeFeedModeStartFromSetting() { return new Object[] { true, false }; @@ -184,6 +251,89 @@ public void readFeedDocumentsStartFromBeginning() throws InterruptedException { } } + @Test(groups = { "long-emulator" }, enabled = false, timeOut = 12 * TIMEOUT) + public void readFeedDocumentsStartFromBeginningWithPkRangeThrottles() throws InterruptedException { + CosmosAsyncContainer createdFeedCollection + = client.getDatabase("TestDb").getContainer("TestFeedContainer"); + CosmosAsyncContainer createdLeaseCollection + = client.getDatabase("TestDb").getContainer("TestLeaseContainer"); + + try { + List createdDocuments = new ArrayList<>(); + Map receivedDocuments = new ConcurrentHashMap<>(); + + changeFeedProcessor = new ChangeFeedProcessorBuilder() + .hostName(hostName) + .handleChanges(changeFeedProcessorHandler(receivedDocuments)) + .feedContainer(createdFeedCollection) + .leaseContainer(createdLeaseCollection) + .options(new ChangeFeedProcessorOptions() + .setLeaseRenewInterval(Duration.ofSeconds(20)) + .setLeaseAcquireInterval(Duration.ofSeconds(10)) + .setLeaseExpirationInterval(Duration.ofSeconds(30)) + .setFeedPollDelay(Duration.ofSeconds(2)) + .setLeasePrefix("TEST") + .setMaxItemCount(10) + .setStartFromBeginning(true) + .setMaxScaleCount(0) // unlimited + ) + .buildChangeFeedProcessor(); + + FeedRange fullRange = FeedRange.forFullRange(); + + FaultInjectionServerErrorResult pkRangeThrottledError = FaultInjectionResultBuilders + .getResultBuilder(FaultInjectionServerErrorType.TOO_MANY_REQUEST) + .suppressServiceRequests(false) + .build(); + + FaultInjectionCondition condition = new FaultInjectionConditionBuilder() + .operationType(FaultInjectionOperationType.METADATA_REQUEST_PARTITION_KEY_RANGES) + .build(); + + String pkRangeThrottledId = String.format("pkrange-throttled-error-%s", UUID.randomUUID()); + + FaultInjectionRuleBuilder ruleBuilder = new FaultInjectionRuleBuilder(pkRangeThrottledId) + .condition(condition) + .result(pkRangeThrottledError); + + FaultInjectionRule pkRangeThrottledFIErrorRule = ruleBuilder.build(); + + CosmosFaultInjectionHelper.configureFaultInjectionRules(createdFeedCollection, Arrays.asList(pkRangeThrottledFIErrorRule)).block(); + + startChangeFeedProcessor(changeFeedProcessor); + + // Wait for the feed processor to receive and process the documents. + Thread.sleep(20000 * CHANGE_FEED_PROCESSOR_TIMEOUT); + + assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); + + safeStopChangeFeedProcessor(changeFeedProcessor); + for (InternalObjectNode item : createdDocuments) { + assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); + } + + // Wait for the feed processor to shutdown. + Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); + + // restart the change feed processor and verify it can start successfully + startChangeFeedProcessor(changeFeedProcessor); + + // Wait for the feed processor to start + Thread.sleep(2 * CHANGE_FEED_PROCESSOR_TIMEOUT); + + assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); + + safeStopChangeFeedProcessor(changeFeedProcessor); + // Wait for the feed processor to shutdown. + Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); + } finally { +// safeDeleteCollection(createdFeedCollection); +// safeDeleteCollection(createdLeaseCollection); + + // Allow some time for the collections to be deleted before exiting. Thread.sleep(500); + } + } + @Test(groups = { "long-emulator" }, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) public void readFeedDocumentsStartFromCustomDate() throws InterruptedException { CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); @@ -1381,6 +1531,61 @@ public void readFeedDocumentsAfterSplit(boolean throughputControlConfigEnabled) } } + @Test(groups = { "query" }, timeOut = 20 * TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class) + public void readPartitionKeyRangesWithSuppressedPageSize() { + + AsyncDocumentClient contextClient = BridgeInternal.getContextClient(this.client); + CosmosAsyncContainer asyncContainer = getSharedMultiPartitionCosmosContainer(this.client); + String containerLink = BridgeInternal.getLink(asyncContainer); + + try { + System.setProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE", "1"); + contextClient + .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) + .doOnNext(feedResponse -> { + logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); + assertThat(feedResponse.getResults().size() <= 1).isTrue(); + }) + .blockLast(); + + } catch (RuntimeException e) { + fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); + } finally { + System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); + } + + try { + System.setProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE", "-1"); + contextClient + .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) + .doOnNext(feedResponse -> { + logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); + assertThat(feedResponse.getResults().size() > 1).isTrue(); + }) + .blockLast(); + + } catch (RuntimeException e) { + fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); + } finally { + System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); + } + + try { + contextClient + .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) + .doOnNext(feedResponse -> { + logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); + assertThat(feedResponse.getResults().size() > 1).isTrue(); + }) + .blockLast(); + + } catch (RuntimeException e) { + fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); + } finally { + System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); + } + } + @Test(groups = { "cfp-split" }, timeOut = 160 * CHANGE_FEED_PROCESSOR_TIMEOUT, retryAnalyzer = SplitTestsRetryAnalyzer.class) public void readFeedDocumentsAfterSplit_maxScaleCount() throws InterruptedException { CosmosAsyncContainer createdFeedCollectionForSplit = createFeedCollection(FEED_COLLECTION_THROUGHPUT); @@ -2208,24 +2413,53 @@ private Consumer> leasesChangeFeedProcessorHandler(LeaseStateMoni log.info("LEASES processing from thread {}", Thread.currentThread().getId()); }; } - @BeforeMethod(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) - public void beforeMethod() throws Exception { - // add a cool off time - CosmosNettyLeakDetectorFactory.resetIdentifiedLeaks(); - } - @AfterMethod(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) - public void afterMethod() throws Exception { - logger.info("captureNettyLeaks: {}", captureNettyLeaks()); - } + @BeforeMethod(groups = { "long-emulator", "cfp-split" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) + public void beforeMethod() { + } - @BeforeClass(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) + @BeforeClass(groups = { "long-emulator", "cfp-split" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) public void before_ChangeFeedProcessorTest() { client = getClientBuilder().buildAsyncClient(); createdDatabase = getSharedCosmosDatabase(client); + + // Following is code that when enabled locally it allows for a predicted database/collection name that can be + // checked in the Azure Portal +// try { +// client.getDatabase(databaseId).read() +// .map(cosmosDatabaseResponse -> cosmosDatabaseResponse.getDatabase()) +// .flatMap(database -> database.delete()) +// .onErrorResume(throwable -> { +// if (throwable instanceof com.azure.cosmos.CosmosClientException) { +// com.azure.cosmos.CosmosClientException clientException = (com.azure.cosmos.CosmosClientException) throwable; +// if (clientException.getStatusCode() == 404) { +// return Mono.empty(); +// } +// } +// return Mono.error(throwable); +// }).block(); +// Thread.sleep(500); +// } catch (Exception e){ +// log.warn("Database delete", e); +// } +// createdDatabase = createDatabase(client, databaseId); + } + + @AfterMethod(groups = { "long-emulator", "cfp-split" }, timeOut = 3 * SHUTDOWN_TIMEOUT, alwaysRun = true) + public void afterMethod() { } - @AfterClass(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) + + @AfterClass(groups = { "long-emulator", "cfp-split" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) public void afterClass() { +// try { +// client.readAllDatabases() +// .flatMap(cosmosDatabaseProperties -> { +// CosmosAsyncDatabase cosmosDatabase = client.getDatabase(cosmosDatabaseProperties.getId()); +// return cosmosDatabase.delete(); +// }).blockLast(); +// Thread.sleep(500); +// } catch (Exception e){ } + safeClose(client); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java index dc2adf1dce7b..7b0d938f3014 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java @@ -192,6 +192,14 @@ private RxDocumentServiceRequest createDocumentServiceRequest() { } private Mono> executeRequestAsync(RxDocumentServiceRequest request) { + + // TODO: Only for testing purposes. Remove post testing. + if (this.options.getResponseInterceptor() != null) { + if (request.requestContext != null) { + request.requestContext.setResponseInterceptor(this.options.getResponseInterceptor()); + } + } + if (this.operationContextAndListener == null) { return handlePerPartitionFailoverPrerequisites(request) .flatMap(client::readFeed) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java index b656b84e66db..72b875324809 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java @@ -250,6 +250,11 @@ public class Configs { public static final String CHARSET_DECODER_ERROR_ACTION_ON_UNMAPPED_CHARACTER = "COSMOS.CHARSET_DECODER_ERROR_ACTION_ON_UNMAPPED_CHARACTER"; public static final String DEFAULT_CHARSET_DECODER_ERROR_ACTION_ON_UNMAPPED_CHARACTER = StringUtils.EMPTY; + // Config to enable recovery for CFP on malformed response + public static final String CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED = "COSMOS.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED"; + public static final String CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED_VARIABLE = "COSMOS_CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED"; + public static final boolean DEFAULT_CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED = true; + // Metrics // Samples: // System.setProperty( @@ -1229,6 +1234,16 @@ public static int getWarnLevelLoggingThresholdForPpaf() { return Integer.parseInt(warnLevelLoggingThresholdForPpaf); } + public static boolean isChangeFeedProcessorMalformedResponseRecoveryEnabled() { + String isMalformedResponseRecoveryEnabled = System.getProperty( + CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED, + firstNonNull( + emptyToNull(System.getenv().get(CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED_VARIABLE)), + String.valueOf(DEFAULT_CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED))); + + return Boolean.parseBoolean(isMalformedResponseRecoveryEnabled); + } + public static String getAzureMonitorConnectionString() { return System.getProperty( APPLICATIONINSIGHTS_CONNECTION_STRING, diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java index 2ef17d397b5f..4375ebadd3d0 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; @@ -53,6 +54,7 @@ public final class CosmosChangeFeedRequestOptionsImpl implements OverridableRequ private boolean completeAfterAllCurrentChangesRetrieved; private Long endLSN; private ReadConsistencyStrategy readConsistencyStrategy; + private Callable responseInterceptor; public CosmosChangeFeedRequestOptionsImpl(CosmosChangeFeedRequestOptionsImpl toBeCloned) { if (toBeCloned.continuationState != null) { @@ -80,6 +82,7 @@ public CosmosChangeFeedRequestOptionsImpl(CosmosChangeFeedRequestOptionsImpl toB this.keywordIdentifiers = toBeCloned.keywordIdentifiers; this.completeAfterAllCurrentChangesRetrieved = toBeCloned.completeAfterAllCurrentChangesRetrieved; this.endLSN = toBeCloned.endLSN; + this.responseInterceptor = toBeCloned.responseInterceptor; } public CosmosChangeFeedRequestOptionsImpl( @@ -245,6 +248,14 @@ public void setRequestContinuation(String etag) { this.feedRangeInternal); } + public Callable getResponseInterceptor() { + return this.responseInterceptor; + } + + public void setResponseInterceptor(Callable responseInterceptor) { + this.responseInterceptor = responseInterceptor; + } + @Beta(value = Beta.SinceVersion.V4_12_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) @Deprecated //since = "V4_37_0", forRemoval = true diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java index 18da5250c458..da6ee93d3687 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -78,6 +79,7 @@ public class DocumentServiceRequestContext implements Cloneable { private volatile PerPartitionCircuitBreakerInfoHolder perPartitionCircuitBreakerInfoHolder; private volatile PerPartitionFailoverInfoHolder perPartitionFailoverInfoHolder; + private volatile Callable responseInterceptor; public DocumentServiceRequestContext() {} @@ -168,6 +170,8 @@ public DocumentServiceRequestContext clone() { context.endToEndOperationLatencyPolicyConfig = this.endToEndOperationLatencyPolicyConfig; context.unavailableRegionsForPartition = this.unavailableRegionsForPartition; context.crossRegionAvailabilityContextForRequest = this.crossRegionAvailabilityContextForRequest; + context.responseInterceptor = this.responseInterceptor; + return context; } @@ -266,5 +270,13 @@ public void setPerPartitionAutomaticFailoverInfoHolder(PartitionLevelFailoverInf this.perPartitionFailoverInfoHolder.setPartitionLevelFailoverInfo(partitionLevelFailoverInfo); } } + + public void setResponseInterceptor(Callable responseInterceptor) { + this.responseInterceptor = responseInterceptor; + } + + public Callable getResponseInterceptor() { + return this.responseInterceptor; + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java index 63c238f0e6ab..d5be4e69c442 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java @@ -466,6 +466,7 @@ public static class SubStatusCodes { public static final int INVALID_RESULT = 20910; public static final int CLOSED_CLIENT = 20912; public static final int PPCB_INVALID_STATE = 20913; + public static final int JACKSON_STREAMS_CONSTRAINED = 20914; //SDK Codes (Server) // IMPORTANT - whenever possible use consistency substatus codes that .Net SDK also uses @@ -479,7 +480,6 @@ public static class SubStatusCodes { public static final int SERVER_GENERATED_503 = 21008; public static final int NO_VALID_STORE_RESPONSE = 21009; public static final int SERVER_GENERATED_408 = 21010; - public static final int FAILED_TO_PARSE_SERVER_RESPONSE = 21011; } public static class HeaderValues { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java index 906fc518f2f7..354d41b1c567 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java @@ -205,7 +205,7 @@ public StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf retainedContent) { + ByteBuf retainedContent) throws Exception { checkNotNull(headers, "Argument 'headers' must not be null."); checkNotNull( @@ -233,7 +233,8 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), new ByteBufInputStream(retainedContent, true), - size); + size, + request.requestContext.getResponseInterceptor()); } else { retainedContent.release(); } @@ -243,7 +244,8 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), null, - 0); + 0, + request.requestContext.getResponseInterceptor()); } private Mono query(RxDocumentServiceRequest request) { @@ -412,7 +414,7 @@ private String ensureSlashPrefixed(String path) { */ private Mono toDocumentServiceResponse(Mono httpResponseMono, RxDocumentServiceRequest request, - HttpRequest httpRequest) { + HttpRequest httpRequest) throws Exception { return httpResponseMono .publishOn(CosmosSchedulers.TRANSPORT_RESPONSE_BOUNDED_ELASTIC) @@ -478,188 +480,139 @@ private Mono toDocumentServiceResponse(Mono 0) { - if (leakDetectionDebuggingEnabled) { - content.touch("RxGatewayStoreModel -exception creating StoreResponse - refCnt: " + content.refCnt()); - logger.debug("RxGatewayStoreModel -exception creating StoreResponse - refCnt: {}", content.refCnt()); - } - // Unwrap failed before StoreResponse took ownership -> release our retain - // there could be a race with the doOnDiscard above - so, use safeRelease - ReferenceCountUtil.safeRelease(content); - } + if (this.gatewayServerErrorInjector != null) { + // only configure when fault injection is used + rsp.setFaultInjectionRuleId( + request + .faultInjectionRequestContext + .getFaultInjectionRuleId(reactorNettyRequestRecord.getTransportRequestId())); - throw t; - } - }) - .doOnDiscard(ByteBuf.class, buf -> { - // This handles the case where the retained buffer is discarded after the map operation - // but before unwrapToStoreResponse takes ownership (e.g., during cancellation) - if (buf.refCnt() > 0) { - if (leakDetectionDebuggingEnabled) { - buf.touch("RxGatewayStoreModel - doOnDiscard after map - refCnt: " + buf.refCnt()); - logger.debug("RxGatewayStoreModel - doOnDiscard after map - refCnt: {}", buf.refCnt()); - } - ReferenceCountUtil.safeRelease(buf); + rsp.setFaultInjectionRuleEvaluationResults( + request + .faultInjectionRequestContext + .getFaultInjectionRuleEvaluationResults(reactorNettyRequestRecord.getTransportRequestId())); } - }) - .single(); - - }).map(rsp -> { - RxDocumentServiceResponse rxDocumentServiceResponse; - if (httpRequest.reactorNettyRequestRecord() != null) { - rxDocumentServiceResponse = - new RxDocumentServiceResponse(this.clientContext, rsp, - httpRequest.reactorNettyRequestRecord().takeTimelineSnapshot()); + } - } else { - rxDocumentServiceResponse = - new RxDocumentServiceResponse(this.clientContext, rsp); - } - rxDocumentServiceResponse.setCosmosDiagnostics(request.requestContext.cosmosDiagnostics); - return rxDocumentServiceResponse; - }).onErrorResume(throwable -> { - Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable); - if (!(unwrappedException instanceof Exception)) { - // fatal error - logger.error("Unexpected failure " + unwrappedException.getMessage(), unwrappedException); - return Mono.error(unwrappedException); - } + if (request.requestContext.cosmosDiagnostics != null) { + BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, rsp, globalEndpointManager); + } - Exception exception = (Exception) unwrappedException; - CosmosException dce; - if (!(exception instanceof CosmosException)) { - int statusCode = 0; - if (WebExceptionUtility.isNetworkFailure(exception)) { + return rsp; + }) + .single(); - // wrap in CosmosException - logger.error("Network failure", exception); + }).map(rsp -> { + RxDocumentServiceResponse rxDocumentServiceResponse; + if (httpRequest.reactorNettyRequestRecord() != null) { + rxDocumentServiceResponse = + new RxDocumentServiceResponse(this.clientContext, rsp, + httpRequest.reactorNettyRequestRecord().takeTimelineSnapshot()); - if (WebExceptionUtility.isReadTimeoutException(exception)) { - statusCode = HttpConstants.StatusCodes.REQUEST_TIMEOUT; - } else { - statusCode = HttpConstants.StatusCodes.SERVICE_UNAVAILABLE; - } - } + } else { + rxDocumentServiceResponse = + new RxDocumentServiceResponse(this.clientContext, rsp); + } + rxDocumentServiceResponse.setCosmosDiagnostics(request.requestContext.cosmosDiagnostics); + return rxDocumentServiceResponse; + }).onErrorResume(throwable -> { + Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable); + if (!(unwrappedException instanceof Exception)) { + // fatal error + logger.error("Unexpected failure {}", unwrappedException.getMessage(), unwrappedException); + return Mono.error(unwrappedException); + } - dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, exception); - BridgeInternal.setRequestHeaders(dce, request.getHeaders()); - } else { - logger.error("Non-network failure", exception); - dce = (CosmosException) exception; - } + Exception exception = (Exception) unwrappedException; + CosmosException dce; + if (!(exception instanceof CosmosException)) { + // wrap in CosmosException + logger.error("Network failure", exception); - if (WebExceptionUtility.isNetworkFailure(dce)) { - if (WebExceptionUtility.isReadTimeoutException(dce)) { - BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_READ_TIMEOUT); + int statusCode = 0; + if (WebExceptionUtility.isNetworkFailure(exception)) { + if (WebExceptionUtility.isReadTimeoutException(exception)) { + statusCode = HttpConstants.StatusCodes.REQUEST_TIMEOUT; } else { - BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_UNAVAILABLE); + statusCode = HttpConstants.StatusCodes.SERVICE_UNAVAILABLE; } } - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setRequestUri(dce, Uri.create(httpRequest.uri().toString())); - - if (request.requestContext.cosmosDiagnostics != null) { - if (httpRequest.reactorNettyRequestRecord() != null) { - ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); - BridgeInternal.setRequestTimeline(dce, reactorNettyRequestRecord.takeTimelineSnapshot()); - - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionRuleId( - dce, - request.faultInjectionRequestContext - .getFaultInjectionRuleId(reactorNettyRequestRecord.getTransportRequestId())); - - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionEvaluationResults( - dce, - request.faultInjectionRequestContext - .getFaultInjectionRuleEvaluationResults(reactorNettyRequestRecord.getTransportRequestId())); - } + dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, exception); + BridgeInternal.setRequestHeaders(dce, request.getHeaders()); + } else { + dce = (CosmosException) exception; + } - BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, dce, globalEndpointManager); + if (WebExceptionUtility.isNetworkFailure(dce)) { + if (WebExceptionUtility.isReadTimeoutException(dce)) { + BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_READ_TIMEOUT); + } else { + BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_UNAVAILABLE); } + } - return Mono.error(dce); - }).doFinally(signalType -> { - - if (signalType != SignalType.CANCEL) { - return; - } + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setRequestUri(dce, Uri.create(httpRequest.uri().toString())); + if (request.requestContext.cosmosDiagnostics != null) { if (httpRequest.reactorNettyRequestRecord() != null) { - - OperationCancelledException oce = new OperationCancelledException("", httpRequest.uri()); - ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); + BridgeInternal.setRequestTimeline(dce, reactorNettyRequestRecord.takeTimelineSnapshot()); + + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionRuleId( + dce, + request.faultInjectionRequestContext + .getFaultInjectionRuleId(reactorNettyRequestRecord.getTransportRequestId())); + + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionEvaluationResults( + dce, + request.faultInjectionRequestContext + .getFaultInjectionRuleEvaluationResults(reactorNettyRequestRecord.getTransportRequestId())); + } - RequestTimeline requestTimeline = reactorNettyRequestRecord.takeTimelineSnapshot(); - long transportRequestId = reactorNettyRequestRecord.getTransportRequestId(); - - GatewayRequestTimelineContext gatewayRequestTimelineContext = new GatewayRequestTimelineContext(requestTimeline, transportRequestId); - - request.requestContext.cancelledGatewayRequestTimelineContexts.add(gatewayRequestTimelineContext); + BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, dce, globalEndpointManager); + } - if (request.requestContext.getCrossRegionAvailabilityContext() != null) { + return Mono.error(dce); + }).doFinally(signalType -> { - CrossRegionAvailabilityContextForRxDocumentServiceRequest availabilityStrategyContextForReq = - request.requestContext.getCrossRegionAvailabilityContext(); + if (signalType != SignalType.CANCEL) { + return; + } - if (availabilityStrategyContextForReq.getAvailabilityStrategyContext().isAvailabilityStrategyEnabled() && !availabilityStrategyContextForReq.getAvailabilityStrategyContext().isHedgedRequest()) { + if (httpRequest.reactorNettyRequestRecord() != null) { - BridgeInternal.setRequestTimeline(oce, reactorNettyRequestRecord.takeTimelineSnapshot()); + ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionRuleId( - oce, - request.faultInjectionRequestContext - .getFaultInjectionRuleId(transportRequestId)); + RequestTimeline requestTimeline = reactorNettyRequestRecord.takeTimelineSnapshot(); + long transportRequestId = reactorNettyRequestRecord.getTransportRequestId(); - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionEvaluationResults( - oce, - request.faultInjectionRequestContext - .getFaultInjectionRuleEvaluationResults(transportRequestId)); + GatewayRequestTimelineContext gatewayRequestTimelineContext = new GatewayRequestTimelineContext(requestTimeline, transportRequestId); - BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, oce, globalEndpointManager); - } - } - } - }); + request.requestContext.cancelledGatewayRequestTimelineContexts.add(gatewayRequestTimelineContext); + } + }); } private void validateOrThrow(RxDocumentServiceRequest request, @@ -674,11 +627,11 @@ private void validateOrThrow(RxDocumentServiceRequest request, ? status.reasonPhrase().replace(" ", "") : ""; - String body = retainedBodyAsByteBuf.readableBytes() > 0 + String body = retainedBodyAsByteBuf != null ? retainedBodyAsByteBuf.toString(StandardCharsets.UTF_8) : null; - ReferenceCountUtil.safeRelease(retainedBodyAsByteBuf); + retainedBodyAsByteBuf.release(); CosmosError cosmosError; cosmosError = (StringUtils.isNotEmpty(body)) ? new CosmosError(body) : new CosmosError(); @@ -783,8 +736,12 @@ public Mono processMessage(RxDocumentServiceRequest r return Mono.error(dce); } - ).flatMap(response -> - this.captureSessionTokenAndHandlePartitionSplit(request, response.getResponseHeaders()).then(Mono.just(response)) + ).flatMap(response -> { + + StringBuilder sb = new StringBuilder(); + sb.append("RxGatewayStoreModel.processMessage:").append(","); + return this.captureSessionTokenAndHandlePartitionSplit(request, response.getResponseHeaders(), sb).then(Mono.just(response)); + } ); } @@ -817,11 +774,6 @@ public void recordOpenConnectionsAndInitCachesStarted(List getDefaultHeaders() { return this.defaultHeaders; } @@ -843,13 +795,19 @@ private void captureSessionToken(RxDocumentServiceRequest request, Map captureSessionTokenAndHandlePartitionSplit(RxDocumentServiceRequest request, - Map responseHeaders) { + Map responseHeaders, + StringBuilder sb) { + + if (sb != null) { + sb.append("RxGatewayStoreModel.captureSessionTokenAndHandlePartitionSplit:").append(","); + } + this.captureSessionToken(request, responseHeaders); if (request.requestContext.resolvedPartitionKeyRange != null && StringUtils.isNotEmpty(request.requestContext.resolvedCollectionRid) && StringUtils.isNotEmpty(responseHeaders.get(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID)) && !responseHeaders.get(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID).equals(request.requestContext.resolvedPartitionKeyRange.getId())) { - return this.partitionKeyRangeCache.refreshAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request.requestContext.resolvedCollectionRid) + return this.partitionKeyRangeCache.refreshAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request.requestContext.resolvedCollectionRid, sb) .flatMap(collectionRoutingMapValueHolder -> Mono.empty()); } return Mono.empty(); @@ -895,8 +853,11 @@ protected Mono resolvePartitionKeyRangeByPkRangeId(RxDocument MetadataDiagnosticsContext metadataCtx = BridgeInternal .getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics); + StringBuilder sb = new StringBuilder(); + sb.append("RxGatewayStoreModel.resolvePartitionKeyRangeByPkRangeId:").append(","); + if (pkRangeId.getCollectionRid() != null) { - return resolvePartitionKeyRangeByPkRangeIdCore(pkRangeId, pkRangeId.getCollectionRid(), metadataCtx); + return resolvePartitionKeyRangeByPkRangeIdCore(pkRangeId, pkRangeId.getCollectionRid(), metadataCtx, sb); } return this.collectionCache.resolveCollectionAsync( @@ -905,13 +866,15 @@ protected Mono resolvePartitionKeyRangeByPkRangeId(RxDocument .flatMap(collectionHolder -> resolvePartitionKeyRangeByPkRangeIdCore( pkRangeId, collectionHolder.v.getResourceId(), - metadataCtx)); + metadataCtx, + sb)); } private Mono resolvePartitionKeyRangeByPkRangeIdCore( PartitionKeyRangeIdentity pkRangeId, String effectiveCollectionRid, - MetadataDiagnosticsContext metadataCtx) { + MetadataDiagnosticsContext metadataCtx, + StringBuilder sb) { Objects.requireNonNull(pkRangeId, "Parameter 'pkRangeId' is required and cannot be null"); Objects.requireNonNull( @@ -923,7 +886,8 @@ private Mono resolvePartitionKeyRangeByPkRangeIdCore( metadataCtx, effectiveCollectionRid, null, - null + null, + sb ) .flatMap(collectionRoutingMapValueHolder -> { @@ -978,10 +942,15 @@ private Mono applySessionToken(RxDocumentServiceRequest request) { } return Mono.empty(); } + + StringBuilder sb = new StringBuilder(); + sb.append("RxGatewayStoreModel.applySessionToken:").append(","); + return partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), collectionValueHolder.v.getResourceId(), null, - null).flatMap(collectionRoutingMapValueHolder -> { + null, + sb).flatMap(collectionRoutingMapValueHolder -> { if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) { //Apply the ambient session. String sessionToken = this.sessionContainer.resolveGlobalSessionToken(request); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java index d32e5d901f18..94652c68737e 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java @@ -98,7 +98,7 @@ public StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf content) { + ByteBuf content) throws Exception { if (content == null) { return super.unwrapToStoreResponse(endpoint, request, statusCode, headers, Unpooled.EMPTY_BUFFER); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java index ec3956eae88d..2d10b4be2ec7 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java @@ -6,6 +6,7 @@ import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState; import java.time.Duration; +import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -17,6 +18,7 @@ public class ProcessorSettings { private Duration feedPollDelay; private final ChangeFeedState startState; private final CosmosAsyncContainer collectionSelfLink; + private Callable responseInterceptor; public ProcessorSettings( ChangeFeedState startState, @@ -54,4 +56,13 @@ public ChangeFeedState getStartState() { public CosmosAsyncContainer getCollectionSelfLink() { return this.collectionSelfLink; } + + public Callable getResponseInterceptor() { + return this.responseInterceptor; + } + + public ProcessorSettings withResponseInterceptor(Callable responseInterceptor) { + this.responseInterceptor = responseInterceptor; + return this; + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java index 1092a5a08f28..69f9c61b92fc 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java @@ -3,6 +3,7 @@ package com.azure.cosmos.implementation.changefeed.common; import com.azure.cosmos.CosmosException; +import com.azure.cosmos.implementation.HttpConstants; /** * Classifies exceptions based on the status codes. @@ -27,6 +28,14 @@ public static StatusCodeErrorType classifyClientException(CosmosException client return StatusCodeErrorType.PARTITION_NOT_FOUND; } + if (clientException.getStatusCode() == HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR) { + if (subStatusCode == HttpConstants.SubStatusCodes.JACKSON_STREAMS_CONSTRAINED) { + return StatusCodeErrorType.JACKSON_STREAMS_CONSTRAINED; + } else if (subStatusCode == HttpConstants.SubStatusCodes.FAILED_TO_PARSE_SERVER_RESPONSE) { + return StatusCodeErrorType.JSON_PARSING_ERROR; + } + } + if (clientException.getStatusCode() == ChangeFeedHelper.HTTP_STATUS_CODE_GONE && (subStatusCode == SubStatusCode_PartitionKeyRangeGone || subStatusCode == SubStatusCode_Splitting_Or_Merging)) { return StatusCodeErrorType.PARTITION_SPLIT_OR_MERGE; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/StatusCodeErrorType.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/StatusCodeErrorType.java index eae515e7dda3..a8c55788374b 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/StatusCodeErrorType.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/StatusCodeErrorType.java @@ -10,5 +10,7 @@ public enum StatusCodeErrorType { PARTITION_NOT_FOUND, PARTITION_SPLIT_OR_MERGE, TRANSIENT_ERROR, - MAX_ITEM_COUNT_TOO_LARGE + MAX_ITEM_COUNT_TOO_LARGE, + JACKSON_STREAMS_CONSTRAINED, + JSON_PARSING_ERROR } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java index 4fb0c1867b81..57abcd563f65 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java @@ -76,7 +76,8 @@ public PartitionProcessor create(Lease lease, ChangeFeedObserver observer, Cl ProcessorSettings settings = new ProcessorSettings(state, this.collectionSelfLink) .withFeedPollDelay(this.changeFeedProcessorOptions.getFeedPollDelay()) - .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()); + .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()) + .withResponseInterceptor(this.changeFeedProcessorOptions.getResponseInterceptor()); PartitionCheckpointer checkpointer = new PartitionCheckpointerImpl(this.leaseCheckpointer, lease); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java index 8baff17be50b..bb38517c614f 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java @@ -4,7 +4,9 @@ import com.azure.cosmos.CosmosException; import com.azure.cosmos.ThroughputControlGroupConfig; +import com.azure.cosmos.implementation.Configs; import com.azure.cosmos.implementation.CosmosSchedulers; +import com.azure.cosmos.implementation.Strings; import com.azure.cosmos.implementation.Utils; import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.implementation.changefeed.CancellationToken; @@ -27,6 +29,7 @@ import com.azure.cosmos.models.CosmosChangeFeedRequestOptions; import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.models.ModelBridgeInternal; +import com.azure.cosmos.util.CosmosChangeFeedContinuationTokenUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -34,6 +37,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -58,6 +62,9 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile String lastServerContinuationToken; private volatile boolean hasMoreResults; private volatile boolean hasServerContinuationTokenChange; + private final int maxStreamsConstrainedRetries = 10; + private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); + private final AtomicInteger unparseableDocumentRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -82,6 +89,8 @@ public PartitionProcessorImpl(ChangeFeedObserver observer, settings.getStartState(), settings.getMaxItemCount(), this.changeFeedMode); + this.options.setResponseInterceptor(settings.getResponseInterceptor()); + this.feedRangeThroughputControlConfigManager = feedRangeThroughputControlConfigManager; this.lastProcessedTime = Instant.now(); } @@ -187,6 +196,10 @@ public Mono run(CancellationToken cancellationToken) { if (this.options.getMaxItemCount() != this.settings.getMaxItemCount()) { this.options.setMaxItemCount(this.settings.getMaxItemCount()); // Reset after successful execution. } + + this.options.setResponseInterceptor(settings.getResponseInterceptor()); + this.streamsConstrainedRetries.set(0); + this.unparseableDocumentRetries.set(0); }) .onErrorResume(throwable -> { if (throwable instanceof CosmosException) { @@ -221,6 +234,7 @@ public Mono run(CancellationToken cancellationToken) { this.resultException = new RuntimeException(clientException); } break; + case MAX_ITEM_COUNT_TOO_LARGE: { if (this.options.getMaxItemCount() <= 1) { logger.error( @@ -246,8 +260,94 @@ public Mono run(CancellationToken cancellationToken) { return !cancellationToken.isCancellationRequested() && currentTime.isBefore(stopTimer); }).flatMap(values -> Flux.empty()); } + break; } - break; + case JACKSON_STREAMS_CONSTRAINED: { + + if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + " : Streams constrained exception encountered. To enable automatic retries, please set the " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + int retryCount = this.streamsConstrainedRetries.incrementAndGet(); + boolean shouldRetry = retryCount <= this.maxStreamsConstrainedRetries; + + if (!shouldRetry) { + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + ": Reached max retries for streams constrained exception with statusCode : [" + clientException.getStatusCode() + "]" + " : subStatusCode " + clientException.getSubStatusCode() + " : message " + clientException.getMessage() + ", failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + logger.warn( + "Lease with token : " + this.lease.getLeaseToken() + " : Streams constrained exception encountered, will retry. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", + clientException); + + + if (this.options.getMaxItemCount() == -1) { + logger.warn( + "Lease with token : " + this.lease.getLeaseToken() + " : max item count is set to -1, will retry after setting it to 100. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", + clientException); + this.options.setMaxItemCount(100); + return Flux.empty(); + } + + if (this.options.getMaxItemCount() <= 1) { + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + " Cannot reduce maxItemCount further as it's already at :" + this.options.getMaxItemCount(), clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + this.options.setMaxItemCount(this.options.getMaxItemCount() / 2); + logger.warn("Lease with token : " + this.lease.getLeaseToken() + " Reducing maxItemCount, new value: " + this.options.getMaxItemCount()); + return Flux.empty(); + } + case JSON_PARSING_ERROR: + + if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + if (this.unparseableDocumentRetries.compareAndSet(0, 1)) { + logger.warn( + "Lease with token : " + this.lease.getLeaseToken() + " : Attempting a retry on parsing error.", clientException); + this.options.setMaxItemCount(1); + return Flux.empty(); + } else { + + logger.error("Lease with token : " + this.lease.getLeaseToken() + " : Encountered parsing error which is not recoverable, attempting to skip document", clientException); + + String continuation = CosmosChangeFeedContinuationTokenUtils.extractContinuationTokenFromCosmosException(clientException); + + if (Strings.isNullOrEmpty(continuation)) { + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + ": Unable to extract continuation token post the parsing exception, failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + ChangeFeedState continuationState = ChangeFeedState.fromString(continuation); + return this.checkpointer.checkpointPartition(continuationState) + .doOnSuccess(lease1 -> { + logger.info("Lease with token : " + this.lease.getLeaseToken() + " Successfully skipped the unparseable document."); + this.options = + PartitionProcessorHelper.createForProcessingFromContinuation(continuation, this.changeFeedMode); + }) + .doOnError(t -> { + logger.error( + "Failed to checkpoint for lease with token : " + this.lease.getLeaseToken() + " with continuation " + this.lease.getReadableContinuationToken() + " from thread " + Thread.currentThread().getId(), t); + this.resultException = new RuntimeException(t); + }); + } default: { logger.error( "Lease with token " + this.lease.getLeaseToken() + diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java index 0a0209778c74..ed820f5496b6 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java @@ -100,7 +100,8 @@ public PartitionProcessor create(Lease lease, ChangeFeedObserver obser ProcessorSettings settings = new ProcessorSettings(state, this.collectionSelfLink) .withFeedPollDelay(this.changeFeedProcessorOptions.getFeedPollDelay()) - .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()); + .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()) + .withResponseInterceptor(this.changeFeedProcessorOptions.getResponseInterceptor()); PartitionCheckpointer checkpointer = new PartitionCheckpointerImpl(this.leaseCheckpointer, lease); return new PartitionProcessorImpl( diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java index db5b4f4a87df..1ed99ca2ff5e 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java @@ -4,9 +4,11 @@ import com.azure.cosmos.CosmosException; import com.azure.cosmos.ThroughputControlGroupConfig; +import com.azure.cosmos.implementation.Configs; import com.azure.cosmos.implementation.CosmosSchedulers; import com.azure.cosmos.implementation.HttpConstants; import com.azure.cosmos.implementation.ImplementationBridgeHelpers; +import com.azure.cosmos.implementation.Strings; import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.implementation.changefeed.CancellationToken; import com.azure.cosmos.implementation.changefeed.ChangeFeedContextClient; @@ -28,6 +30,7 @@ import com.azure.cosmos.models.CosmosChangeFeedRequestOptions; import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.models.ModelBridgeInternal; +import com.azure.cosmos.util.CosmosChangeFeedContinuationTokenUtils; import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +39,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -58,6 +62,9 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile String lastServerContinuationToken; private volatile boolean hasMoreResults; private volatile boolean hasServerContinuationTokenChange; + private final int maxStreamsConstrainedRetries = 10; + private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); + private final AtomicInteger unparseableDocumentRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -77,6 +84,8 @@ public PartitionProcessorImpl(ChangeFeedObserver observer, ChangeFeedState state = settings.getStartState(); this.options = ModelBridgeInternal.createChangeFeedRequestOptionsForChangeFeedState(state); this.options.setMaxItemCount(settings.getMaxItemCount()); + this.options.setResponseInterceptor(settings.getResponseInterceptor()); + // For pk version, merge is not support, exclude it from the capabilities header ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.getCosmosChangeFeedRequestOptionsAccessor() .setHeader( @@ -194,6 +203,10 @@ public Mono run(CancellationToken cancellationToken) { if (this.options.getMaxItemCount() != this.settings.getMaxItemCount()) { this.options.setMaxItemCount(this.settings.getMaxItemCount()); // Reset after successful execution. } + + this.options.setResponseInterceptor(settings.getResponseInterceptor()); + this.streamsConstrainedRetries.set(0); + this.unparseableDocumentRetries.set(0); }) .onErrorResume(throwable -> { if (throwable instanceof CosmosException) { @@ -251,8 +264,95 @@ public Mono run(CancellationToken cancellationToken) { return !cancellationToken.isCancellationRequested() && currentTime.isBefore(stopTimer); }).flatMap(values -> Flux.empty()); } + + break; } - break; + case JACKSON_STREAMS_CONSTRAINED: { + + if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { + logger.error( + "Partition : " + this.lease.getLeaseToken() + " : Streams constrained exception encountered. To enable automatic retries, please set the " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + int retryCount = this.streamsConstrainedRetries.incrementAndGet(); + boolean shouldRetry = retryCount <= this.maxStreamsConstrainedRetries; + + if (!shouldRetry) { + logger.error( + "Partition : " + this.lease.getLeaseToken() + ": Reached max retries for streams constrained exception with statusCode : [" + clientException.getStatusCode() + "]" + " : subStatusCode " + clientException.getSubStatusCode() + " : message " + clientException.getMessage() + ", failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + logger.warn( + "Partition : " + this.lease.getLeaseToken() + " : Streams constrained exception encountered, will retry. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", + clientException); + + if (this.options.getMaxItemCount() == -1) { + logger.warn( + "Partition : " + this.lease.getLeaseToken() + " : max item count is set to -1, will retry after setting it to 100. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", + clientException); + this.options.setMaxItemCount(100); + return Flux.empty(); + } + + if (this.options.getMaxItemCount() <= 1) { + logger.error( + "Cannot reduce maxItemCount further as it's already at :" + this.options.getMaxItemCount(), clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + this.options.setMaxItemCount(this.options.getMaxItemCount() / 2); + logger.warn("Reducing maxItemCount, new value: " + this.options.getMaxItemCount()); + return Flux.empty(); + } + case JSON_PARSING_ERROR: + + if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { + logger.error( + "Partition : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + if (this.unparseableDocumentRetries.compareAndSet(0, 1)) { + logger.warn( + "Partition : " + this.lease.getLeaseToken() + " : Attempting a retry on parsing error.", clientException); + this.options.setMaxItemCount(1); + return Flux.empty(); + } else { + + logger.error("Partition : " + this.lease.getLeaseToken() + " : Encountered parsing error which is not recoverable, attempting to skip document", clientException); + + String continuation = CosmosChangeFeedContinuationTokenUtils.extractContinuationTokenFromCosmosException(clientException); + + if (Strings.isNullOrEmpty(continuation)) { + logger.error( + "Partition : " + this.lease.getLeaseToken() + ": Unable to extract continuation token post the parsing exception, failing.", + clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); + } + + ChangeFeedState continuationState = ChangeFeedState.fromString(continuation); + return this.checkpointer.checkpointPartition(continuationState) + .doOnSuccess(lease1 -> { + logger.info("Partition : " + this.lease.getLeaseToken() + " Successfully skipped the unparseable document."); + this.options = + CosmosChangeFeedRequestOptions + .createForProcessingFromContinuation(continuation); + }) + .doOnError(t -> { + logger.error( + "Failed to checkpoint for Partition : " + this.lease.getLeaseToken() + " with continuation " + this.lease.getReadableContinuationToken() + " from thread " + Thread.currentThread().getId(), t); + this.resultException = new RuntimeException(t); + }); + } default: { logger.error("Unrecognized Cosmos exception returned error code " + docDbError, clientException); this.resultException = new RuntimeException(clientException); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java index bbf642ba667a..6d98eec86baa 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java @@ -6,6 +6,7 @@ import com.azure.cosmos.implementation.Configs; import com.azure.cosmos.implementation.HttpConstants; import com.azure.cosmos.implementation.Utils; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; import io.netty.buffer.ByteBufInputStream; import io.netty.util.internal.StringUtil; @@ -17,6 +18,8 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.Callable; import java.util.Base64; import java.util.Map; @@ -26,9 +29,16 @@ public class JsonNodeStorePayload implements StorePayload { private final int responsePayloadSize; private final JsonNode jsonValue; + public JsonNodeStorePayload( + ByteBufInputStream bufferStream, + int readableBytes, + Map responseHeaders, + Callable interceptor) throws Exception { + public JsonNodeStorePayload(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { if (readableBytes > 0) { this.responsePayloadSize = readableBytes; + this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders, interceptor); this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders); } else { this.responsePayloadSize = 0; @@ -36,16 +46,52 @@ public JsonNodeStorePayload(ByteBufInputStream bufferStream, int readableBytes, } } + private static JsonNode fromJson( + ByteBufInputStream bufferStream, + int readableBytes, + Map responseHeaders, + Callable interceptor) throws Exception { + private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { byte[] bytes = new byte[readableBytes]; try { + + if (interceptor != null) { + interceptor.call(); + } + bufferStream.read(bytes); + return Utils.getSimpleObjectMapper().readTree(bytes); } catch (IOException e) { + + logger.error("Unable to parse JSON, fallback to use customized charset decoder.", e); + if (fallbackCharsetDecoder != null) { + + if (interceptor != null) { + interceptor.call(); + } + + return fromJsonWithFallbackCharsetDecoder(bytes, responseHeaders, interceptor); logger.warn("Unable to parse JSON, fallback to use customized charset decoder.", e); return fromJsonWithFallbackCharsetDecoder(bytes, responseHeaders); } else { + Exception nestedException = new IllegalStateException("Unable to parse JSON.", e); + + if (e instanceof StreamConstraintsException) { + throw Utils.createCosmosException( + HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR, + HttpConstants.SubStatusCodes.JACKSON_STREAMS_CONSTRAINED, + nestedException, + responseHeaders); + } + + throw Utils.createCosmosException( + HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR, + HttpConstants.SubStatusCodes.FAILED_TO_PARSE_SERVER_RESPONSE, + nestedException, + responseHeaders); String baseErrorMessage = "Failed to parse JSON document. No fallback charset decoder configured."; @@ -67,8 +113,15 @@ private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBy } } + private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map responseHeaders, Callable interceptor) throws Exception { private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map responseHeaders) { try { + + if (interceptor != null) { + // Log if a fallback charset decoder is enabled for this request + interceptor.call(); + } + String sanitizedJson = fallbackCharsetDecoder.decode(ByteBuffer.wrap(bytes)).toString(); return Utils.getSimpleObjectMapper().readTree(sanitizedJson); } catch (IOException e) { @@ -83,12 +136,30 @@ private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map toStoreResponse(HttpResponse httpClientResponse, Stri // transforms to Mono int size = 0; if (byteBufContent == null || (size = byteBufContent.readableBytes()) == 0) { + try { + return new StoreResponse( + endpoint, + httpClientResponse.statusCode(), + HttpUtils.unescape(httpResponseHeaders.toMap()), + null, + 0, + null); + } catch (Exception e) { + throw reactor.core.Exceptions.propagate(e); + } + } + + try { return new StoreResponse( endpoint, httpClientResponse.statusCode(), HttpUtils.unescape(httpResponseHeaders.toMap()), - null, - 0); + new ByteBufInputStream(byteBufContent, true), + size, + null); + } catch (Exception e) { + throw reactor.core.Exceptions.propagate(e); } - - return new StoreResponse( - endpoint, - httpClientResponse.statusCode(), - HttpUtils.unescape(httpResponseHeaders.toMap()), - new ByteBufInputStream(byteBufContent, true), - size); }); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java index 22c5ed8624b6..c8b10eb3c17d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java @@ -16,6 +16,7 @@ import com.azure.cosmos.implementation.OperationCancelledException; import com.azure.cosmos.implementation.RequestTimeline; import com.azure.cosmos.implementation.RxDocumentServiceRequest; +import com.azure.cosmos.implementation.RxDocumentServiceResponse; import com.azure.cosmos.implementation.UserAgentContainer; import com.azure.cosmos.implementation.clienttelemetry.ClientTelemetry; import com.azure.cosmos.implementation.clienttelemetry.CosmosMeterOptions; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java index a4bab8f3edbe..5fdc21a79314 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.netty.buffer.ByteBufInputStream; import io.netty.util.IllegalReferenceCountException; +import io.netty.util.IllegalReferenceCountException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -51,7 +53,8 @@ public StoreResponse( int status, Map headerMap, ByteBufInputStream contentStream, - int responsePayloadLength) { + int responsePayloadLength, + Callable responseInterceptor) throws Exception { checkArgument((contentStream == null) == (responsePayloadLength == 0), "Parameter 'contentStream' must be consistent with 'responsePayloadLength'."); @@ -71,8 +74,14 @@ public StoreResponse( replicaStatusList = new HashMap<>(); if (contentStream != null) { try { +<<<<<<< HEAD this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap); } finally { +======= + this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap, responseInterceptor); + } + finally { +>>>>>>> 8f6eb3273f6 (Add a way to skip records in Change Feed Processor. (#26)) try { contentStream.close(); } catch (Throwable e) { @@ -204,6 +213,35 @@ public String getHeaderValue(String attribute) { return null; } + //NOTE: only used for testing purpose to change the response header value + private void setHeaderValue(String headerName, String value) { + if (this.responseHeaderValues == null || this.responseHeaderNames.length != this.responseHeaderValues.length) { + return; + } + + for (int i = 0; i < responseHeaderNames.length; i++) { + if (responseHeaderNames[i].equalsIgnoreCase(headerName)) { + responseHeaderValues[i] = value; + break; + } + } + this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap); + } finally { + this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap, responseInterceptor); + } + finally { + } catch (Throwable e) { + if (!(e instanceof IllegalReferenceCountException)) { + // Log as warning instead of debug to make ByteBuf leak issues more visible + logger.warn("Failed to close content stream. This may cause a Netty ByteBuf leak.", e); + } + // NOTE: Only used in local test through transport client interceptor + public void setGCLSN(long gclsn) { + this.setHeaderValue(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, String.valueOf(gclsn)); + } + + } + //NOTE: only used for testing purpose to change the response header value private void setHeaderValue(String headerName, String value) { if (this.responseHeaderValues == null || this.responseHeaderNames.length != this.responseHeaderValues.length) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java index d7e8ccb56421..efe1e9a93dac 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java @@ -71,6 +71,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -967,7 +968,7 @@ private void completeAllPendingRequestsExceptionally( * @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager request manager} belongs. * @param response the {@link RntbdResponse message} received. */ - private void messageReceived(final ChannelHandlerContext context, final RntbdResponse response) { + private void messageReceived(final ChannelHandlerContext context, final RntbdResponse response) throws Exception { final Long transportRequestId = response.getTransportRequestId(); @@ -1001,6 +1002,8 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes final String requestUriAsString = requestRecord.args().physicalAddressUri() != null ? requestRecord.args().physicalAddressUri().getURI().toString() : null; + Callable responseInterceptor = serviceRequest.requestContext.getResponseInterceptor(); + if ((HttpResponseStatus.OK.code() <= statusCode && statusCode < HttpResponseStatus.MULTIPLE_CHOICES.code()) || statusCode == HttpResponseStatus.NOT_MODIFIED.code()) { @@ -1008,7 +1011,7 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes if (rntbdCtx == null) { throw new IllegalStateException("Expecting non-null rntbd context."); } - final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString); + final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString, responseInterceptor); if (this.serverErrorInjector != null) { Consumer completeWithInjectedDelayConsumer = diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java index 1fb8b43d3fe8..6dbe28c32b60 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java @@ -25,6 +25,7 @@ import java.time.Instant; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdConstants.RntbdResponseHeader; @@ -347,7 +348,7 @@ public static RntbdResponse decode(final ByteBuf in) { return new RntbdResponse(in.readSlice(end - start), frame, headers, content); } - StoreResponse toStoreResponse(final String serverVersion, final String endpoint) { + StoreResponse toStoreResponse(final String serverVersion, final String endpoint, Callable responseInterceptor) throws Exception { checkNotNull(serverVersion, "Argument 'serverVersion' must not be null."); @@ -359,7 +360,8 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint) this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), null, - 0); + 0, + responseInterceptor); } return new StoreResponse( @@ -367,7 +369,8 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint) this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), new ByteBufInputStream(this.content.retain(), true), - length); + length, + responseInterceptor); } // endregion diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java index 58a2dc95cf8d..451c1d73dda9 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java @@ -14,5 +14,5 @@ StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf retainedContent); + ByteBuf retainedContent) throws Exception; } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java index ed94c7232a6a..c1830b58ced4 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java @@ -4,9 +4,12 @@ package com.azure.cosmos.implementation.query; import com.azure.cosmos.BridgeInternal; +import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DiagnosticsClientContext; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; +import com.azure.cosmos.implementation.HttpConstants; +import com.azure.cosmos.implementation.guava25.base.Strings; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.ImplementationBridgeHelpers; import com.azure.cosmos.implementation.GoneException; @@ -166,6 +169,22 @@ protected String applyServerResponseContinuation( serverContinuationToken, request, shouldMoveToNextTokenOnETagReplace); } + @Override + protected String applyServerResponseContinuation(String serverContinuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { + + if (!Strings.isNullOrEmpty(serverContinuationToken)) { + String replacedContinuation = this.changeFeedState.applyServerResponseContinuation( + serverContinuationToken, request, false); + + Map responseHeaders = cosmosException.getResponseHeaders(); + responseHeaders.put(HttpConstants.HttpHeaders.E_TAG, replacedContinuation); + + return replacedContinuation; + } + + return com.azure.cosmos.implementation.Strings.Emtpy; + } + @Override protected boolean isFullyDrained(boolean isChangeFeed, FeedResponse response) { if (ModelBridgeInternal.noChanges(response)) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java index 7089f50d0a8b..c0ab24caf37a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java @@ -4,10 +4,14 @@ package com.azure.cosmos.implementation.query; import com.azure.cosmos.CosmosDiagnostics; +import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.CrossRegionAvailabilityContextForRxDocumentServiceRequest; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.FeedOperationContextForCircuitBreaker; import com.azure.cosmos.implementation.GlobalEndpointManager; +import com.azure.cosmos.implementation.HttpConstants; +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.ImplementationBridgeHelpers; import com.azure.cosmos.implementation.RxDocumentServiceRequest; @@ -21,6 +25,7 @@ import reactor.core.publisher.SignalType; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -101,6 +106,11 @@ protected abstract String applyServerResponseContinuation( RxDocumentServiceRequest request, FeedResponse response); + protected abstract String applyServerResponseContinuation( + String serverContinuationToken, + RxDocumentServiceRequest request, + CosmosException cosmosException); + protected abstract boolean isFullyDrained(boolean isChangeFeed, FeedResponse response); protected abstract String getContinuationForLogging(); @@ -145,6 +155,10 @@ private void updateState(FeedResponse response, RxDocumentServiceRequest requ } } + private void updateStateForException(String continuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { + this.applyServerResponseContinuation(continuationToken, request, cosmosException); + } + protected void reEnableShouldFetchMoreForRetry() { this.shouldFetchMore.set(true); } @@ -198,7 +212,24 @@ private Mono> nextPage(RxDocumentServiceRequest request) { } } }) - .doOnError(throwable -> completed.set(true)) + .doOnError(throwable -> { + + if (throwable instanceof CosmosException) { + CosmosException cosmosException = (CosmosException) throwable; + Map responseHeaders = cosmosException.getResponseHeaders(); + + if (responseHeaders != null) { + String responseContinuation = + OperationType.ReadFeed.equals(request.getOperationType()) && ResourceType.Document.equals(request.getResourceType()) ? + responseHeaders.get(HttpConstants.HttpHeaders.E_TAG) : + responseHeaders.get(HttpConstants.HttpHeaders.CONTINUATION); + + this.updateStateForException(responseContinuation, request, cosmosException); + } + } + + completed.set(true); + }) .doFinally(signalType -> { // If the signal type is not cancel(which means success or error), we do not need to tracking the diagnostics here // as the downstream will capture it diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java index 37153b6cbbf1..22c13ca560c6 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java @@ -5,6 +5,7 @@ import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosDiagnostics; +import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; @@ -53,6 +54,14 @@ protected String applyServerResponseContinuation( return this.continuationToken = serverContinuationToken; } + @Override + protected String applyServerResponseContinuation( + String serverContinuationToken, + RxDocumentServiceRequest request, + CosmosException cosmosException) { + return serverContinuationToken; + } + @Override protected boolean isFullyDrained(boolean isChangeFeed, FeedResponse response) { // if token is null or if change feed query and no changes then done diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java index 9efe5a2dae66..1e03232c427a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java @@ -5,11 +5,13 @@ import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosDiagnostics; +import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.ObservableHelper; import com.azure.cosmos.implementation.RxDocumentClientImpl; import com.azure.cosmos.implementation.RxDocumentServiceRequest; +import com.azure.cosmos.implementation.Strings; import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.spark.OperationContextAndListenerTuple; @@ -53,7 +55,7 @@ public ServerSideOnlyContinuationNonDocumentFetcherImpl( checkNotNull(client, "Argument 'client' must not be null."); checkNotNull(createRequestFunc, "Argument 'createRequestFunc' must not be null."); - + this.createRequestFunc = createRequestFunc; this.continuationToken = continuationToken; this.retryPolicySupplier = () -> client.getResetSessionTokenRetryPolicy().getRequestPolicy(null); @@ -68,6 +70,11 @@ protected String applyServerResponseContinuation( return this.continuationToken = serverContinuationToken; } + @Override + protected String applyServerResponseContinuation(String serverContinuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { + return Strings.Emtpy; + } + @Override public Mono> nextPage() { DocumentClientRetryPolicy retryPolicy = this.retryPolicySupplier.get(); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java index 6e35e910f3b9..6e437608e28d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java @@ -9,6 +9,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -55,6 +56,7 @@ public final class ChangeFeedProcessorOptions { private Scheduler scheduler; private ThroughputControlGroupConfig feedPollThroughputControlGroupConfig; + private Callable responseInterceptor; /** * Instantiates a new Change feed processor options. @@ -386,7 +388,7 @@ public ChangeFeedProcessorOptions setScheduler(Scheduler scheduler) { * Please use this config with caution. By default, CFP will try to process the changes as fast as possible, * only use this config if you want to limit the RU that can be used for your change feed processing. * By using this config, it can slow down the process and cause the lag. - * + * * For direct mode, please configure the throughput control group with the total RU you would allow for changeFeed processing. * For gateway mode, please configure the throughput control group with the total RU you would allow for changeFeed processing / total CFP Instances. * @@ -429,4 +431,26 @@ public ChangeFeedProcessorOptions setLeaseVerificationEnabledOnRestart(boolean e public boolean isLeaseVerificationEnabledOnRestart() { return this.leaseVerificationOnRestartEnabled; } + + /** + * Sets a callback to be invoked after every response from the service to the Change Feed requests. + * This can be used for logging or metrics gathering purposes. + * + * @param responseInterceptor a callback to be invoked after every response from the service to the Change Feed requests. + * @return the {@link ChangeFeedProcessorOptions}. + */ + public ChangeFeedProcessorOptions setResponseInterceptor(Callable responseInterceptor) { + this.responseInterceptor = responseInterceptor; + return this; + } + + /** + * Gets a callback to be invoked after every response from the service to the Change Feed requests. + * This can be used for logging or metrics gathering purposes. + * + * @return a callback to be invoked after every response from the service to the Change Feed requests. + */ + public Callable getResponseInterceptor() { + return this.responseInterceptor; + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java index 800c4987f1f3..b0a6a28c9b9c 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java @@ -3,7 +3,6 @@ package com.azure.cosmos.models; -import com.azure.cosmos.ConsistencyLevel; import com.azure.cosmos.CosmosDiagnosticsThresholds; import com.azure.cosmos.CosmosItemSerializer; import com.azure.cosmos.ReadConsistencyStrategy; @@ -31,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -609,6 +609,26 @@ public Set getKeywordIdentifiers() { return this.actualRequestOptions.getKeywordIdentifiers(); } + /** + * Sets the response interceptor to be called after receiving the response + * for the request. + * + * @param responseInterceptor the response interceptor. + */ + public void setResponseInterceptor(Callable responseInterceptor) { + this.actualRequestOptions.setResponseInterceptor(responseInterceptor); + } + + /** + * Gets the response interceptor to be called after receiving the response + * for the request. + * + * @return the response interceptor. + */ + public Callable getResponseInterceptor() { + return this.actualRequestOptions.getResponseInterceptor(); + } + void setOperationContextAndListenerTuple(OperationContextAndListenerTuple operationContextAndListenerTuple) { this.actualRequestOptions.setOperationContextAndListenerTuple(operationContextAndListenerTuple); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java index 0121d731db2f..3a64cf2f948c 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java @@ -3,6 +3,8 @@ package com.azure.cosmos.util; +import com.azure.cosmos.CosmosException; +import com.azure.cosmos.implementation.HttpConstants; import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState; import com.azure.cosmos.implementation.changefeed.common.ChangeFeedStateV1; import com.azure.cosmos.implementation.feedranges.FeedRangeContinuation; @@ -108,4 +110,16 @@ private static List> getSegmentedTokens( return segmentedTokens; } + + public static String extractContinuationTokenFromCosmosException(CosmosException ce) { + Map responseHeaders = ce.getResponseHeaders(); + return getValueOrNull(responseHeaders, HttpConstants.HttpHeaders.E_TAG); + } + + private static String getValueOrNull(Map map, String key) { + if (map != null) { + return map.get(key); + } + return null; + } } From c47d56174512754386433c229bf7c54e912672ce Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Fri, 21 Nov 2025 13:14:29 -0500 Subject: [PATCH 2/6] Cleaning up PR. --- ...PerPartitionAutomaticFailoverE2ETests.java | 2 +- .../cosmos/RetryContextOnDiagnosticTest.java | 32 +- .../cosmos/implementation/RetryUtilsTest.java | 2 +- .../RxDocumentClientImplTest.java | 7 +- .../implementation/StoreResponseBuilder.java | 6 +- .../batch/CosmosBulkItemResponseTest.java | 10 +- .../TransactionalBatchResponseTests.java | 10 +- .../ConsistencyReaderTest.java | 10 +- .../ConsistencyWriterTest.java | 6 +- .../directconnectivity/EndpointMock.java | 8 +- .../JsonNodeStorePayloadTests.java | 2 +- .../directconnectivity/QuorumReaderTest.java | 12 +- ...licatedResourceClientGoneForWriteTest.java | 2 +- ...catedResourceClientPartitionSplitTest.java | 2 +- ...ReplicatedResourceClientRetryWithTest.java | 2 +- .../StoreReaderDotNetTest.java | 18 +- .../directconnectivity/StoreReaderTest.java | 8 +- .../directconnectivity/StoreResponseTest.java | 4 +- ...StoreResultDiagnosticsSerializerTests.java | 4 +- .../implementation/ChangeFeedQueryImpl.java | 8 - .../CosmosChangeFeedRequestOptionsImpl.java | 11 - .../DocumentServiceRequestContext.java | 12 - .../cosmos/implementation/HttpConstants.java | 1 + .../implementation/RxGatewayStoreModel.java | 317 ++++++++++-------- .../implementation/ThinClientStoreModel.java | 2 +- .../changefeed/ProcessorSettings.java | 11 - .../PartitionProcessorFactoryImpl.java | 3 +- .../epkversion/PartitionProcessorImpl.java | 49 +-- .../PartitionProcessorFactoryImpl.java | 3 +- .../pkversion/PartitionProcessorImpl.java | 49 +-- .../JsonNodeStorePayload.java | 88 +---- .../directconnectivity/ResponseUtils.java | 30 +- .../RntbdTransportClient.java | 1 - .../directconnectivity/StoreResponse.java | 40 +-- .../rntbd/RntbdRequestManager.java | 7 +- .../rntbd/RntbdResponse.java | 9 +- .../http/HttpTransportSerializer.java | 2 +- .../query/ChangeFeedFetcher.java | 19 -- .../cosmos/implementation/query/Fetcher.java | 33 +- ...ServerSideOnlyContinuationFetcherImpl.java | 9 - ...nlyContinuationNonDocumentFetcherImpl.java | 7 - .../models/ChangeFeedProcessorOptions.java | 24 -- .../CosmosChangeFeedRequestOptions.java | 22 +- 43 files changed, 283 insertions(+), 621 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java index c3ef4307cc53..92d21daf488b 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/PerPartitionAutomaticFailoverE2ETests.java @@ -2270,7 +2270,7 @@ private AccountLevelLocationContext getAccountLevelLocationContext(DatabaseAccou regionMap); } - private StoreResponse constructStoreResponse(OperationType operationType, int statusCode) throws Exception { + private StoreResponse constructStoreResponse(OperationType operationType, int statusCode) throws JsonProcessingException { StoreResponseBuilder storeResponseBuilder = StoreResponseBuilder.create() .withContent(OBJECT_MAPPER.writeValueAsString(getTestPojoObject())) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java index cb00426d6812..ee59f0ac723e 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java @@ -117,13 +117,6 @@ public void backoffRetryUtilityExecuteRetry() throws Exception { .withContent(rawJson) .withStatus(200) .build())); - .thenReturn(Mono.just(new StoreResponse( - null, - 200, - new HashMap<>(), - new ByteBufInputStream(buffer, true), - buffer.readableBytes(), - null))); Mono monoResponse = BackoffRetryUtility.executeRetry(callbackMethod, retryPolicy); StoreResponse response = validateSuccess(monoResponse); @@ -155,7 +148,7 @@ public void backoffRetryUtilityExecuteRetryWithFailure() throws Exception { @Test(groups = {"unit"}, timeOut = TIMEOUT * 2) @SuppressWarnings("unchecked") - public void backoffRetryUtilityExecuteAsync() throws Exception { + public void backoffRetryUtilityExecuteAsync() { Function, Mono> inBackoffAlternateCallbackMethod = Mockito.mock(Function.class); Function, Mono> parameterizedCallbackMethod = Mockito.mock(Function.class); @@ -171,13 +164,6 @@ public void backoffRetryUtilityExecuteAsync() throws Exception { .withContent(rawJson) .withStatus(200) .build())); - .thenReturn(Mono.just(new StoreResponse( - null, - 200, - new HashMap<>(), - new ByteBufInputStream(buffer, true), - buffer.readableBytes(), - null))); Mono monoResponse = BackoffRetryUtility.executeAsync( parameterizedCallbackMethod, retryPolicy, @@ -357,7 +343,7 @@ public void goneExceptionSuccessScenarioFaultInjection() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneExceptionSuccessScenarioQuery() throws Exception { + public void goneExceptionSuccessScenarioQuery() { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -414,7 +400,7 @@ public void goneExceptionSuccessScenarioQuery() throws Exception { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneExceptionSuccessScenario() throws Exception { + public void goneExceptionSuccessScenario() throws JsonProcessingException { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -502,7 +488,7 @@ public void goneExceptionSuccessScenario() throws Exception { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneAndThrottlingExceptionSuccessScenario() throws Exception { + public void goneAndThrottlingExceptionSuccessScenario() throws JsonProcessingException { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -571,7 +557,7 @@ public void goneAndThrottlingExceptionSuccessScenario() throws Exception { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void goneAndThrottlingExceptionSuccessScenarioQuery() throws Exception { + public void goneAndThrottlingExceptionSuccessScenarioQuery() { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -689,7 +675,7 @@ public void goneExceptionFailureScenario() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void sessionNonAvailableExceptionScenario() throws Exception { + public void sessionNonAvailableExceptionScenario() throws JsonProcessingException { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -806,7 +792,7 @@ public void sessionNonAvailableExceptionFailureScenario() { @Test(groups = {"long-emulator"}, timeOut = TIMEOUT) @SuppressWarnings("unchecked") - public void throttlingExceptionScenario() throws Exception { + public void throttlingExceptionScenario() throws JsonProcessingException { CosmosClient cosmosClient = new CosmosClientBuilder() .endpoint(TestConfigurations.HOST) .key(TestConfigurations.MASTER_KEY) @@ -1022,14 +1008,14 @@ public RetryContext getRetryContext() { } } - private StoreResponse getStoreResponse(int statusCode) throws Exception { + private StoreResponse getStoreResponse(int statusCode) throws JsonProcessingException { StoreResponseBuilder storeResponseBuilder = StoreResponseBuilder.create().withContent(OBJECT_MAPPER.writeValueAsString(getTestPojoObject())) .withStatus(statusCode); return storeResponseBuilder.build(); } - private StoreResponse getQueryStoreResponse() throws Exception { + private StoreResponse getQueryStoreResponse() { String queryContent = "{\n" + " \"_rid\": \"IaBwAPRwFTg=\",\n" + " \"Documents\": [\n" + diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java index 860e337e0bd1..df602b2d287a 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RetryUtilsTest.java @@ -147,7 +147,7 @@ public Mono answer(InvocationOnMock invocation) throws Throwable }); } - private StoreResponse getStoreResponse() throws Exception { + private StoreResponse getStoreResponse() { StoreResponseBuilder storeResponseBuilder = new StoreResponseBuilder().withContent("{\"id\":\"Test content\"}") .withStatus(200); return storeResponseBuilder.build(); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java index bb57c57a6b25..cbc9301142f4 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java @@ -113,7 +113,7 @@ public void setUp() { // todo: fix and revert enabled = false when circuit breaker is enabled @Test(groups = {"unit"}, enabled = true) - public void readMany() throws Exception { + public void readMany() { // setup static method mocks MockedStatic httpClientMock = Mockito.mockStatic(HttpClient.class); @@ -441,15 +441,14 @@ public RetryContext getRetryContext() { }; } - private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String content, Map headers) throws Exception { + private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String content, Map headers) { byte[] blob = content.getBytes(StandardCharsets.UTF_8); StoreResponse storeResponse = new StoreResponse( null, HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length, - null); + blob.length); RxDocumentServiceResponse documentServiceResponse = new RxDocumentServiceResponse(new DiagnosticsClientContext() { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java index 262d7a931414..96fb55bd0922 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java @@ -92,11 +92,11 @@ public StoreResponseBuilder withContent(String content) { return this; } - public StoreResponse build() throws Exception { + public StoreResponse build() { ByteBuf buffer = getUTF8BytesOrNull(content); if (buffer == null) { - return new StoreResponse(null, status, headers, null, 0, null); + return new StoreResponse(null, status, headers, null, 0); } - return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); + return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes()); } } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java index 58e8c34fc79c..019cb9a5b213 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java @@ -33,7 +33,7 @@ public class CosmosBulkItemResponseTest { private static final int TIMEOUT = 40000; @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateAllSetValuesInCosmosBulkItemResponse() throws Exception { + public void validateAllSetValuesInCosmosBulkItemResponse() { List results = new ArrayList<>(); ItemBulkOperation[] arrayOperations = new ItemBulkOperation[1]; @@ -83,8 +83,7 @@ public void validateAllSetValuesInCosmosBulkItemResponse() throws Exception { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length, - null); + blob.length); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -125,7 +124,7 @@ public void validateAllSetValuesInCosmosBulkItemResponse() throws Exception { } @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateEmptyHeaderInCosmosBulkItemResponse() throws Exception { + public void validateEmptyHeaderInCosmosBulkItemResponse() { List results = new ArrayList<>(); ItemBulkOperation[] arrayOperations = new ItemBulkOperation[1]; @@ -167,8 +166,7 @@ public void validateEmptyHeaderInCosmosBulkItemResponse() throws Exception { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length, - null); + blob.length); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java index cff576ae8403..f631d5268f59 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java @@ -32,7 +32,7 @@ public class TransactionalBatchResponseTests { private static final int TIMEOUT = 40000; @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateAllSetValuesInResponse() throws Exception { + public void validateAllSetValuesInResponse() { List results = new ArrayList<>(); ItemBatchOperation[] arrayOperations = new ItemBatchOperation[1]; @@ -79,8 +79,7 @@ public void validateAllSetValuesInResponse() throws Exception { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length, - null); + blob.length); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -106,7 +105,7 @@ public void validateAllSetValuesInResponse() throws Exception { } @Test(groups = {"unit"}, timeOut = TIMEOUT) - public void validateEmptyHeaderInResponse() throws Exception { + public void validateEmptyHeaderInResponse() { List results = new ArrayList<>(); ItemBatchOperation[] arrayOperations = new ItemBatchOperation[1]; @@ -144,8 +143,7 @@ public void validateEmptyHeaderInResponse() throws Exception { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length, - null); + blob.length); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java index ff320b6e682a..372730278a48 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyReaderTest.java @@ -146,7 +146,7 @@ public void replicaSizes(int systemMaxReplicaCount, } @Test(groups = "unit") - public void readAny() throws Exception { + public void readAny() { List secondaries = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); Uri primaryAddress = Uri.create("primary"); AddressSelectorWrapper addressSelectorWrapper = AddressSelectorWrapper.Builder.Simple.create() @@ -231,7 +231,7 @@ public void readAny() throws Exception { } @Test(groups = "unit") - public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWithLowerLSN_FindAnotherReplica() throws Exception { + public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWithLowerLSN_FindAnotherReplica() { long slowReplicaLSN = 651176; String partitionKeyRangeId = "1"; long fasterReplicaLSN = 651177; @@ -355,7 +355,7 @@ public void readSessionConsistency_SomeReplicasLagBehindAndReturningResponseWith * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSession() throws Exception { + public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSession() { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; @@ -443,7 +443,7 @@ public void sessionNotAvailableFromSomeReplicasThrowingNotFound_FindReplicaSatis * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromAllReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSessionOnRetry() throws Exception { + public void sessionNotAvailableFromAllReplicasThrowingNotFound_FindReplicaSatisfyingRequestedSessionOnRetry() { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; @@ -776,7 +776,7 @@ public Object[][] simpleReadStrongArgProvider() { } @Test(groups = "unit", dataProvider = "simpleReadStrongArgProvider") - public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode) throws Exception { + public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode) { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); Uri primaryReplicaURI = Uri.create("primary"); ImmutableList secondaryReplicaURIs = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java index da6e0f791e4c..9ede1c281219 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java @@ -208,12 +208,12 @@ public void run() { } @Test(groups = "unit") - public void getLsnAndGlobalCommittedLsn() throws Exception { + public void getLsnAndGlobalCommittedLsn() { Map headers = new HashMap<>(); headers.put(WFConstants.BackendHeaders.LSN, "3"); headers.put(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, "2"); - StoreResponse sr = new StoreResponse(null, 0, headers, null, 0, null); + StoreResponse sr = new StoreResponse(null, 0, headers, null, 0); Utils.ValueHolder lsn = Utils.ValueHolder.initialize(-2l); Utils.ValueHolder globalCommittedLsn = Utils.ValueHolder.initialize(-2l); ConsistencyWriter.getLsnAndGlobalCommittedLsn(sr, lsn, globalCommittedLsn); @@ -314,7 +314,7 @@ public void storeResponseRecordedOnException(Exception ex, StoreResponse storeRe } @DataProvider(name = "globalStrongArgProvider") - public Object[][] globalStrongArgProvider() throws Exception { + public Object[][] globalStrongArgProvider() { return new Object[][]{ { ConsistencyLevel.SESSION, diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java index 2be5727599ef..8113875998d2 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/EndpointMock.java @@ -56,7 +56,7 @@ public void validate(EndpointMockVerificationBuilder verificationBuilder) { } } - public static Builder.NoSecondaryReplica noSecondaryReplicaBuilder() throws Exception { + public static Builder.NoSecondaryReplica noSecondaryReplicaBuilder() { return new Builder.NoSecondaryReplica(); } @@ -149,9 +149,6 @@ static public class NoSecondaryReplica extends Builder { private StoreResponse readStoreResponse = defaultResponse; private Function1WithCheckedException storeResponseFunc; - public NoSecondaryReplica() throws Exception { - } - public NoSecondaryReplica primaryReplica(Uri primaryReplica) { this.primary = primaryReplica; return this; @@ -220,9 +217,6 @@ static public class NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOn Map> secondaryResponseFunc = new HashMap<>(); - public NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOnPrimary() throws Exception { - } - public NoSecondaryReplica_TwoSecondaryReplicasGoLiveAfterFirstHitOnPrimary primaryReplica(Uri primaryReplica) { this.primary = primaryReplica; diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java index 5ae4d875d640..16a15157118d 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java @@ -28,7 +28,7 @@ public void parsingBytesWithInvalidUT8Bytes() { try { byte[] bytes = hexStringToByteArray(invalidHexString); ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); - JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>(), null); + JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>()); jsonNodeStorePayload.getPayload().toString(); } finally { System.clearProperty("COSMOS.CHARSET_DECODER_ERROR_ACTION_ON_MALFORMED_INPUT"); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java index 1e2046da67c3..8a22146d0bf1 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/QuorumReaderTest.java @@ -56,7 +56,7 @@ public Object[][] simpleReadStrongArgProvider() { }; } - private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) throws Exception { + private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) { StoreResponseBuilder srb = StoreResponseBuilder.create(); if (rc != null) { srb.withRequestCharge(rc); @@ -74,7 +74,7 @@ private StoreResponse storeResponse(Long lsn, Long localLSN, Double rc) throws E } @Test(groups = "unit", dataProvider = "simpleReadStrongArgProvider") - public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode, Long lsn, Long localLSN) throws Exception { + public void basicReadStrong_AllReplicasSameLSN(int replicaCountToRead, ReadMode readMode, Long lsn, Long localLSN) { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); Uri primaryReplicaURI = Uri.create("primary"); ImmutableList secondaryReplicaURIs = ImmutableList.of(Uri.create("secondary1"), Uri.create("secondary2"), Uri.create("secondary3")); @@ -143,7 +143,7 @@ public Object[][] readStrong_RequestBarrierArgProvider() { @Test(groups = "unit", dataProvider = "readStrong_RequestBarrierArgProvider") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_OnlySecondary_RequestBarrier_Success(int numberOfBarrierRequestTillCatchUp) throws Exception { + public void readStrong_OnlySecondary_RequestBarrier_Success(int numberOfBarrierRequestTillCatchUp) { // scenario: we get lsn l1, l2 where l1 > l2 // we do barrier request and send it to all replicas till we have two replicas with at least l1 lsn @@ -288,7 +288,7 @@ public Object[][] readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_ @Test(groups = "unit", dataProvider = "readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_SuccessArgProvider") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Success(int numberOfHeadBarriersWithPrimaryIncludedTillQuorumMet) throws Exception { + public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Success(int numberOfHeadBarriersWithPrimaryIncludedTillQuorumMet) { // scenario: we exhaust all barrier request retries on secondaries // after that we start barrier requests including the primary @@ -449,7 +449,7 @@ public void readStrong_SecondaryReadBarrierExhausted_ReadBarrierOnPrimary_Succes @Test(groups = "unit") @SuppressWarnings({"unchecked", "rawtypes"}) - public void readStrong_QuorumNotSelected_ReadPrimary() throws Exception { + public void readStrong_QuorumNotSelected_ReadPrimary() { // scenario: attempts to read from secondaries, // only one secondary is available so ends in QuorumNotSelected State // reads from Primary and succeeds @@ -554,7 +554,7 @@ public void readStrong_QuorumNotSelected_ReadPrimary() throws Exception { } @DataProvider(name = "readPrimaryArgProvider") - public Object[][] readPrimaryArgProvider() throws Exception { + public Object[][] readPrimaryArgProvider() { return new Object[][]{ // endpoint, verifier for endpoint expected result, verifying the StoreResponse returned { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java index 6697a674d758..dd42eb6eef44 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientGoneForWriteTest.java @@ -41,7 +41,7 @@ public Object[][] goneOnWriteRefreshesAddressesArgProvider() { groups = { "unit" }, dataProvider = "goneOnWriteRefreshesAddressesArgProvider", timeOut = ReplicatedResourceClientPartitionSplitTest.TIMEOUT) - public void gone_RefreshCache_Write(ConsistencyLevel consistencyLevel) throws Exception { + public void gone_RefreshCache_Write(ConsistencyLevel consistencyLevel) { Uri primaryAddress = Uri.create("http://primary/"); List secondaryAddresses = new ArrayList<>(); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java index b2d638267398..3d3a49410362 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientPartitionSplitTest.java @@ -46,7 +46,7 @@ public Object[][] partitionIsSplittingArgProvider() { } @Test(groups = { "unit" }, dataProvider = "partitionIsSplittingArgProvider", timeOut = TIMEOUT) - public void partitionSplit_RefreshCache_Read(ConsistencyLevel consistencyLevel, int partitionIsSplitting) throws Exception { + public void partitionSplit_RefreshCache_Read(ConsistencyLevel consistencyLevel, int partitionIsSplitting) { Uri secondary1AddressBeforeMove = Uri.create("secondary"); Uri secondary1AddressAfterMove = Uri.create("secondaryNew"); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java index 3a075b9d6479..9fce6118c374 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ReplicatedResourceClientRetryWithTest.java @@ -37,7 +37,7 @@ public class ReplicatedResourceClientRetryWithTest { protected static final int TIMEOUT = 120000; @Test(groups = { "unit" }, timeOut = TIMEOUT) - public void retryWith_RetrySucceeds() throws Exception { + public void retryWith_RetrySucceeds() throws URISyntaxException { Uri primaryAddress = Uri.create("http://primary/"); List secondaryAddresses = new ArrayList<>(); secondaryAddresses.add(Uri.create("http://secondary-1/")); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java index 2ef731184b83..c7b33aff323c 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderDotNetTest.java @@ -36,6 +36,7 @@ import org.testng.annotations.Test; import reactor.core.publisher.Mono; +import java.net.URISyntaxException; import java.time.Duration; import java.util.ArrayDeque; import java.util.List; @@ -74,7 +75,7 @@ public void addressCache() { * Tests for TransportClient */ @Test(groups = "unit") - public void transportClient() throws Exception { + public void transportClient() { // create a real document service request RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); @@ -121,7 +122,7 @@ public void transportClient() throws Exception { validator.validate(response); } - private TransportClient getMockTransportClientDuringUpgrade(AddressInformation[] addressInformation) throws Exception { + private TransportClient getMockTransportClientDuringUpgrade(AddressInformation[] addressInformation) { // create objects for all the dependencies of the StoreReader TransportClient mockTransportClient = Mockito.mock(TransportClient.class); @@ -185,7 +186,7 @@ private enum ReadQuorumResultKind { QuorumNotSelected } - private TransportClient getMockTransportClientForGlobalStrongReads(AddressInformation[] addressInformation, ReadQuorumResultKind result) throws Exception { + private TransportClient getMockTransportClientForGlobalStrongReads(AddressInformation[] addressInformation, ReadQuorumResultKind result) { // create objects for all the dependencies of the StoreReader TransportClient mockTransportClient = Mockito.mock(TransportClient.class); @@ -302,7 +303,8 @@ private TransportClient getMockTransportClientForGlobalStrongWrites( int indexOfCaughtUpReplica, boolean undershootGlobalCommittedLsnDuringBarrier, boolean overshootLsnDuringBarrier, - boolean overshootGlobalCommittedLsnDuringBarrier) throws Exception { + boolean overshootGlobalCommittedLsnDuringBarrier) + { TransportClient mockTransportClient = Mockito.mock(TransportClient.class); // create mock store response object @@ -433,7 +435,7 @@ private IAddressResolver getMockAddressCache(AddressInformation[] addressInforma * Tests for {@link StoreReader} */ @Test(groups = "unit") - public void storeReaderBarrier() throws Exception { + public void storeReaderBarrier() { // create a real document service request RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); @@ -560,7 +562,7 @@ public static void validateException(Mono single, */ @Test(groups = "unit", enabled = false) @SuppressWarnings("unchecked") - public void storeClient() throws Exception { + public void storeClient() throws URISyntaxException { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; @@ -648,7 +650,7 @@ public void storeClient() throws Exception { */ @Test(groups = "unit") @SuppressWarnings("unchecked") - public void globalStrongConsistentWrite() throws Exception { + public void globalStrongConsistentWrite() { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Create, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; @@ -735,7 +737,7 @@ public void globalStrongConsistentWrite() throws Exception { */ @Test(groups = "unit", priority = 1) @SuppressWarnings({"unchecked", "rawtypes"}) - public void globalStrongConsistency() throws Exception { + public void globalStrongConsistency() { // create a real document service request (with auth token level = god) RxDocumentServiceRequest entity = RxDocumentServiceRequest.create(mockDiagnosticsClientContext(), OperationType.Read, ResourceType.Document); entity.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey; diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java index f0f4441a7134..27052ab0d7a5 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreReaderTest.java @@ -206,7 +206,7 @@ public void exception(Exception ex, Class klass, int expectedStatusCo * tries others till we find a replica which can support the given session token */ @Test(groups = "unit") - public void sessionNotAvailableFromSomeReplicas_FindReplicaSatisfyingRequestedSession() throws Exception { + public void sessionNotAvailableFromSomeReplicas_FindReplicaSatisfyingRequestedSession() { long slowReplicaLSN = 651175; long globalCommittedLsn = 651174; String partitionKeyRangeId = "73"; @@ -614,7 +614,7 @@ public void readPrimaryAsync_Error() { } @Test(groups = "unit") - public void canParseLongLsn() throws Exception { + public void canParseLongLsn() { TransportClient transportClient = Mockito.mock(TransportClient.class); AddressSelector addressSelector = Mockito.mock(AddressSelector.class); ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); @@ -704,7 +704,7 @@ public void readPrimaryAsync_RetryOnPrimaryReplicaMove(Exception firstExceptionF boolean performLocalRefreshOnGoneException, boolean retryWithForceRefreshExpected, FailureValidator failureFromSingle, - boolean expectedStoreResponseInStoredReadResult) throws Exception { + boolean expectedStoreResponseInStoredReadResult) { ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); StoreResponse response = StoreResponseBuilder.create().build(); @@ -771,7 +771,7 @@ public Object[][] readMultipleReplicasAsyncArgProvider() { } @Test(groups = "unit", dataProvider = "readMultipleReplicasAsyncArgProvider") - public void readMultipleReplicasAsync(boolean includePrimary, int replicaCountToRead, ReadMode readMode) throws Exception { + public void readMultipleReplicasAsync(boolean includePrimary, int replicaCountToRead, ReadMode readMode) { // This adds basic tests for StoreReader.readMultipleReplicasAsync(.) without failure // TODO: add some tests for readMultipleReplicasAsync which mock behaviour of failure of reading from a replica ISessionContainer sessionContainer = Mockito.mock(ISessionContainer.class); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java index c33852b1d48a..ca0ccf1d1340 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java @@ -14,7 +14,7 @@ public class StoreResponseTest { @Test(groups = { "unit" }) - public void stringContent() throws Exception { + public void stringContent() { String content = "I am body"; String jsonContent = "{\"id\":\"" + content + "\"}"; HashMap headerMap = new HashMap<>(); @@ -22,7 +22,7 @@ public void stringContent() throws Exception { headerMap.put("key2", "value2"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java index 8e3092ccd976..18eea0e3cc5e 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java @@ -36,8 +36,8 @@ public StoreResultDiagnosticsSerializerTests() throws IOException { //TODO: add more test cases @Test(groups = "unit") - public void storeResultDiagnosticsSerializerTests() throws Exception { - StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0, null); + public void storeResultDiagnosticsSerializerTests() { + StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0); StoreResult storeResult = new StoreResult( storeResponse, null, diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java index 7b0d938f3014..dc2adf1dce7b 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ChangeFeedQueryImpl.java @@ -192,14 +192,6 @@ private RxDocumentServiceRequest createDocumentServiceRequest() { } private Mono> executeRequestAsync(RxDocumentServiceRequest request) { - - // TODO: Only for testing purposes. Remove post testing. - if (this.options.getResponseInterceptor() != null) { - if (request.requestContext != null) { - request.requestContext.setResponseInterceptor(this.options.getResponseInterceptor()); - } - } - if (this.operationContextAndListener == null) { return handlePerPartitionFailoverPrerequisites(request) .flatMap(client::readFeed) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java index 4375ebadd3d0..2ef17d397b5f 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosChangeFeedRequestOptionsImpl.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; @@ -54,7 +53,6 @@ public final class CosmosChangeFeedRequestOptionsImpl implements OverridableRequ private boolean completeAfterAllCurrentChangesRetrieved; private Long endLSN; private ReadConsistencyStrategy readConsistencyStrategy; - private Callable responseInterceptor; public CosmosChangeFeedRequestOptionsImpl(CosmosChangeFeedRequestOptionsImpl toBeCloned) { if (toBeCloned.continuationState != null) { @@ -82,7 +80,6 @@ public CosmosChangeFeedRequestOptionsImpl(CosmosChangeFeedRequestOptionsImpl toB this.keywordIdentifiers = toBeCloned.keywordIdentifiers; this.completeAfterAllCurrentChangesRetrieved = toBeCloned.completeAfterAllCurrentChangesRetrieved; this.endLSN = toBeCloned.endLSN; - this.responseInterceptor = toBeCloned.responseInterceptor; } public CosmosChangeFeedRequestOptionsImpl( @@ -248,14 +245,6 @@ public void setRequestContinuation(String etag) { this.feedRangeInternal); } - public Callable getResponseInterceptor() { - return this.responseInterceptor; - } - - public void setResponseInterceptor(Callable responseInterceptor) { - this.responseInterceptor = responseInterceptor; - } - @Beta(value = Beta.SinceVersion.V4_12_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) @Deprecated //since = "V4_37_0", forRemoval = true diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java index da6ee93d3687..18da5250c458 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DocumentServiceRequestContext.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -79,7 +78,6 @@ public class DocumentServiceRequestContext implements Cloneable { private volatile PerPartitionCircuitBreakerInfoHolder perPartitionCircuitBreakerInfoHolder; private volatile PerPartitionFailoverInfoHolder perPartitionFailoverInfoHolder; - private volatile Callable responseInterceptor; public DocumentServiceRequestContext() {} @@ -170,8 +168,6 @@ public DocumentServiceRequestContext clone() { context.endToEndOperationLatencyPolicyConfig = this.endToEndOperationLatencyPolicyConfig; context.unavailableRegionsForPartition = this.unavailableRegionsForPartition; context.crossRegionAvailabilityContextForRequest = this.crossRegionAvailabilityContextForRequest; - context.responseInterceptor = this.responseInterceptor; - return context; } @@ -270,13 +266,5 @@ public void setPerPartitionAutomaticFailoverInfoHolder(PartitionLevelFailoverInf this.perPartitionFailoverInfoHolder.setPartitionLevelFailoverInfo(partitionLevelFailoverInfo); } } - - public void setResponseInterceptor(Callable responseInterceptor) { - this.responseInterceptor = responseInterceptor; - } - - public Callable getResponseInterceptor() { - return this.responseInterceptor; - } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java index d5be4e69c442..401904199b24 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java @@ -480,6 +480,7 @@ public static class SubStatusCodes { public static final int SERVER_GENERATED_503 = 21008; public static final int NO_VALID_STORE_RESPONSE = 21009; public static final int SERVER_GENERATED_408 = 21010; + public static final int FAILED_TO_PARSE_SERVER_RESPONSE = 21011; } public static class HeaderValues { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java index 354d41b1c567..906fc518f2f7 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java @@ -205,7 +205,7 @@ public StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf retainedContent) throws Exception { + ByteBuf retainedContent) { checkNotNull(headers, "Argument 'headers' must not be null."); checkNotNull( @@ -233,8 +233,7 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), new ByteBufInputStream(retainedContent, true), - size, - request.requestContext.getResponseInterceptor()); + size); } else { retainedContent.release(); } @@ -244,8 +243,7 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), null, - 0, - request.requestContext.getResponseInterceptor()); + 0); } private Mono query(RxDocumentServiceRequest request) { @@ -414,7 +412,7 @@ private String ensureSlashPrefixed(String path) { */ private Mono toDocumentServiceResponse(Mono httpResponseMono, RxDocumentServiceRequest request, - HttpRequest httpRequest) throws Exception { + HttpRequest httpRequest) { return httpResponseMono .publishOn(CosmosSchedulers.TRANSPORT_RESPONSE_BOUNDED_ELASTIC) @@ -480,139 +478,188 @@ private Mono toDocumentServiceResponse(Mono 0) { + if (leakDetectionDebuggingEnabled) { + content.touch("RxGatewayStoreModel -exception creating StoreResponse - refCnt: " + content.refCnt()); + logger.debug("RxGatewayStoreModel -exception creating StoreResponse - refCnt: {}", content.refCnt()); + } + // Unwrap failed before StoreResponse took ownership -> release our retain + // there could be a race with the doOnDiscard above - so, use safeRelease + ReferenceCountUtil.safeRelease(content); + } + + throw t; } - } + }) + .doOnDiscard(ByteBuf.class, buf -> { + // This handles the case where the retained buffer is discarded after the map operation + // but before unwrapToStoreResponse takes ownership (e.g., during cancellation) + if (buf.refCnt() > 0) { + if (leakDetectionDebuggingEnabled) { + buf.touch("RxGatewayStoreModel - doOnDiscard after map - refCnt: " + buf.refCnt()); + logger.debug("RxGatewayStoreModel - doOnDiscard after map - refCnt: {}", buf.refCnt()); + } + ReferenceCountUtil.safeRelease(buf); + } + }) + .single(); - if (request.requestContext.cosmosDiagnostics != null) { - BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, rsp, globalEndpointManager); - } + }).map(rsp -> { + RxDocumentServiceResponse rxDocumentServiceResponse; + if (httpRequest.reactorNettyRequestRecord() != null) { + rxDocumentServiceResponse = + new RxDocumentServiceResponse(this.clientContext, rsp, + httpRequest.reactorNettyRequestRecord().takeTimelineSnapshot()); - return rsp; - }) - .single(); + } else { + rxDocumentServiceResponse = + new RxDocumentServiceResponse(this.clientContext, rsp); + } + rxDocumentServiceResponse.setCosmosDiagnostics(request.requestContext.cosmosDiagnostics); + return rxDocumentServiceResponse; + }).onErrorResume(throwable -> { + Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable); + if (!(unwrappedException instanceof Exception)) { + // fatal error + logger.error("Unexpected failure " + unwrappedException.getMessage(), unwrappedException); + return Mono.error(unwrappedException); + } - }).map(rsp -> { - RxDocumentServiceResponse rxDocumentServiceResponse; - if (httpRequest.reactorNettyRequestRecord() != null) { - rxDocumentServiceResponse = - new RxDocumentServiceResponse(this.clientContext, rsp, - httpRequest.reactorNettyRequestRecord().takeTimelineSnapshot()); + Exception exception = (Exception) unwrappedException; + CosmosException dce; + if (!(exception instanceof CosmosException)) { + int statusCode = 0; + if (WebExceptionUtility.isNetworkFailure(exception)) { - } else { - rxDocumentServiceResponse = - new RxDocumentServiceResponse(this.clientContext, rsp); - } - rxDocumentServiceResponse.setCosmosDiagnostics(request.requestContext.cosmosDiagnostics); - return rxDocumentServiceResponse; - }).onErrorResume(throwable -> { - Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable); - if (!(unwrappedException instanceof Exception)) { - // fatal error - logger.error("Unexpected failure {}", unwrappedException.getMessage(), unwrappedException); - return Mono.error(unwrappedException); - } + // wrap in CosmosException + logger.error("Network failure", exception); - Exception exception = (Exception) unwrappedException; - CosmosException dce; - if (!(exception instanceof CosmosException)) { - // wrap in CosmosException - logger.error("Network failure", exception); + if (WebExceptionUtility.isReadTimeoutException(exception)) { + statusCode = HttpConstants.StatusCodes.REQUEST_TIMEOUT; + } else { + statusCode = HttpConstants.StatusCodes.SERVICE_UNAVAILABLE; + } + } + + dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, exception); + BridgeInternal.setRequestHeaders(dce, request.getHeaders()); + } else { + logger.error("Non-network failure", exception); + dce = (CosmosException) exception; + } - int statusCode = 0; - if (WebExceptionUtility.isNetworkFailure(exception)) { - if (WebExceptionUtility.isReadTimeoutException(exception)) { - statusCode = HttpConstants.StatusCodes.REQUEST_TIMEOUT; + if (WebExceptionUtility.isNetworkFailure(dce)) { + if (WebExceptionUtility.isReadTimeoutException(dce)) { + BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_READ_TIMEOUT); } else { - statusCode = HttpConstants.StatusCodes.SERVICE_UNAVAILABLE; + BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_UNAVAILABLE); } } - dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, exception); - BridgeInternal.setRequestHeaders(dce, request.getHeaders()); - } else { - dce = (CosmosException) exception; - } + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setRequestUri(dce, Uri.create(httpRequest.uri().toString())); + + if (request.requestContext.cosmosDiagnostics != null) { + if (httpRequest.reactorNettyRequestRecord() != null) { + ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); + BridgeInternal.setRequestTimeline(dce, reactorNettyRequestRecord.takeTimelineSnapshot()); + + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionRuleId( + dce, + request.faultInjectionRequestContext + .getFaultInjectionRuleId(reactorNettyRequestRecord.getTransportRequestId())); - if (WebExceptionUtility.isNetworkFailure(dce)) { - if (WebExceptionUtility.isReadTimeoutException(dce)) { - BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_READ_TIMEOUT); - } else { - BridgeInternal.setSubStatusCode(dce, HttpConstants.SubStatusCodes.GATEWAY_ENDPOINT_UNAVAILABLE); + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionEvaluationResults( + dce, + request.faultInjectionRequestContext + .getFaultInjectionRuleEvaluationResults(reactorNettyRequestRecord.getTransportRequestId())); + } + + BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, dce, globalEndpointManager); } - } - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setRequestUri(dce, Uri.create(httpRequest.uri().toString())); + return Mono.error(dce); + }).doFinally(signalType -> { + + if (signalType != SignalType.CANCEL) { + return; + } - if (request.requestContext.cosmosDiagnostics != null) { if (httpRequest.reactorNettyRequestRecord() != null) { + + OperationCancelledException oce = new OperationCancelledException("", httpRequest.uri()); + ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); - BridgeInternal.setRequestTimeline(dce, reactorNettyRequestRecord.takeTimelineSnapshot()); - - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionRuleId( - dce, - request.faultInjectionRequestContext - .getFaultInjectionRuleId(reactorNettyRequestRecord.getTransportRequestId())); - - ImplementationBridgeHelpers - .CosmosExceptionHelper - .getCosmosExceptionAccessor() - .setFaultInjectionEvaluationResults( - dce, - request.faultInjectionRequestContext - .getFaultInjectionRuleEvaluationResults(reactorNettyRequestRecord.getTransportRequestId())); - } - BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, dce, globalEndpointManager); - } + RequestTimeline requestTimeline = reactorNettyRequestRecord.takeTimelineSnapshot(); + long transportRequestId = reactorNettyRequestRecord.getTransportRequestId(); - return Mono.error(dce); - }).doFinally(signalType -> { + GatewayRequestTimelineContext gatewayRequestTimelineContext = new GatewayRequestTimelineContext(requestTimeline, transportRequestId); - if (signalType != SignalType.CANCEL) { - return; - } + request.requestContext.cancelledGatewayRequestTimelineContexts.add(gatewayRequestTimelineContext); - if (httpRequest.reactorNettyRequestRecord() != null) { + if (request.requestContext.getCrossRegionAvailabilityContext() != null) { - ReactorNettyRequestRecord reactorNettyRequestRecord = httpRequest.reactorNettyRequestRecord(); + CrossRegionAvailabilityContextForRxDocumentServiceRequest availabilityStrategyContextForReq = + request.requestContext.getCrossRegionAvailabilityContext(); - RequestTimeline requestTimeline = reactorNettyRequestRecord.takeTimelineSnapshot(); - long transportRequestId = reactorNettyRequestRecord.getTransportRequestId(); + if (availabilityStrategyContextForReq.getAvailabilityStrategyContext().isAvailabilityStrategyEnabled() && !availabilityStrategyContextForReq.getAvailabilityStrategyContext().isHedgedRequest()) { - GatewayRequestTimelineContext gatewayRequestTimelineContext = new GatewayRequestTimelineContext(requestTimeline, transportRequestId); + BridgeInternal.setRequestTimeline(oce, reactorNettyRequestRecord.takeTimelineSnapshot()); - request.requestContext.cancelledGatewayRequestTimelineContexts.add(gatewayRequestTimelineContext); - } - }); + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionRuleId( + oce, + request.faultInjectionRequestContext + .getFaultInjectionRuleId(transportRequestId)); + + ImplementationBridgeHelpers + .CosmosExceptionHelper + .getCosmosExceptionAccessor() + .setFaultInjectionEvaluationResults( + oce, + request.faultInjectionRequestContext + .getFaultInjectionRuleEvaluationResults(transportRequestId)); + + BridgeInternal.recordGatewayResponse(request.requestContext.cosmosDiagnostics, request, oce, globalEndpointManager); + } + } + } + }); } private void validateOrThrow(RxDocumentServiceRequest request, @@ -627,11 +674,11 @@ private void validateOrThrow(RxDocumentServiceRequest request, ? status.reasonPhrase().replace(" ", "") : ""; - String body = retainedBodyAsByteBuf != null + String body = retainedBodyAsByteBuf.readableBytes() > 0 ? retainedBodyAsByteBuf.toString(StandardCharsets.UTF_8) : null; - retainedBodyAsByteBuf.release(); + ReferenceCountUtil.safeRelease(retainedBodyAsByteBuf); CosmosError cosmosError; cosmosError = (StringUtils.isNotEmpty(body)) ? new CosmosError(body) : new CosmosError(); @@ -736,12 +783,8 @@ public Mono processMessage(RxDocumentServiceRequest r return Mono.error(dce); } - ).flatMap(response -> { - - StringBuilder sb = new StringBuilder(); - sb.append("RxGatewayStoreModel.processMessage:").append(","); - return this.captureSessionTokenAndHandlePartitionSplit(request, response.getResponseHeaders(), sb).then(Mono.just(response)); - } + ).flatMap(response -> + this.captureSessionTokenAndHandlePartitionSplit(request, response.getResponseHeaders()).then(Mono.just(response)) ); } @@ -774,6 +817,11 @@ public void recordOpenConnectionsAndInitCachesStarted(List getDefaultHeaders() { return this.defaultHeaders; } @@ -795,19 +843,13 @@ private void captureSessionToken(RxDocumentServiceRequest request, Map captureSessionTokenAndHandlePartitionSplit(RxDocumentServiceRequest request, - Map responseHeaders, - StringBuilder sb) { - - if (sb != null) { - sb.append("RxGatewayStoreModel.captureSessionTokenAndHandlePartitionSplit:").append(","); - } - + Map responseHeaders) { this.captureSessionToken(request, responseHeaders); if (request.requestContext.resolvedPartitionKeyRange != null && StringUtils.isNotEmpty(request.requestContext.resolvedCollectionRid) && StringUtils.isNotEmpty(responseHeaders.get(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID)) && !responseHeaders.get(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID).equals(request.requestContext.resolvedPartitionKeyRange.getId())) { - return this.partitionKeyRangeCache.refreshAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request.requestContext.resolvedCollectionRid, sb) + return this.partitionKeyRangeCache.refreshAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request.requestContext.resolvedCollectionRid) .flatMap(collectionRoutingMapValueHolder -> Mono.empty()); } return Mono.empty(); @@ -853,11 +895,8 @@ protected Mono resolvePartitionKeyRangeByPkRangeId(RxDocument MetadataDiagnosticsContext metadataCtx = BridgeInternal .getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics); - StringBuilder sb = new StringBuilder(); - sb.append("RxGatewayStoreModel.resolvePartitionKeyRangeByPkRangeId:").append(","); - if (pkRangeId.getCollectionRid() != null) { - return resolvePartitionKeyRangeByPkRangeIdCore(pkRangeId, pkRangeId.getCollectionRid(), metadataCtx, sb); + return resolvePartitionKeyRangeByPkRangeIdCore(pkRangeId, pkRangeId.getCollectionRid(), metadataCtx); } return this.collectionCache.resolveCollectionAsync( @@ -866,15 +905,13 @@ protected Mono resolvePartitionKeyRangeByPkRangeId(RxDocument .flatMap(collectionHolder -> resolvePartitionKeyRangeByPkRangeIdCore( pkRangeId, collectionHolder.v.getResourceId(), - metadataCtx, - sb)); + metadataCtx)); } private Mono resolvePartitionKeyRangeByPkRangeIdCore( PartitionKeyRangeIdentity pkRangeId, String effectiveCollectionRid, - MetadataDiagnosticsContext metadataCtx, - StringBuilder sb) { + MetadataDiagnosticsContext metadataCtx) { Objects.requireNonNull(pkRangeId, "Parameter 'pkRangeId' is required and cannot be null"); Objects.requireNonNull( @@ -886,8 +923,7 @@ private Mono resolvePartitionKeyRangeByPkRangeIdCore( metadataCtx, effectiveCollectionRid, null, - null, - sb + null ) .flatMap(collectionRoutingMapValueHolder -> { @@ -942,15 +978,10 @@ private Mono applySessionToken(RxDocumentServiceRequest request) { } return Mono.empty(); } - - StringBuilder sb = new StringBuilder(); - sb.append("RxGatewayStoreModel.applySessionToken:").append(","); - return partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), collectionValueHolder.v.getResourceId(), null, - null, - sb).flatMap(collectionRoutingMapValueHolder -> { + null).flatMap(collectionRoutingMapValueHolder -> { if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) { //Apply the ambient session. String sessionToken = this.sessionContainer.resolveGlobalSessionToken(request); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java index 94652c68737e..d32e5d901f18 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/ThinClientStoreModel.java @@ -98,7 +98,7 @@ public StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf content) throws Exception { + ByteBuf content) { if (content == null) { return super.unwrapToStoreResponse(endpoint, request, statusCode, headers, Unpooled.EMPTY_BUFFER); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java index 2d10b4be2ec7..ec3956eae88d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/ProcessorSettings.java @@ -6,7 +6,6 @@ import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState; import java.time.Duration; -import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -18,7 +17,6 @@ public class ProcessorSettings { private Duration feedPollDelay; private final ChangeFeedState startState; private final CosmosAsyncContainer collectionSelfLink; - private Callable responseInterceptor; public ProcessorSettings( ChangeFeedState startState, @@ -56,13 +54,4 @@ public ChangeFeedState getStartState() { public CosmosAsyncContainer getCollectionSelfLink() { return this.collectionSelfLink; } - - public Callable getResponseInterceptor() { - return this.responseInterceptor; - } - - public ProcessorSettings withResponseInterceptor(Callable responseInterceptor) { - this.responseInterceptor = responseInterceptor; - return this; - } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java index 57abcd563f65..4fb0c1867b81 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorFactoryImpl.java @@ -76,8 +76,7 @@ public PartitionProcessor create(Lease lease, ChangeFeedObserver observer, Cl ProcessorSettings settings = new ProcessorSettings(state, this.collectionSelfLink) .withFeedPollDelay(this.changeFeedProcessorOptions.getFeedPollDelay()) - .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()) - .withResponseInterceptor(this.changeFeedProcessorOptions.getResponseInterceptor()); + .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()); PartitionCheckpointer checkpointer = new PartitionCheckpointerImpl(this.leaseCheckpointer, lease); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java index bb38517c614f..9923a3f972d6 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java @@ -64,7 +64,6 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile boolean hasServerContinuationTokenChange; private final int maxStreamsConstrainedRetries = 10; private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); - private final AtomicInteger unparseableDocumentRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -89,7 +88,6 @@ public PartitionProcessorImpl(ChangeFeedObserver observer, settings.getStartState(), settings.getMaxItemCount(), this.changeFeedMode); - this.options.setResponseInterceptor(settings.getResponseInterceptor()); this.feedRangeThroughputControlConfigManager = feedRangeThroughputControlConfigManager; this.lastProcessedTime = Instant.now(); @@ -197,9 +195,7 @@ public Mono run(CancellationToken cancellationToken) { this.options.setMaxItemCount(this.settings.getMaxItemCount()); // Reset after successful execution. } - this.options.setResponseInterceptor(settings.getResponseInterceptor()); this.streamsConstrainedRetries.set(0); - this.unparseableDocumentRetries.set(0); }) .onErrorResume(throwable -> { if (throwable instanceof CosmosException) { @@ -287,7 +283,6 @@ public Mono run(CancellationToken cancellationToken) { "Lease with token : " + this.lease.getLeaseToken() + " : Streams constrained exception encountered, will retry. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", clientException); - if (this.options.getMaxItemCount() == -1) { logger.warn( "Lease with token : " + this.lease.getLeaseToken() + " : max item count is set to -1, will retry after setting it to 100. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", @@ -308,46 +303,10 @@ public Mono run(CancellationToken cancellationToken) { return Flux.empty(); } case JSON_PARSING_ERROR: - - if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { - logger.error( - "Lease with token : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); - this.resultException = new RuntimeException(clientException); - return Flux.error(throwable); - } - - if (this.unparseableDocumentRetries.compareAndSet(0, 1)) { - logger.warn( - "Lease with token : " + this.lease.getLeaseToken() + " : Attempting a retry on parsing error.", clientException); - this.options.setMaxItemCount(1); - return Flux.empty(); - } else { - - logger.error("Lease with token : " + this.lease.getLeaseToken() + " : Encountered parsing error which is not recoverable, attempting to skip document", clientException); - - String continuation = CosmosChangeFeedContinuationTokenUtils.extractContinuationTokenFromCosmosException(clientException); - - if (Strings.isNullOrEmpty(continuation)) { - logger.error( - "Lease with token : " + this.lease.getLeaseToken() + ": Unable to extract continuation token post the parsing exception, failing.", - clientException); - this.resultException = new RuntimeException(clientException); - return Flux.error(throwable); - } - - ChangeFeedState continuationState = ChangeFeedState.fromString(continuation); - return this.checkpointer.checkpointPartition(continuationState) - .doOnSuccess(lease1 -> { - logger.info("Lease with token : " + this.lease.getLeaseToken() + " Successfully skipped the unparseable document."); - this.options = - PartitionProcessorHelper.createForProcessingFromContinuation(continuation, this.changeFeedMode); - }) - .doOnError(t -> { - logger.error( - "Failed to checkpoint for lease with token : " + this.lease.getLeaseToken() + " with continuation " + this.lease.getReadableContinuationToken() + " from thread " + Thread.currentThread().getId(), t); - this.resultException = new RuntimeException(t); - }); - } + logger.error( + "Lease with token : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); default: { logger.error( "Lease with token " + this.lease.getLeaseToken() + diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java index ed820f5496b6..0a0209778c74 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorFactoryImpl.java @@ -100,8 +100,7 @@ public PartitionProcessor create(Lease lease, ChangeFeedObserver obser ProcessorSettings settings = new ProcessorSettings(state, this.collectionSelfLink) .withFeedPollDelay(this.changeFeedProcessorOptions.getFeedPollDelay()) - .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()) - .withResponseInterceptor(this.changeFeedProcessorOptions.getResponseInterceptor()); + .withMaxItemCount(this.changeFeedProcessorOptions.getMaxItemCount()); PartitionCheckpointer checkpointer = new PartitionCheckpointerImpl(this.leaseCheckpointer, lease); return new PartitionProcessorImpl( diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java index 1ed99ca2ff5e..aaafeeb0fbbf 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java @@ -64,7 +64,6 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile boolean hasServerContinuationTokenChange; private final int maxStreamsConstrainedRetries = 10; private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); - private final AtomicInteger unparseableDocumentRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -84,7 +83,6 @@ public PartitionProcessorImpl(ChangeFeedObserver observer, ChangeFeedState state = settings.getStartState(); this.options = ModelBridgeInternal.createChangeFeedRequestOptionsForChangeFeedState(state); this.options.setMaxItemCount(settings.getMaxItemCount()); - this.options.setResponseInterceptor(settings.getResponseInterceptor()); // For pk version, merge is not support, exclude it from the capabilities header ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.getCosmosChangeFeedRequestOptionsAccessor() @@ -204,9 +202,7 @@ public Mono run(CancellationToken cancellationToken) { this.options.setMaxItemCount(this.settings.getMaxItemCount()); // Reset after successful execution. } - this.options.setResponseInterceptor(settings.getResponseInterceptor()); this.streamsConstrainedRetries.set(0); - this.unparseableDocumentRetries.set(0); }) .onErrorResume(throwable -> { if (throwable instanceof CosmosException) { @@ -312,47 +308,10 @@ public Mono run(CancellationToken cancellationToken) { return Flux.empty(); } case JSON_PARSING_ERROR: - - if (!Configs.isChangeFeedProcessorMalformedResponseRecoveryEnabled()) { - logger.error( - "Partition : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); - this.resultException = new RuntimeException(clientException); - return Flux.error(throwable); - } - - if (this.unparseableDocumentRetries.compareAndSet(0, 1)) { - logger.warn( - "Partition : " + this.lease.getLeaseToken() + " : Attempting a retry on parsing error.", clientException); - this.options.setMaxItemCount(1); - return Flux.empty(); - } else { - - logger.error("Partition : " + this.lease.getLeaseToken() + " : Encountered parsing error which is not recoverable, attempting to skip document", clientException); - - String continuation = CosmosChangeFeedContinuationTokenUtils.extractContinuationTokenFromCosmosException(clientException); - - if (Strings.isNullOrEmpty(continuation)) { - logger.error( - "Partition : " + this.lease.getLeaseToken() + ": Unable to extract continuation token post the parsing exception, failing.", - clientException); - this.resultException = new RuntimeException(clientException); - return Flux.error(throwable); - } - - ChangeFeedState continuationState = ChangeFeedState.fromString(continuation); - return this.checkpointer.checkpointPartition(continuationState) - .doOnSuccess(lease1 -> { - logger.info("Partition : " + this.lease.getLeaseToken() + " Successfully skipped the unparseable document."); - this.options = - CosmosChangeFeedRequestOptions - .createForProcessingFromContinuation(continuation); - }) - .doOnError(t -> { - logger.error( - "Failed to checkpoint for Partition : " + this.lease.getLeaseToken() + " with continuation " + this.lease.getReadableContinuationToken() + " from thread " + Thread.currentThread().getId(), t); - this.resultException = new RuntimeException(t); - }); - } + logger.error( + "Partition : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + this.resultException = new RuntimeException(clientException); + return Flux.error(throwable); default: { logger.error("Unrecognized Cosmos exception returned error code " + docDbError, clientException); this.resultException = new RuntimeException(clientException); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java index 6d98eec86baa..2a18b76e343a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java @@ -18,8 +18,6 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.concurrent.Callable; import java.util.Base64; import java.util.Map; @@ -29,16 +27,9 @@ public class JsonNodeStorePayload implements StorePayload { private final int responsePayloadSize; private final JsonNode jsonValue; - public JsonNodeStorePayload( - ByteBufInputStream bufferStream, - int readableBytes, - Map responseHeaders, - Callable interceptor) throws Exception { - public JsonNodeStorePayload(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { if (readableBytes > 0) { this.responsePayloadSize = readableBytes; - this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders, interceptor); this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders); } else { this.responsePayloadSize = 0; @@ -46,52 +37,16 @@ public JsonNodeStorePayload(ByteBufInputStream bufferStream, int readableBytes, } } - private static JsonNode fromJson( - ByteBufInputStream bufferStream, - int readableBytes, - Map responseHeaders, - Callable interceptor) throws Exception { - private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { byte[] bytes = new byte[readableBytes]; try { - - if (interceptor != null) { - interceptor.call(); - } - bufferStream.read(bytes); - return Utils.getSimpleObjectMapper().readTree(bytes); } catch (IOException e) { - - logger.error("Unable to parse JSON, fallback to use customized charset decoder.", e); - if (fallbackCharsetDecoder != null) { - - if (interceptor != null) { - interceptor.call(); - } - - return fromJsonWithFallbackCharsetDecoder(bytes, responseHeaders, interceptor); logger.warn("Unable to parse JSON, fallback to use customized charset decoder.", e); return fromJsonWithFallbackCharsetDecoder(bytes, responseHeaders); } else { - Exception nestedException = new IllegalStateException("Unable to parse JSON.", e); - - if (e instanceof StreamConstraintsException) { - throw Utils.createCosmosException( - HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR, - HttpConstants.SubStatusCodes.JACKSON_STREAMS_CONSTRAINED, - nestedException, - responseHeaders); - } - - throw Utils.createCosmosException( - HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR, - HttpConstants.SubStatusCodes.FAILED_TO_PARSE_SERVER_RESPONSE, - nestedException, - responseHeaders); String baseErrorMessage = "Failed to parse JSON document. No fallback charset decoder configured."; @@ -106,22 +61,15 @@ private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBy throw Utils.createCosmosException( HttpConstants.StatusCodes.BADREQUEST, - HttpConstants.SubStatusCodes.FAILED_TO_PARSE_SERVER_RESPONSE, + evaluateSubStatusCode(e), innerException, responseHeaders); } } } - private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map responseHeaders, Callable interceptor) throws Exception { private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map responseHeaders) { try { - - if (interceptor != null) { - // Log if a fallback charset decoder is enabled for this request - interceptor.call(); - } - String sanitizedJson = fallbackCharsetDecoder.decode(ByteBuffer.wrap(bytes)).toString(); return Utils.getSimpleObjectMapper().readTree(sanitizedJson); } catch (IOException e) { @@ -136,33 +84,15 @@ private static JsonNode fromJsonWithFallbackCharsetDecoder(byte[] bytes, Map toStoreResponse(HttpResponse httpClientResponse, Stri // transforms to Mono int size = 0; if (byteBufContent == null || (size = byteBufContent.readableBytes()) == 0) { - try { - return new StoreResponse( - endpoint, - httpClientResponse.statusCode(), - HttpUtils.unescape(httpResponseHeaders.toMap()), - null, - 0, - null); - } catch (Exception e) { - throw reactor.core.Exceptions.propagate(e); - } - } - - try { return new StoreResponse( endpoint, httpClientResponse.statusCode(), HttpUtils.unescape(httpResponseHeaders.toMap()), - new ByteBufInputStream(byteBufContent, true), - size, - null); - } catch (Exception e) { - throw reactor.core.Exceptions.propagate(e); + null, + 0); } + + return new StoreResponse( + endpoint, + httpClientResponse.statusCode(), + HttpUtils.unescape(httpResponseHeaders.toMap()), + new ByteBufInputStream(byteBufContent, true), + size); }); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java index c8b10eb3c17d..22c5ed8624b6 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/RntbdTransportClient.java @@ -16,7 +16,6 @@ import com.azure.cosmos.implementation.OperationCancelledException; import com.azure.cosmos.implementation.RequestTimeline; import com.azure.cosmos.implementation.RxDocumentServiceRequest; -import com.azure.cosmos.implementation.RxDocumentServiceResponse; import com.azure.cosmos.implementation.UserAgentContainer; import com.azure.cosmos.implementation.clienttelemetry.ClientTelemetry; import com.azure.cosmos.implementation.clienttelemetry.CosmosMeterOptions; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java index 5fdc21a79314..a4bab8f3edbe 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java @@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.JsonNode; import io.netty.buffer.ByteBufInputStream; import io.netty.util.IllegalReferenceCountException; -import io.netty.util.IllegalReferenceCountException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -53,8 +51,7 @@ public StoreResponse( int status, Map headerMap, ByteBufInputStream contentStream, - int responsePayloadLength, - Callable responseInterceptor) throws Exception { + int responsePayloadLength) { checkArgument((contentStream == null) == (responsePayloadLength == 0), "Parameter 'contentStream' must be consistent with 'responsePayloadLength'."); @@ -74,14 +71,8 @@ public StoreResponse( replicaStatusList = new HashMap<>(); if (contentStream != null) { try { -<<<<<<< HEAD this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap); } finally { -======= - this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap, responseInterceptor); - } - finally { ->>>>>>> 8f6eb3273f6 (Add a way to skip records in Change Feed Processor. (#26)) try { contentStream.close(); } catch (Throwable e) { @@ -213,35 +204,6 @@ public String getHeaderValue(String attribute) { return null; } - //NOTE: only used for testing purpose to change the response header value - private void setHeaderValue(String headerName, String value) { - if (this.responseHeaderValues == null || this.responseHeaderNames.length != this.responseHeaderValues.length) { - return; - } - - for (int i = 0; i < responseHeaderNames.length; i++) { - if (responseHeaderNames[i].equalsIgnoreCase(headerName)) { - responseHeaderValues[i] = value; - break; - } - } - this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap); - } finally { - this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap, responseInterceptor); - } - finally { - } catch (Throwable e) { - if (!(e instanceof IllegalReferenceCountException)) { - // Log as warning instead of debug to make ByteBuf leak issues more visible - logger.warn("Failed to close content stream. This may cause a Netty ByteBuf leak.", e); - } - // NOTE: Only used in local test through transport client interceptor - public void setGCLSN(long gclsn) { - this.setHeaderValue(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, String.valueOf(gclsn)); - } - - } - //NOTE: only used for testing purpose to change the response header value private void setHeaderValue(String headerName, String value) { if (this.responseHeaderValues == null || this.responseHeaderNames.length != this.responseHeaderValues.length) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java index efe1e9a93dac..d7e8ccb56421 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java @@ -71,7 +71,6 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -968,7 +967,7 @@ private void completeAllPendingRequestsExceptionally( * @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager request manager} belongs. * @param response the {@link RntbdResponse message} received. */ - private void messageReceived(final ChannelHandlerContext context, final RntbdResponse response) throws Exception { + private void messageReceived(final ChannelHandlerContext context, final RntbdResponse response) { final Long transportRequestId = response.getTransportRequestId(); @@ -1002,8 +1001,6 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes final String requestUriAsString = requestRecord.args().physicalAddressUri() != null ? requestRecord.args().physicalAddressUri().getURI().toString() : null; - Callable responseInterceptor = serviceRequest.requestContext.getResponseInterceptor(); - if ((HttpResponseStatus.OK.code() <= statusCode && statusCode < HttpResponseStatus.MULTIPLE_CHOICES.code()) || statusCode == HttpResponseStatus.NOT_MODIFIED.code()) { @@ -1011,7 +1008,7 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes if (rntbdCtx == null) { throw new IllegalStateException("Expecting non-null rntbd context."); } - final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString, responseInterceptor); + final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString); if (this.serverErrorInjector != null) { Consumer completeWithInjectedDelayConsumer = diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java index 6dbe28c32b60..1fb8b43d3fe8 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java @@ -25,7 +25,6 @@ import java.time.Instant; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdConstants.RntbdResponseHeader; @@ -348,7 +347,7 @@ public static RntbdResponse decode(final ByteBuf in) { return new RntbdResponse(in.readSlice(end - start), frame, headers, content); } - StoreResponse toStoreResponse(final String serverVersion, final String endpoint, Callable responseInterceptor) throws Exception { + StoreResponse toStoreResponse(final String serverVersion, final String endpoint) { checkNotNull(serverVersion, "Argument 'serverVersion' must not be null."); @@ -360,8 +359,7 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint, this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), null, - 0, - responseInterceptor); + 0); } return new StoreResponse( @@ -369,8 +367,7 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint, this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), new ByteBufInputStream(this.content.retain(), true), - length, - responseInterceptor); + length); } // endregion diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java index 451c1d73dda9..58a2dc95cf8d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpTransportSerializer.java @@ -14,5 +14,5 @@ StoreResponse unwrapToStoreResponse( RxDocumentServiceRequest request, int statusCode, HttpHeaders headers, - ByteBuf retainedContent) throws Exception; + ByteBuf retainedContent); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java index c1830b58ced4..ed94c7232a6a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ChangeFeedFetcher.java @@ -4,12 +4,9 @@ package com.azure.cosmos.implementation.query; import com.azure.cosmos.BridgeInternal; -import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DiagnosticsClientContext; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; -import com.azure.cosmos.implementation.HttpConstants; -import com.azure.cosmos.implementation.guava25.base.Strings; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.ImplementationBridgeHelpers; import com.azure.cosmos.implementation.GoneException; @@ -169,22 +166,6 @@ protected String applyServerResponseContinuation( serverContinuationToken, request, shouldMoveToNextTokenOnETagReplace); } - @Override - protected String applyServerResponseContinuation(String serverContinuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { - - if (!Strings.isNullOrEmpty(serverContinuationToken)) { - String replacedContinuation = this.changeFeedState.applyServerResponseContinuation( - serverContinuationToken, request, false); - - Map responseHeaders = cosmosException.getResponseHeaders(); - responseHeaders.put(HttpConstants.HttpHeaders.E_TAG, replacedContinuation); - - return replacedContinuation; - } - - return com.azure.cosmos.implementation.Strings.Emtpy; - } - @Override protected boolean isFullyDrained(boolean isChangeFeed, FeedResponse response) { if (ModelBridgeInternal.noChanges(response)) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java index c0ab24caf37a..7089f50d0a8b 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/Fetcher.java @@ -4,14 +4,10 @@ package com.azure.cosmos.implementation.query; import com.azure.cosmos.CosmosDiagnostics; -import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.CrossRegionAvailabilityContextForRxDocumentServiceRequest; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.FeedOperationContextForCircuitBreaker; import com.azure.cosmos.implementation.GlobalEndpointManager; -import com.azure.cosmos.implementation.HttpConstants; -import com.azure.cosmos.implementation.OperationType; -import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.ImplementationBridgeHelpers; import com.azure.cosmos.implementation.RxDocumentServiceRequest; @@ -25,7 +21,6 @@ import reactor.core.publisher.SignalType; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -106,11 +101,6 @@ protected abstract String applyServerResponseContinuation( RxDocumentServiceRequest request, FeedResponse response); - protected abstract String applyServerResponseContinuation( - String serverContinuationToken, - RxDocumentServiceRequest request, - CosmosException cosmosException); - protected abstract boolean isFullyDrained(boolean isChangeFeed, FeedResponse response); protected abstract String getContinuationForLogging(); @@ -155,10 +145,6 @@ private void updateState(FeedResponse response, RxDocumentServiceRequest requ } } - private void updateStateForException(String continuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { - this.applyServerResponseContinuation(continuationToken, request, cosmosException); - } - protected void reEnableShouldFetchMoreForRetry() { this.shouldFetchMore.set(true); } @@ -212,24 +198,7 @@ private Mono> nextPage(RxDocumentServiceRequest request) { } } }) - .doOnError(throwable -> { - - if (throwable instanceof CosmosException) { - CosmosException cosmosException = (CosmosException) throwable; - Map responseHeaders = cosmosException.getResponseHeaders(); - - if (responseHeaders != null) { - String responseContinuation = - OperationType.ReadFeed.equals(request.getOperationType()) && ResourceType.Document.equals(request.getResourceType()) ? - responseHeaders.get(HttpConstants.HttpHeaders.E_TAG) : - responseHeaders.get(HttpConstants.HttpHeaders.CONTINUATION); - - this.updateStateForException(responseContinuation, request, cosmosException); - } - } - - completed.set(true); - }) + .doOnError(throwable -> completed.set(true)) .doFinally(signalType -> { // If the signal type is not cancel(which means success or error), we do not need to tracking the diagnostics here // as the downstream will capture it diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java index 22c13ca560c6..37153b6cbbf1 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationFetcherImpl.java @@ -5,7 +5,6 @@ import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosDiagnostics; -import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; @@ -54,14 +53,6 @@ protected String applyServerResponseContinuation( return this.continuationToken = serverContinuationToken; } - @Override - protected String applyServerResponseContinuation( - String serverContinuationToken, - RxDocumentServiceRequest request, - CosmosException cosmosException) { - return serverContinuationToken; - } - @Override protected boolean isFullyDrained(boolean isChangeFeed, FeedResponse response) { // if token is null or if change feed query and no changes then done diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java index 1e03232c427a..863bdaa2b4ac 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/ServerSideOnlyContinuationNonDocumentFetcherImpl.java @@ -5,13 +5,11 @@ import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.CosmosDiagnostics; -import com.azure.cosmos.CosmosException; import com.azure.cosmos.implementation.DocumentClientRetryPolicy; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.ObservableHelper; import com.azure.cosmos.implementation.RxDocumentClientImpl; import com.azure.cosmos.implementation.RxDocumentServiceRequest; -import com.azure.cosmos.implementation.Strings; import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; import com.azure.cosmos.implementation.spark.OperationContextAndListenerTuple; @@ -70,11 +68,6 @@ protected String applyServerResponseContinuation( return this.continuationToken = serverContinuationToken; } - @Override - protected String applyServerResponseContinuation(String serverContinuationToken, RxDocumentServiceRequest request, CosmosException cosmosException) { - return Strings.Emtpy; - } - @Override public Mono> nextPage() { DocumentClientRetryPolicy retryPolicy = this.retryPolicySupplier.get(); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java index 6e437608e28d..7e3c0d6e2f50 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/ChangeFeedProcessorOptions.java @@ -9,7 +9,6 @@ import java.time.Duration; import java.time.Instant; -import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -56,7 +55,6 @@ public final class ChangeFeedProcessorOptions { private Scheduler scheduler; private ThroughputControlGroupConfig feedPollThroughputControlGroupConfig; - private Callable responseInterceptor; /** * Instantiates a new Change feed processor options. @@ -431,26 +429,4 @@ public ChangeFeedProcessorOptions setLeaseVerificationEnabledOnRestart(boolean e public boolean isLeaseVerificationEnabledOnRestart() { return this.leaseVerificationOnRestartEnabled; } - - /** - * Sets a callback to be invoked after every response from the service to the Change Feed requests. - * This can be used for logging or metrics gathering purposes. - * - * @param responseInterceptor a callback to be invoked after every response from the service to the Change Feed requests. - * @return the {@link ChangeFeedProcessorOptions}. - */ - public ChangeFeedProcessorOptions setResponseInterceptor(Callable responseInterceptor) { - this.responseInterceptor = responseInterceptor; - return this; - } - - /** - * Gets a callback to be invoked after every response from the service to the Change Feed requests. - * This can be used for logging or metrics gathering purposes. - * - * @return a callback to be invoked after every response from the service to the Change Feed requests. - */ - public Callable getResponseInterceptor() { - return this.responseInterceptor; - } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java index b0a6a28c9b9c..800c4987f1f3 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosChangeFeedRequestOptions.java @@ -3,6 +3,7 @@ package com.azure.cosmos.models; +import com.azure.cosmos.ConsistencyLevel; import com.azure.cosmos.CosmosDiagnosticsThresholds; import com.azure.cosmos.CosmosItemSerializer; import com.azure.cosmos.ReadConsistencyStrategy; @@ -30,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull; @@ -609,26 +609,6 @@ public Set getKeywordIdentifiers() { return this.actualRequestOptions.getKeywordIdentifiers(); } - /** - * Sets the response interceptor to be called after receiving the response - * for the request. - * - * @param responseInterceptor the response interceptor. - */ - public void setResponseInterceptor(Callable responseInterceptor) { - this.actualRequestOptions.setResponseInterceptor(responseInterceptor); - } - - /** - * Gets the response interceptor to be called after receiving the response - * for the request. - * - * @return the response interceptor. - */ - public Callable getResponseInterceptor() { - return this.actualRequestOptions.getResponseInterceptor(); - } - void setOperationContextAndListenerTuple(OperationContextAndListenerTuple operationContextAndListenerTuple) { this.actualRequestOptions.setOperationContextAndListenerTuple(operationContextAndListenerTuple); } From c443880c5f9a701c32d8e33434c15013d9287ebf Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Fri, 21 Nov 2025 13:30:35 -0500 Subject: [PATCH 3/6] Modify StreamsConstraintException retries. --- .../changefeed/common/ExceptionClassifier.java | 2 +- .../changefeed/epkversion/PartitionProcessorImpl.java | 8 ++++---- .../changefeed/pkversion/PartitionProcessorImpl.java | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java index 69f9c61b92fc..760b867b4ff6 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/common/ExceptionClassifier.java @@ -28,7 +28,7 @@ public static StatusCodeErrorType classifyClientException(CosmosException client return StatusCodeErrorType.PARTITION_NOT_FOUND; } - if (clientException.getStatusCode() == HttpConstants.StatusCodes.INTERNAL_SERVER_ERROR) { + if (clientException.getStatusCode() == HttpConstants.StatusCodes.BADREQUEST) { if (subStatusCode == HttpConstants.SubStatusCodes.JACKSON_STREAMS_CONSTRAINED) { return StatusCodeErrorType.JACKSON_STREAMS_CONSTRAINED; } else if (subStatusCode == HttpConstants.SubStatusCodes.FAILED_TO_PARSE_SERVER_RESPONSE) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java index 9923a3f972d6..f865b44ac9c8 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/epkversion/PartitionProcessorImpl.java @@ -62,7 +62,7 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile String lastServerContinuationToken; private volatile boolean hasMoreResults; private volatile boolean hasServerContinuationTokenChange; - private final int maxStreamsConstrainedRetries = 10; + private final int maxStreamsConstrainedRetries = 1; private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -287,7 +287,7 @@ public Mono run(CancellationToken cancellationToken) { logger.warn( "Lease with token : " + this.lease.getLeaseToken() + " : max item count is set to -1, will retry after setting it to 100. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", clientException); - this.options.setMaxItemCount(100); + this.options.setMaxItemCount(1); return Flux.empty(); } @@ -298,13 +298,13 @@ public Mono run(CancellationToken cancellationToken) { return Flux.error(throwable); } - this.options.setMaxItemCount(this.options.getMaxItemCount() / 2); + this.options.setMaxItemCount(1); logger.warn("Lease with token : " + this.lease.getLeaseToken() + " Reducing maxItemCount, new value: " + this.options.getMaxItemCount()); return Flux.empty(); } case JSON_PARSING_ERROR: logger.error( - "Lease with token : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + "Lease with token : " + this.lease.getLeaseToken() + ": Parsing error encountered.", clientException); this.resultException = new RuntimeException(clientException); return Flux.error(throwable); default: { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java index aaafeeb0fbbf..322fa5cd6adb 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/changefeed/pkversion/PartitionProcessorImpl.java @@ -62,7 +62,7 @@ class PartitionProcessorImpl implements PartitionProcessor { private volatile String lastServerContinuationToken; private volatile boolean hasMoreResults; private volatile boolean hasServerContinuationTokenChange; - private final int maxStreamsConstrainedRetries = 10; + private final int maxStreamsConstrainedRetries = 1; private final AtomicInteger streamsConstrainedRetries = new AtomicInteger(0); private final FeedRangeThroughputControlConfigManager feedRangeThroughputControlConfigManager; private volatile Instant lastProcessedTime; @@ -292,7 +292,7 @@ public Mono run(CancellationToken cancellationToken) { logger.warn( "Partition : " + this.lease.getLeaseToken() + " : max item count is set to -1, will retry after setting it to 100. " + "retryCount " + retryCount + " of " + this.maxStreamsConstrainedRetries + " retries.", clientException); - this.options.setMaxItemCount(100); + this.options.setMaxItemCount(1); return Flux.empty(); } @@ -303,13 +303,13 @@ public Mono run(CancellationToken cancellationToken) { return Flux.error(throwable); } - this.options.setMaxItemCount(this.options.getMaxItemCount() / 2); + this.options.setMaxItemCount(1); logger.warn("Reducing maxItemCount, new value: " + this.options.getMaxItemCount()); return Flux.empty(); } case JSON_PARSING_ERROR: logger.error( - "Partition : " + this.lease.getLeaseToken() + ": Parsing error encountered. To enable automatic retries, please set the + " + Configs.CHANGE_FEED_PROCESSOR_MALFORMED_RESPONSE_RECOVERY_ENABLED + " configuration to 'true'. Failing.", clientException); + "Partition : " + this.lease.getLeaseToken() + ": Parsing error encountered.", clientException); this.resultException = new RuntimeException(clientException); return Flux.error(throwable); default: { From dbe2ee7a3127ad9608ba216512ae48083a579d85 Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Fri, 21 Nov 2025 13:40:46 -0500 Subject: [PATCH 4/6] Cleaning tests. --- .../directconnectivity/StoreResponseTest.java | 4 +- .../IncrementalChangeFeedProcessorTest.java | 278 +++++++----------- .../IncrementalChangeFeedProcessorTest.java | 259 +--------------- 3 files changed, 115 insertions(+), 426 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java index ca0ccf1d1340..1e6c6cc147f8 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java @@ -30,7 +30,7 @@ public void stringContent() { } @Test(groups = { "unit" }) - public void headerNamesAreCaseInsensitive() throws Exception { + public void headerNamesAreCaseInsensitive() { String content = "I am body"; String jsonContent = "{\"id\":\"" + content + "\"}"; HashMap headerMap = new HashMap<>(); @@ -39,7 +39,7 @@ public void headerNamesAreCaseInsensitive() throws Exception { headerMap.put("KEY3", "value3"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java index 63c553db38c7..a543309ef37a 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java @@ -53,10 +53,7 @@ import com.azure.cosmos.test.faultinjection.FaultInjectionRuleBuilder; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorResult; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorType; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.NullNode; @@ -92,7 +89,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -136,58 +132,6 @@ public Object[] getCurrentStateTestConfigs() { }; } - @DataProvider(name = "parsingErrorArgProvider") - public static Object[][] parsingErrorArgProvider() { - return new Object[][]{ - { - new StreamConstraintsException("A StreamConstraintsException has been hit!"), - -1 - }, - { - new JacksonException("A JacksonException has been hit!") { - @Override - public JsonLocation getLocation() { - return null; - } - - @Override - public String getOriginalMessage() { - return ""; - } - - @Override - public Object getProcessor() { - return null; - } - }, - -1 - }, - { - new StreamConstraintsException("A StreamConstraintsException has been hit!"), - 10 - }, - { - new JacksonException("A JacksonException has been hit!") { - @Override - public JsonLocation getLocation() { - return null; - } - - @Override - public String getOriginalMessage() { - return ""; - } - - @Override - public Object getProcessor() { - return null; - } - }, - 10 - } - }; - } - @DataProvider public Object[] incrementalChangeFeedModeStartFromSetting() { return new Object[] { true, false }; @@ -312,6 +256,107 @@ public void readFeedDocumentsStartFromCustomDate() throws InterruptedException { } } + @Test(groups = { "query" }, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) + public void verifyConsistentTimestamps() throws InterruptedException { + CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); + CosmosAsyncContainer createdLeaseCollection = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); + + try { + List createdDocuments = new ArrayList<>(); + Map receivedDocuments = new ConcurrentHashMap<>(); + ChangeFeedProcessor changeFeedProcessor = new ChangeFeedProcessorBuilder() + .hostName(hostName) + .handleLatestVersionChanges((List docs) -> { + logger.info("START processing from thread {}", Thread.currentThread().getId()); + for (ChangeFeedProcessorItem item : docs) { + processItem(item, receivedDocuments); + } + logger.info("END processing from thread {}", Thread.currentThread().getId()); + }) + .feedContainer(createdFeedCollection) + .leaseContainer(createdLeaseCollection) + .options(new ChangeFeedProcessorOptions() + .setLeaseRenewInterval(Duration.ofSeconds(20)) + .setLeaseAcquireInterval(Duration.ofSeconds(10)) + .setLeaseExpirationInterval(Duration.ofSeconds(30)) + .setFeedPollDelay(Duration.ofSeconds(1)) + .setLeasePrefix("TEST") + .setMaxItemCount(10) + .setMinScaleCount(1) + .setMaxScaleCount(3) + ) + .buildChangeFeedProcessor(); + AtomicReference initialTimestamp = new AtomicReference<>(); + + startChangeFeedProcessor(changeFeedProcessor); + + assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); + + safeStopChangeFeedProcessor(changeFeedProcessor); + + createdLeaseCollection.queryItems("SELECT * FROM c", new CosmosQueryRequestOptions(), JsonNode.class) + .byPage() + .flatMap(feedResponse -> { + for (JsonNode item : feedResponse.getResults()) { + if (item.get("timestamp") != null) { + initialTimestamp.set(item.get("timestamp").asText()); + logger.info("Found timestamp: %s", initialTimestamp); + } + } + return Mono.empty(); + }).blockLast(); + + + + startChangeFeedProcessor(changeFeedProcessor); + + // create a gap between previously written documents + Thread.sleep(3000); + + // process some documents after the CFP is started and stopped + setupReadFeedDocuments(createdDocuments, createdFeedCollection, FEED_COUNT); + + // Wait for the feed processor to receive and process the documents. + waitToReceiveDocuments(receivedDocuments, 40 * CHANGE_FEED_PROCESSOR_TIMEOUT, FEED_COUNT); + safeStopChangeFeedProcessor(changeFeedProcessor); + + logger.info("After processing documents"); + + AtomicReference newTimestamp = new AtomicReference<>(); + + + createdLeaseCollection.queryItems("SELECT * FROM c", new CosmosQueryRequestOptions(), JsonNode.class) + .byPage() + .flatMap(feedResponse -> { + for (JsonNode item : feedResponse.getResults()) { + if (item.get("timestamp") != null) { + newTimestamp.set(item.get("timestamp").asText()); + logger.info("Found timestamp: %s", newTimestamp); + } + } + return Mono.empty(); + }).blockLast(); + + + assertThat(newTimestamp.get()).doesNotContain("[UTC]"); + assertThat(initialTimestamp.get()).doesNotContain("[UTC]"); + + for (InternalObjectNode item : createdDocuments) { + assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); + } + + // Wait for the feed processor to shutdown. + Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); + + } finally { + safeDeleteCollection(createdFeedCollection); + safeDeleteCollection(createdLeaseCollection); + + // Allow some time for the collections to be deleted before exiting. + Thread.sleep(500); + } + } + @Test(groups = {"multi-master"}, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) public void readFeedDocumentsStartFromCustomDateForMultiWrite_test() throws InterruptedException { CosmosClientBuilder clientBuilder = getClientBuilder(); @@ -1984,127 +2029,6 @@ public void incrementalChangeFeedModeToFullFidelityChangeFeedMode(boolean isStar } } - @Test(groups = { "long-emulator" }, dataProvider = "parsingErrorArgProvider", timeOut = 12 * TIMEOUT) - public void readFeedDocumentsStartFromBeginningWithJsonProcessingErrors(Exception exceptionType, int maxItemCount) throws InterruptedException { - - if (BridgeInternal.getContextClient(this.client).getConnectionPolicy().getConnectionMode() - == ConnectionMode.DIRECT) { - throw new SkipException("The fix is only for Gateway mode. Direct mode fix will be followed up on."); - } - - CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); - CosmosAsyncContainer createdLeaseCollection = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); - Callable responseInterceptor = null; - - // Response Interceptor Properties - AtomicInteger pageCounter = new AtomicInteger(0); - AtomicInteger exceptionCounter = new AtomicInteger(0); - AtomicInteger totalExceptionHits = new AtomicInteger(0); - - if (exceptionType instanceof StreamConstraintsException) { - responseInterceptor = () -> { - // inject when certain no. of pages have been processed - if (pageCounter.get() > 1 && pageCounter.get() % 2 == 0) { - if (exceptionCounter.get() < 3) { - exceptionCounter.incrementAndGet(); - totalExceptionHits.incrementAndGet(); - throw exceptionType; - } else { - exceptionCounter.set(0); - } - } - - return null; - }; - } else { - responseInterceptor = () -> { - // inject when certain no. of pages have been processed - if (pageCounter.get() > 1 && pageCounter.get() % 2 == 0) { - if (exceptionCounter.get() < 2) { - exceptionCounter.incrementAndGet(); - totalExceptionHits.incrementAndGet(); - throw exceptionType; - } else { - exceptionCounter.set(0); - } - } - - return null; - }; - } - - try { - List createdDocuments = new ArrayList<>(); - Map receivedDocuments = new ConcurrentHashMap<>(); - setupReadFeedDocuments(createdDocuments, createdFeedCollection, 100); - - changeFeedProcessor = new ChangeFeedProcessorBuilder() - .hostName(hostName) - .handleLatestVersionChanges((docs) -> { - logger.info("START processing from thread {}", Thread.currentThread().getId()); - for (ChangeFeedProcessorItem item : docs) { - processItem(item, receivedDocuments); - } - - pageCounter.incrementAndGet(); - logger.info("END processing from thread {}", Thread.currentThread().getId()); - }) - .feedContainer(createdFeedCollection) - .leaseContainer(createdLeaseCollection) - .options(new ChangeFeedProcessorOptions() - .setLeaseRenewInterval(Duration.ofSeconds(20)) - .setLeaseAcquireInterval(Duration.ofSeconds(10)) - .setLeaseExpirationInterval(Duration.ofSeconds(30)) - .setFeedPollDelay(Duration.ofSeconds(2)) - .setLeasePrefix("TEST") - .setMaxItemCount(maxItemCount) - .setStartFromBeginning(true) - .setMaxScaleCount(0) // unlimited - .setResponseInterceptor(responseInterceptor) - ) - .buildChangeFeedProcessor(); - - startChangeFeedProcessor(changeFeedProcessor); - - for (int i = 0; i < 5; i++) { - setupReadFeedDocuments(createdDocuments, createdFeedCollection, 100); - Thread.sleep(10_000); - } - - // Wait for the feed processor to receive and process the documents. - Thread.sleep(20 * CHANGE_FEED_PROCESSOR_TIMEOUT); - - assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); - - safeStopChangeFeedProcessor(changeFeedProcessor); - - // Wait for the feed processor to shutdown. - Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); - - logger.warn("Total documents received: {}", receivedDocuments.size()); - logger.warn("Total created documents : {}", createdDocuments.size()); - logger.warn("Total exception hits : {}", totalExceptionHits.get()); - - assertThat(totalExceptionHits.get()).isGreaterThan(0); - - if (exceptionType instanceof StreamConstraintsException) { - assertThat(receivedDocuments.size()).isEqualTo(createdDocuments.size()); - - for (InternalObjectNode item : createdDocuments) { - assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); - } - } else { - assertThat(receivedDocuments.size()).isEqualTo(createdDocuments.size() - totalExceptionHits.get() / 2); - } - } finally { - safeDeleteCollection(createdFeedCollection); - safeDeleteCollection(createdLeaseCollection); - - // Allow some time for the collections to be deleted before exiting. - Thread.sleep(500); - } - } - @Test(groups = { "cfp-split" }, timeOut = 160 * CHANGE_FEED_PROCESSOR_TIMEOUT, retryAnalyzer = SplitTestsRetryAnalyzer.class) public void verifyLeasesOnRestart_AfterSplit() throws InterruptedException { CosmosAsyncContainer createdFeedCollectionForSplit = createFeedCollection(FEED_COLLECTION_THROUGHPUT); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java index 97223ede3db7..6d3559406329 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java @@ -2,7 +2,6 @@ // Licensed under the MIT License. package com.azure.cosmos.rx.changefeed.pkversion; -import com.azure.cosmos.BridgeInternal; import com.azure.cosmos.ChangeFeedProcessor; import com.azure.cosmos.ChangeFeedProcessorBuilder; import com.azure.cosmos.ConsistencyLevel; @@ -11,7 +10,6 @@ import com.azure.cosmos.CosmosAsyncDatabase; import com.azure.cosmos.CosmosClientBuilder; import com.azure.cosmos.CosmosEndToEndOperationLatencyPolicyConfigBuilder; -import com.azure.cosmos.FlakyTestRetryAnalyzer; import com.azure.cosmos.CosmosNettyLeakDetectorFactory; import com.azure.cosmos.SplitTestsRetryAnalyzer; import com.azure.cosmos.SplitTimeoutException; @@ -41,19 +39,7 @@ import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.models.ThroughputResponse; import com.azure.cosmos.rx.TestSuiteBase; -import com.azure.cosmos.test.faultinjection.CosmosFaultInjectionHelper; -import com.azure.cosmos.test.faultinjection.FaultInjectionCondition; -import com.azure.cosmos.test.faultinjection.FaultInjectionConditionBuilder; -import com.azure.cosmos.test.faultinjection.FaultInjectionOperationType; -import com.azure.cosmos.test.faultinjection.FaultInjectionResultBuilders; -import com.azure.cosmos.test.faultinjection.FaultInjectionRule; -import com.azure.cosmos.test.faultinjection.FaultInjectionRuleBuilder; -import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorResult; -import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorType; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.RandomStringUtils; @@ -86,7 +72,6 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -101,8 +86,7 @@ public class IncrementalChangeFeedProcessorTest extends TestSuiteBase { private static final ObjectMapper OBJECT_MAPPER = Utils.getSimpleObjectMapper(); private CosmosAsyncDatabase createdDatabase; -// private final String databaseId = "testdb1"; -// private final String hostName = "TestHost1"; + private final String hostName = RandomStringUtils.randomAlphabetic(6); private final int FEED_COUNT = 10; private final int CHANGE_FEED_PROCESSOR_TIMEOUT = 5000; @@ -127,58 +111,6 @@ public static Object[][] throughputControlConfigArgProvider() { }; } - @DataProvider(name = "parsingErrorArgProvider") - public static Object[][] parsingErrorArgProvider() { - return new Object[][]{ - { - new StreamConstraintsException("A StreamConstraintsException has been hit!"), - -1 - }, - { - new JacksonException("A JacksonException has been hit!") { - @Override - public JsonLocation getLocation() { - return null; - } - - @Override - public String getOriginalMessage() { - return ""; - } - - @Override - public Object getProcessor() { - return null; - } - }, - -1 - }, - { - new StreamConstraintsException("A StreamConstraintsException has been hit!"), - 10 - }, - { - new JacksonException("A JacksonException has been hit!") { - @Override - public JsonLocation getLocation() { - return null; - } - - @Override - public String getOriginalMessage() { - return ""; - } - - @Override - public Object getProcessor() { - return null; - } - }, - 10 - } - }; - } - @DataProvider public Object[] incrementalChangeFeedModeStartFromSetting() { return new Object[] { true, false }; @@ -251,89 +183,6 @@ public void readFeedDocumentsStartFromBeginning() throws InterruptedException { } } - @Test(groups = { "long-emulator" }, enabled = false, timeOut = 12 * TIMEOUT) - public void readFeedDocumentsStartFromBeginningWithPkRangeThrottles() throws InterruptedException { - CosmosAsyncContainer createdFeedCollection - = client.getDatabase("TestDb").getContainer("TestFeedContainer"); - CosmosAsyncContainer createdLeaseCollection - = client.getDatabase("TestDb").getContainer("TestLeaseContainer"); - - try { - List createdDocuments = new ArrayList<>(); - Map receivedDocuments = new ConcurrentHashMap<>(); - - changeFeedProcessor = new ChangeFeedProcessorBuilder() - .hostName(hostName) - .handleChanges(changeFeedProcessorHandler(receivedDocuments)) - .feedContainer(createdFeedCollection) - .leaseContainer(createdLeaseCollection) - .options(new ChangeFeedProcessorOptions() - .setLeaseRenewInterval(Duration.ofSeconds(20)) - .setLeaseAcquireInterval(Duration.ofSeconds(10)) - .setLeaseExpirationInterval(Duration.ofSeconds(30)) - .setFeedPollDelay(Duration.ofSeconds(2)) - .setLeasePrefix("TEST") - .setMaxItemCount(10) - .setStartFromBeginning(true) - .setMaxScaleCount(0) // unlimited - ) - .buildChangeFeedProcessor(); - - FeedRange fullRange = FeedRange.forFullRange(); - - FaultInjectionServerErrorResult pkRangeThrottledError = FaultInjectionResultBuilders - .getResultBuilder(FaultInjectionServerErrorType.TOO_MANY_REQUEST) - .suppressServiceRequests(false) - .build(); - - FaultInjectionCondition condition = new FaultInjectionConditionBuilder() - .operationType(FaultInjectionOperationType.METADATA_REQUEST_PARTITION_KEY_RANGES) - .build(); - - String pkRangeThrottledId = String.format("pkrange-throttled-error-%s", UUID.randomUUID()); - - FaultInjectionRuleBuilder ruleBuilder = new FaultInjectionRuleBuilder(pkRangeThrottledId) - .condition(condition) - .result(pkRangeThrottledError); - - FaultInjectionRule pkRangeThrottledFIErrorRule = ruleBuilder.build(); - - CosmosFaultInjectionHelper.configureFaultInjectionRules(createdFeedCollection, Arrays.asList(pkRangeThrottledFIErrorRule)).block(); - - startChangeFeedProcessor(changeFeedProcessor); - - // Wait for the feed processor to receive and process the documents. - Thread.sleep(20000 * CHANGE_FEED_PROCESSOR_TIMEOUT); - - assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); - - safeStopChangeFeedProcessor(changeFeedProcessor); - for (InternalObjectNode item : createdDocuments) { - assertThat(receivedDocuments.containsKey(item.getId())).as("Document with getId: " + item.getId()).isTrue(); - } - - // Wait for the feed processor to shutdown. - Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); - - // restart the change feed processor and verify it can start successfully - startChangeFeedProcessor(changeFeedProcessor); - - // Wait for the feed processor to start - Thread.sleep(2 * CHANGE_FEED_PROCESSOR_TIMEOUT); - - assertThat(changeFeedProcessor.isStarted()).as("Change Feed Processor instance is running").isTrue(); - - safeStopChangeFeedProcessor(changeFeedProcessor); - // Wait for the feed processor to shutdown. - Thread.sleep(CHANGE_FEED_PROCESSOR_TIMEOUT); - } finally { -// safeDeleteCollection(createdFeedCollection); -// safeDeleteCollection(createdLeaseCollection); - - // Allow some time for the collections to be deleted before exiting. Thread.sleep(500); - } - } - @Test(groups = { "long-emulator" }, timeOut = 50 * CHANGE_FEED_PROCESSOR_TIMEOUT) public void readFeedDocumentsStartFromCustomDate() throws InterruptedException { CosmosAsyncContainer createdFeedCollection = createFeedCollection(FEED_COLLECTION_THROUGHPUT); @@ -1531,61 +1380,6 @@ public void readFeedDocumentsAfterSplit(boolean throughputControlConfigEnabled) } } - @Test(groups = { "query" }, timeOut = 20 * TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class) - public void readPartitionKeyRangesWithSuppressedPageSize() { - - AsyncDocumentClient contextClient = BridgeInternal.getContextClient(this.client); - CosmosAsyncContainer asyncContainer = getSharedMultiPartitionCosmosContainer(this.client); - String containerLink = BridgeInternal.getLink(asyncContainer); - - try { - System.setProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE", "1"); - contextClient - .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) - .doOnNext(feedResponse -> { - logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); - assertThat(feedResponse.getResults().size() <= 1).isTrue(); - }) - .blockLast(); - - } catch (RuntimeException e) { - fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); - } finally { - System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); - } - - try { - System.setProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE", "-1"); - contextClient - .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) - .doOnNext(feedResponse -> { - logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); - assertThat(feedResponse.getResults().size() > 1).isTrue(); - }) - .blockLast(); - - } catch (RuntimeException e) { - fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); - } finally { - System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); - } - - try { - contextClient - .readPartitionKeyRanges(containerLink, (CosmosQueryRequestOptions) null) - .doOnNext(feedResponse -> { - logger.info("[PAGE SIZE CHECK]: feedResponse size: {}", feedResponse.getResults().size()); - assertThat(feedResponse.getResults().size() > 1).isTrue(); - }) - .blockLast(); - - } catch (RuntimeException e) { - fail("readPartitionKeyRangesWithSuppressedPageSize failed which was expected to succeed!", e); - } finally { - System.clearProperty("COSMOS.MAX_ITEM_COUNT_READ_FEED_PK_RANGE"); - } - } - @Test(groups = { "cfp-split" }, timeOut = 160 * CHANGE_FEED_PROCESSOR_TIMEOUT, retryAnalyzer = SplitTestsRetryAnalyzer.class) public void readFeedDocumentsAfterSplit_maxScaleCount() throws InterruptedException { CosmosAsyncContainer createdFeedCollectionForSplit = createFeedCollection(FEED_COLLECTION_THROUGHPUT); @@ -2413,53 +2207,24 @@ private Consumer> leasesChangeFeedProcessorHandler(LeaseStateMoni log.info("LEASES processing from thread {}", Thread.currentThread().getId()); }; } + @BeforeMethod(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) + public void beforeMethod() throws Exception { + // add a cool off time + CosmosNettyLeakDetectorFactory.resetIdentifiedLeaks(); + } - @BeforeMethod(groups = { "long-emulator", "cfp-split" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) - public void beforeMethod() { - } + @AfterMethod(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) + public void afterMethod() throws Exception { + logger.info("captureNettyLeaks: {}", captureNettyLeaks()); + } - @BeforeClass(groups = { "long-emulator", "cfp-split" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) + @BeforeClass(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) public void before_ChangeFeedProcessorTest() { client = getClientBuilder().buildAsyncClient(); createdDatabase = getSharedCosmosDatabase(client); - - // Following is code that when enabled locally it allows for a predicted database/collection name that can be - // checked in the Azure Portal -// try { -// client.getDatabase(databaseId).read() -// .map(cosmosDatabaseResponse -> cosmosDatabaseResponse.getDatabase()) -// .flatMap(database -> database.delete()) -// .onErrorResume(throwable -> { -// if (throwable instanceof com.azure.cosmos.CosmosClientException) { -// com.azure.cosmos.CosmosClientException clientException = (com.azure.cosmos.CosmosClientException) throwable; -// if (clientException.getStatusCode() == 404) { -// return Mono.empty(); -// } -// } -// return Mono.error(throwable); -// }).block(); -// Thread.sleep(500); -// } catch (Exception e){ -// log.warn("Database delete", e); -// } -// createdDatabase = createDatabase(client, databaseId); - } - - @AfterMethod(groups = { "long-emulator", "cfp-split" }, timeOut = 3 * SHUTDOWN_TIMEOUT, alwaysRun = true) - public void afterMethod() { } - - @AfterClass(groups = { "long-emulator", "cfp-split" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) + @AfterClass(groups = { "long-emulator", "cfp-split", "multi-master", "query" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) public void afterClass() { -// try { -// client.readAllDatabases() -// .flatMap(cosmosDatabaseProperties -> { -// CosmosAsyncDatabase cosmosDatabase = client.getDatabase(cosmosDatabaseProperties.getId()); -// return cosmosDatabase.delete(); -// }).blockLast(); -// Thread.sleep(500); -// } catch (Exception e){ } - safeClose(client); } From cf267fad0a6a15e7ecb6446b8125af1b2cd11479 Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Fri, 21 Nov 2025 13:41:35 -0500 Subject: [PATCH 5/6] Cleaning up. --- .../CosmosChangeFeedContinuationTokenUtils.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java index 3a64cf2f948c..0121d731db2f 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/CosmosChangeFeedContinuationTokenUtils.java @@ -3,8 +3,6 @@ package com.azure.cosmos.util; -import com.azure.cosmos.CosmosException; -import com.azure.cosmos.implementation.HttpConstants; import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState; import com.azure.cosmos.implementation.changefeed.common.ChangeFeedStateV1; import com.azure.cosmos.implementation.feedranges.FeedRangeContinuation; @@ -110,16 +108,4 @@ private static List> getSegmentedTokens( return segmentedTokens; } - - public static String extractContinuationTokenFromCosmosException(CosmosException ce) { - Map responseHeaders = ce.getResponseHeaders(); - return getValueOrNull(responseHeaders, HttpConstants.HttpHeaders.E_TAG); - } - - private static String getValueOrNull(Map map, String key) { - if (map != null) { - return map.get(key); - } - return null; - } } From 9746f022ae8a6f2204bde02e8c04a282c3b96bfb Mon Sep 17 00:00:00 2001 From: Abhijeet Mohanty Date: Fri, 21 Nov 2025 17:29:32 -0500 Subject: [PATCH 6/6] Adding tests. --- .../RxDocumentClientImplTest.java | 4 +- .../implementation/StoreResponseBuilder.java | 4 +- .../batch/CosmosBulkItemResponseTest.java | 8 +- .../TransactionalBatchResponseTests.java | 8 +- .../ConsistencyWriterTest.java | 2 +- .../JsonNodeStorePayloadTests.java | 2 +- .../directconnectivity/StoreResponseTest.java | 4 +- ...StoreResultDiagnosticsSerializerTests.java | 2 +- .../IncrementalChangeFeedProcessorTest.java | 77 +++++++++++- .../IncrementalChangeFeedProcessorTest.java | 74 ++++++++++++ .../faultinjection/JsonParseInterceptor.java | 38 ++++++ .../JsonParseInterceptorHelper.java | 59 ++++++++++ .../implementation/RxGatewayStoreModel.java | 10 +- .../JsonNodeStorePayload.java | 110 +++++++++++++++++- .../directconnectivity/ResponseUtils.java | 9 +- .../directconnectivity/StoreResponse.java | 17 +-- .../rntbd/RntbdRequestManager.java | 7 +- .../rntbd/RntbdResponse.java | 12 +- 18 files changed, 410 insertions(+), 37 deletions(-) create mode 100644 sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptor.java create mode 100644 sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptorHelper.java diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java index cbc9301142f4..6d342d43eeea 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/RxDocumentClientImplTest.java @@ -448,7 +448,9 @@ private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String co HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null, + null); RxDocumentServiceResponse documentServiceResponse = new RxDocumentServiceResponse(new DiagnosticsClientContext() { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java index 96fb55bd0922..500d2a67b0d9 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/StoreResponseBuilder.java @@ -95,8 +95,8 @@ public StoreResponseBuilder withContent(String content) { public StoreResponse build() { ByteBuf buffer = getUTF8BytesOrNull(content); if (buffer == null) { - return new StoreResponse(null, status, headers, null, 0); + return new StoreResponse(null, status, headers, null, 0, null, null); } - return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null, null); } } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java index 019cb9a5b213..31e86ac065ee 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/CosmosBulkItemResponseTest.java @@ -83,7 +83,9 @@ public void validateAllSetValuesInCosmosBulkItemResponse() { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -166,7 +168,9 @@ public void validateEmptyHeaderInCosmosBulkItemResponse() { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java index f631d5268f59..dfaa4adaf527 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/batch/TransactionalBatchResponseTests.java @@ -79,7 +79,9 @@ public void validateAllSetValuesInResponse() { HttpResponseStatus.OK.code(), headers, new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), @@ -143,7 +145,9 @@ public void validateEmptyHeaderInResponse() { HttpResponseStatus.OK.code(), new HashMap<>(), new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true), - blob.length); + blob.length, + null, + null); CosmosBatchResponse batchResponse = BatchResponseParser.fromDocumentServiceResponse( new RxDocumentServiceResponse(null, storeResponse), diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java index 9ede1c281219..39214066a398 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/ConsistencyWriterTest.java @@ -213,7 +213,7 @@ public void getLsnAndGlobalCommittedLsn() { headers.put(WFConstants.BackendHeaders.LSN, "3"); headers.put(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, "2"); - StoreResponse sr = new StoreResponse(null, 0, headers, null, 0); + StoreResponse sr = new StoreResponse(null, 0, headers, null, 0, null, null); Utils.ValueHolder lsn = Utils.ValueHolder.initialize(-2l); Utils.ValueHolder globalCommittedLsn = Utils.ValueHolder.initialize(-2l); ConsistencyWriter.getLsnAndGlobalCommittedLsn(sr, lsn, globalCommittedLsn); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java index 16a15157118d..afe80f8364f6 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayloadTests.java @@ -28,7 +28,7 @@ public void parsingBytesWithInvalidUT8Bytes() { try { byte[] bytes = hexStringToByteArray(invalidHexString); ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); - JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>()); + JsonNodeStorePayload jsonNodeStorePayload = new JsonNodeStorePayload(new ByteBufInputStream(byteBuf), bytes.length, new HashMap<>(), null, null); jsonNodeStorePayload.getPayload().toString(); } finally { System.clearProperty("COSMOS.CHARSET_DECODER_ERROR_ACTION_ON_MALFORMED_INPUT"); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java index 1e6c6cc147f8..b2166cee2851 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResponseTest.java @@ -22,7 +22,7 @@ public void stringContent() { headerMap.put("key2", "value2"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null, null); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); @@ -39,7 +39,7 @@ public void headerNamesAreCaseInsensitive() { headerMap.put("KEY3", "value3"); ByteBuf buffer = getUTF8BytesOrNull(jsonContent); - StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes()); + StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes(), null, null); assertThat(sp.getStatus()).isEqualTo(200); assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content); diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java index 18eea0e3cc5e..1db78a492e3b 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/directconnectivity/StoreResultDiagnosticsSerializerTests.java @@ -37,7 +37,7 @@ public StoreResultDiagnosticsSerializerTests() throws IOException { //TODO: add more test cases @Test(groups = "unit") public void storeResultDiagnosticsSerializerTests() { - StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0); + StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0, null, null); StoreResult storeResult = new StoreResult( storeResponse, null, diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java index a543309ef37a..ad604b1ae501 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/epkversion/IncrementalChangeFeedProcessorTest.java @@ -20,6 +20,8 @@ import com.azure.cosmos.implementation.DatabaseAccountLocation; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.InternalObjectNode; +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.RxDocumentClientImpl; import com.azure.cosmos.implementation.TestConfigurations; import com.azure.cosmos.implementation.Utils; @@ -53,6 +55,7 @@ import com.azure.cosmos.test.faultinjection.FaultInjectionRuleBuilder; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorResult; import com.azure.cosmos.test.faultinjection.FaultInjectionServerErrorType; +import com.azure.cosmos.test.faultinjection.JsonParseInterceptorHelper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -2101,6 +2104,72 @@ public void verifyLeasesOnRestart_AfterSplit() throws InterruptedException { } } + /** + * Tests that ChangeFeedProcessor gracefully handles StreamConstraintsException during JSON parsing. + * + *

This test uses a GLOBAL interceptor to inject a StreamConstraintsException on the first + * JSON parse operation across all threads (including CFP worker threads from thread pools).

+ * + *

IMPORTANT: This test should NOT run in parallel with other tests that use + * the JSON parse interceptor, as the interceptor is global and would cause interference.

+ * + *

Expected behavior: The ChangeFeedProcessor should retry with reduced maxItemCount and + * eventually process all documents successfully.

+ */ + @Test(groups = { "long-emulator" }, timeOut = 50000, singleThreaded = true) + public void changeFeedProcessorHandlesStreamConstraintsException() throws Exception { + CosmosAsyncContainer feedContainer = createFeedCollection(FEED_COLLECTION_THROUGHPUT); + CosmosAsyncContainer leaseContainer = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); + + try { + List createdDocuments = new ArrayList<>(); + Map receivedDocuments = new ConcurrentHashMap<>(); + + // Create documents + setupReadFeedDocuments(createdDocuments, feedContainer, FEED_COUNT); + + // Set up GLOBAL interceptor to throw StreamConstraintsException once + // Works across all threads (main thread + CFP worker threads from thread pools) + // NOTE: Test marked as singleThreaded to prevent interference with parallel tests + try (AutoCloseable interceptor = JsonParseInterceptorHelper.injectStreamConstraintsExceptionOnce(OperationType.ReadFeed, ResourceType.Document)) { + + ChangeFeedProcessorOptions options = new ChangeFeedProcessorOptions(); + options.setMaxItemCount(100); + options.setStartFromBeginning(true); + + ChangeFeedProcessor changeFeedProcessor = new ChangeFeedProcessorBuilder() + .hostName(hostName) + .feedContainer(feedContainer) + .leaseContainer(leaseContainer) + .options(options) + .handleLatestVersionChanges(changeFeedProcessorHandler(receivedDocuments)) + .buildChangeFeedProcessor(); + + try { + startChangeFeedProcessor(changeFeedProcessor); + + // Wait for documents to be processed + Thread.sleep(10000); + + safeStopChangeFeedProcessor(changeFeedProcessor); + + // Verify all documents were processed despite the StreamConstraintsException + assertThat(receivedDocuments.size()).isEqualTo(FEED_COUNT); + + logger.info("Successfully processed {} documents after handling StreamConstraintsException", + receivedDocuments.size()); + + } finally { + Thread.sleep(2000); + } + } + + } finally { + safeDeleteCollection(feedContainer); + safeDeleteCollection(leaseContainer); + } + } + private void startChangeFeedProcessor(ChangeFeedProcessor changeFeedProcessor) { changeFeedProcessor .start() @@ -2321,21 +2390,21 @@ private Consumer> leasesChangeFeedProcessorHandler }; } - @BeforeMethod(groups = { "query", "cfp-split" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) + @BeforeMethod(groups = { "query", "cfp-split", "long-emulator" }, timeOut = 2 * SETUP_TIMEOUT, alwaysRun = true) public void beforeMethod() { } - @BeforeClass(groups = { "query", "cfp-split" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) + @BeforeClass(groups = { "query", "cfp-split", "long-emulator" }, timeOut = SETUP_TIMEOUT, alwaysRun = true) public void before_ChangeFeedProcessorTest() { client = getClientBuilder().buildAsyncClient(); createdDatabase = getSharedCosmosDatabase(client); } - @AfterMethod(groups = { "query", "cfp-split" }, timeOut = 3 * SHUTDOWN_TIMEOUT, alwaysRun = true) + @AfterMethod(groups = { "query", "cfp-split", "long-emulator" }, timeOut = 3 * SHUTDOWN_TIMEOUT, alwaysRun = true) public void afterMethod() { } - @AfterClass(groups = { "query", "cfp-split" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) + @AfterClass(groups = { "query", "cfp-split", "long-emulator" }, timeOut = 2 * SHUTDOWN_TIMEOUT, alwaysRun = true) public void afterClass() { safeClose(client); } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java index 6d3559406329..8f718534e305 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/changefeed/pkversion/IncrementalChangeFeedProcessorTest.java @@ -20,6 +20,8 @@ import com.azure.cosmos.implementation.DatabaseAccountLocation; import com.azure.cosmos.implementation.GlobalEndpointManager; import com.azure.cosmos.implementation.InternalObjectNode; +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.RxDocumentClientImpl; import com.azure.cosmos.implementation.Utils; import com.azure.cosmos.implementation.changefeed.pkversion.ServiceItemLease; @@ -39,6 +41,7 @@ import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.models.ThroughputResponse; import com.azure.cosmos.rx.TestSuiteBase; +import com.azure.cosmos.test.faultinjection.JsonParseInterceptorHelper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -1761,6 +1764,77 @@ public void readFeedDocumentsWithThroughputControl() throws InterruptedException } } + /** + * Tests that ChangeFeedProcessor gracefully handles StreamConstraintsException during JSON parsing. + * + *

This test uses a GLOBAL interceptor to inject a StreamConstraintsException on the first + * JSON parse operation across all threads (including CFP worker threads from thread pools).

+ * + *

IMPORTANT: This test should NOT run in parallel with other tests that use + * the JSON parse interceptor, as the interceptor is global and would cause interference.

+ * + *

Expected behavior: The ChangeFeedProcessor should retry with reduced maxItemCount and + * eventually process all documents successfully.

+ */ + @Test(groups = { "long-emulator" }, timeOut = 50000, singleThreaded = true) + public void changeFeedProcessorHandlesStreamConstraintsException() throws Exception { + CosmosAsyncContainer feedContainer = createFeedCollection(FEED_COLLECTION_THROUGHPUT); + CosmosAsyncContainer leaseContainer = createLeaseCollection(LEASE_COLLECTION_THROUGHPUT); + + try { + List createdDocuments = new ArrayList<>(); + Map receivedDocuments = new ConcurrentHashMap<>(); + + // Create documents + setupReadFeedDocuments(createdDocuments, feedContainer, FEED_COUNT); + + // Set up GLOBAL interceptor to throw StreamConstraintsException once + // Works across all threads (main thread + CFP worker threads from thread pools) + // NOTE: Test marked as singleThreaded to prevent interference with parallel tests + try (AutoCloseable interceptor = JsonParseInterceptorHelper.injectStreamConstraintsExceptionOnce(OperationType.ReadFeed, ResourceType.Document)) { + + ChangeFeedProcessorOptions options = new ChangeFeedProcessorOptions(); + options.setMaxItemCount(100); + options.setStartFromBeginning(true); + + ChangeFeedProcessor changeFeedProcessor = new ChangeFeedProcessorBuilder() + .hostName(hostName) + .feedContainer(feedContainer) + .leaseContainer(leaseContainer) + .options(options) + .handleChanges(docs -> { + logger.info("Received {} documents", docs.size()); + for (JsonNode doc : docs) { + receivedDocuments.put(doc.get("id").asText(), doc); + } + }) + .buildChangeFeedProcessor(); + + try { + startChangeFeedProcessor(changeFeedProcessor); + + // Wait for documents to be processed + Thread.sleep(10000); + + safeStopChangeFeedProcessor(changeFeedProcessor); + + // Verify all documents were processed despite the StreamConstraintsException + assertThat(receivedDocuments.size()).isEqualTo(FEED_COUNT); + + logger.info("Successfully processed {} documents after handling StreamConstraintsException", + receivedDocuments.size()); + + } finally { + Thread.sleep(2000); + } + } + + } finally { + safeDeleteCollection(feedContainer); + safeDeleteCollection(leaseContainer); + } + } + // Steps followed in this test // 1. Ingest 10 documents into the feed container. // 2. Start the LatestVersion / INCREMENTAL ChangeFeedProcessor with either startFromBeginning set to 'true' or 'false'. diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptor.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptor.java new file mode 100644 index 000000000000..3d7dcba11933 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptor.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.test.faultinjection; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.Map; + +/** + * Functional interface for intercepting JSON parsing in tests. + * This allows injecting faults during JSON deserialization for testing purposes. + */ +@FunctionalInterface +public interface JsonParseInterceptor { + /** + * Intercepts JSON parsing to allow fault injection. + * + * @param bytes the byte array containing JSON + * @param responseHeaders the response headers + * @param defaultParser the default parsing logic to delegate to + * @return the parsed JsonNode + * @throws IOException if parsing fails or fault is injected + */ + JsonNode intercept( + byte[] bytes, + Map responseHeaders, + DefaultJsonParser defaultParser + ) throws IOException; + + /** + * Functional interface for the default JSON parsing logic. + */ + @FunctionalInterface + interface DefaultJsonParser { + JsonNode parse(byte[] bytes, Map responseHeaders) throws IOException; + } +} diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptorHelper.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptorHelper.java new file mode 100644 index 000000000000..0dfed4c0e956 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/test/faultinjection/JsonParseInterceptorHelper.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.test.faultinjection; + +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; +import com.azure.cosmos.implementation.directconnectivity.JsonNodeStorePayload; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper class to inject faults into JSON parsing for testing purposes. + * This should only be used in test scenarios. + * + *

IMPORTANT: This uses a GLOBAL interceptor that affects all threads. + * Tests using these methods should NOT run in parallel with other tests using interceptors. + * Use TestNG's singleThreaded configuration or similar mechanisms for test isolation.

+ */ +public class JsonParseInterceptorHelper { + private static final Logger logger = LoggerFactory.getLogger(JsonParseInterceptorHelper.class); + + /** + * Sets an interceptor that throws StreamConstraintsException once, then delegates to default parser. + * Useful for testing retry logic in change feed processor. + * + *

WARNING: This sets a GLOBAL interceptor. The test using this method + * should NOT run in parallel with other tests to avoid interference.

+ * + * @return AutoCloseable that clears the interceptor when closed + */ + public static AutoCloseable injectStreamConstraintsExceptionOnce(OperationType operationType, ResourceType resourceType) { + AtomicInteger callCount = new AtomicInteger(0); + + JsonNodeStorePayload.TestOnlyJsonParseInterceptor interceptor = (bytes, responseHeaders, defaultParser, actualOperationType, actualResourceType) -> { + if (operationType.equals(actualOperationType) && resourceType.equals(actualResourceType)) { + + int count = callCount.incrementAndGet(); + + if (count == 1) { + logger.info("JsonParseInterceptor: Injecting StreamConstraintsException (call #{})", count); + throw new StreamConstraintsException("Test-injected StreamConstraintsException"); + } + } + + logger.debug("JsonParseInterceptor: Delegating to default parser (call #{})", callCount.get()); + return defaultParser.parse(bytes, responseHeaders); + }; + + JsonNodeStorePayload.setTestOnlyJsonParseInterceptor(interceptor); + + return () -> { + logger.info("JsonParseInterceptor: Clearing interceptor after {} calls", callCount.get()); + JsonNodeStorePayload.clearTestOnlyJsonParseInterceptor(); + }; + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java index 906fc518f2f7..ca5e5cbb84d5 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java @@ -25,8 +25,6 @@ import com.azure.cosmos.implementation.http.HttpTransportSerializer; import com.azure.cosmos.implementation.http.ReactorNettyRequestRecord; import com.azure.cosmos.implementation.interceptor.ITransportClientInterceptor; -import com.azure.cosmos.implementation.perPartitionCircuitBreaker.GlobalPartitionEndpointManagerForPerPartitionCircuitBreaker; -import com.azure.cosmos.implementation.perPartitionCircuitBreaker.LocationSpecificHealthContext; import com.azure.cosmos.implementation.routing.PartitionKeyInternal; import com.azure.cosmos.implementation.routing.PartitionKeyInternalHelper; import com.azure.cosmos.implementation.routing.PartitionKeyRangeIdentity; @@ -233,7 +231,9 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), new ByteBufInputStream(retainedContent, true), - size); + size, + request.getOperationType(), + request.getResourceType()); } else { retainedContent.release(); } @@ -243,7 +243,9 @@ public StoreResponse unwrapToStoreResponse( statusCode, HttpUtils.unescape(headers.toLowerCaseMap()), null, - 0); + 0, + request.getOperationType(), + request.getResourceType()); } private Mono query(RxDocumentServiceRequest request) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java index 2a18b76e343a..eeed41958bf7 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/JsonNodeStorePayload.java @@ -5,6 +5,8 @@ import com.azure.cosmos.implementation.Configs; import com.azure.cosmos.implementation.HttpConstants; +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.Utils; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.JsonNode; @@ -20,27 +22,67 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; public class JsonNodeStorePayload implements StorePayload { private static final Logger logger = LoggerFactory.getLogger(JsonNodeStorePayload.class); private static final CharsetDecoder fallbackCharsetDecoder = getFallbackCharsetDecoder(); + + // Test-only interceptor for fault injection + // Using AtomicReference for thread-safe global interceptor + // This is a test-only feature and tests should ensure proper isolation through test orchestration + private static final AtomicReference globalTestInterceptor = new AtomicReference<>(); + private final int responsePayloadSize; private final JsonNode jsonValue; - public JsonNodeStorePayload(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { + public JsonNodeStorePayload( + ByteBufInputStream bufferStream, + int readableBytes, + Map responseHeaders, + OperationType operationType, + ResourceType resourceType) { + if (readableBytes > 0) { this.responsePayloadSize = readableBytes; - this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders); + this.jsonValue = fromJson(bufferStream, readableBytes, responseHeaders, operationType, resourceType); } else { this.responsePayloadSize = 0; this.jsonValue = null; } } - private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders) { + private static JsonNode fromJson(ByteBufInputStream bufferStream, int readableBytes, Map responseHeaders, + OperationType operationType, ResourceType resourceType) { byte[] bytes = new byte[readableBytes]; try { bufferStream.read(bytes); + + // Allow test-only interceptor to inject faults before parsing + TestOnlyJsonParseInterceptor interceptor = globalTestInterceptor.get(); + if (interceptor != null) { + return interceptor.intercept(bytes, responseHeaders, + (b, h) -> fromJsonWithBytes(b, h), operationType, resourceType); + } + + return fromJsonWithBytes(bytes, responseHeaders); + } catch (IOException e) { + // IOException from read operation + String baseErrorMessage = "Failed to read JSON document from stream."; + logger.error(baseErrorMessage, e); + + IllegalStateException innerException = new IllegalStateException("Unable to read JSON stream.", e); + + throw Utils.createCosmosException( + HttpConstants.StatusCodes.BADREQUEST, + evaluateSubStatusCode(e), + innerException, + responseHeaders); + } + } + + private static JsonNode fromJsonWithBytes(byte[] bytes, Map responseHeaders) throws IOException { + try { return Utils.getSimpleObjectMapper().readTree(bytes); } catch (IOException e) { if (fallbackCharsetDecoder != null) { @@ -162,4 +204,66 @@ private static int evaluateSubStatusCode(IOException exception) { return HttpConstants.SubStatusCodes.UNKNOWN; } + + /** + * Test-only interceptor interface for fault injection. + * WARNING: This is intended for testing purposes only and should not be used in production code. + */ + @FunctionalInterface + public interface TestOnlyJsonParseInterceptor { + /** + * Intercepts JSON parsing to allow fault injection. + * + * @param bytes the byte array containing JSON + * @param responseHeaders the response headers + * @param defaultParser the default parsing logic to delegate to + * @return the parsed JsonNode + * @throws IOException if parsing fails or fault is injected + */ + JsonNode intercept( + byte[] bytes, + Map responseHeaders, + DefaultJsonParser defaultParser, + OperationType operationType, + ResourceType resourceType + ) throws IOException; + + /** + * Functional interface for the default JSON parsing logic. + */ + @FunctionalInterface + interface DefaultJsonParser { + JsonNode parse(byte[] bytes, Map responseHeaders) throws IOException; + } + } + + /** + * Sets a test-only interceptor for JSON parsing globally. + * WARNING: This is intended for testing purposes only and should not be used in production code. + * + *

This sets a GLOBAL interceptor that affects all threads. Tests using this interceptor + * should NOT run in parallel with other tests to avoid interference. Use TestNG's + * singleThreaded = true or similar mechanisms to ensure test isolation.

+ * + *

The interceptor will be active across all threads including thread pool workers, + * making it suitable for testing multi-threaded components like ChangeFeedProcessor.

+ * + * @param interceptor the interceptor to set (null to clear) + */ + public static void setTestOnlyJsonParseInterceptor(TestOnlyJsonParseInterceptor interceptor) { + globalTestInterceptor.set(interceptor); + if (interceptor != null) { + logger.warn("GLOBAL test-only JSON parse interceptor has been set on thread {}. " + + "This affects ALL threads and should only be used in isolated test scenarios. " + + "Ensure tests using this do NOT run in parallel.", + Thread.currentThread().getName()); + } + } + + /** + * Clears the test-only interceptor. + */ + public static void clearTestOnlyJsonParseInterceptor() { + globalTestInterceptor.set(null); + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/ResponseUtils.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/ResponseUtils.java index bd5bf896eac4..e508e0e446cf 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/ResponseUtils.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/ResponseUtils.java @@ -4,7 +4,6 @@ package com.azure.cosmos.implementation.directconnectivity; import com.azure.cosmos.implementation.http.HttpHeaders; -import com.azure.cosmos.implementation.http.HttpRequest; import com.azure.cosmos.implementation.http.HttpResponse; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; @@ -29,7 +28,9 @@ static Mono toStoreResponse(HttpResponse httpClientResponse, Stri httpClientResponse.statusCode(), HttpUtils.unescape(httpResponseHeaders.toMap()), null, - 0); + 0, + null, + null); } return new StoreResponse( @@ -37,7 +38,9 @@ static Mono toStoreResponse(HttpResponse httpClientResponse, Stri httpClientResponse.statusCode(), HttpUtils.unescape(httpResponseHeaders.toMap()), new ByteBufInputStream(byteBufContent, true), - size); + size, + null, + null); }); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java index a4bab8f3edbe..13ad4007d9bd 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/StoreResponse.java @@ -4,7 +4,9 @@ package com.azure.cosmos.implementation.directconnectivity; import com.azure.cosmos.implementation.HttpConstants; +import com.azure.cosmos.implementation.OperationType; import com.azure.cosmos.implementation.RequestTimeline; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdChannelAcquisitionTimeline; import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdChannelStatistics; @@ -15,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,11 +48,13 @@ public class StoreResponse { private final String endpoint; public StoreResponse( - String endpoint, - int status, - Map headerMap, - ByteBufInputStream contentStream, - int responsePayloadLength) { + String endpoint, + int status, + Map headerMap, + ByteBufInputStream contentStream, + int responsePayloadLength, + OperationType operationType, + ResourceType resourceType) { checkArgument((contentStream == null) == (responsePayloadLength == 0), "Parameter 'contentStream' must be consistent with 'responsePayloadLength'."); @@ -71,7 +74,7 @@ public StoreResponse( replicaStatusList = new HashMap<>(); if (contentStream != null) { try { - this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap); + this.responsePayload = new JsonNodeStorePayload(contentStream, responsePayloadLength, headerMap, operationType, resourceType); } finally { try { contentStream.close(); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java index d7e8ccb56421..8c3c0fe48a84 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdRequestManager.java @@ -16,6 +16,7 @@ import com.azure.cosmos.implementation.LockedException; import com.azure.cosmos.implementation.MethodNotAllowedException; import com.azure.cosmos.implementation.NotFoundException; +import com.azure.cosmos.implementation.OperationType; import com.azure.cosmos.implementation.PartitionIsMigratingException; import com.azure.cosmos.implementation.PartitionKeyRangeGoneException; import com.azure.cosmos.implementation.PartitionKeyRangeIsSplittingException; @@ -23,6 +24,7 @@ import com.azure.cosmos.implementation.RequestEntityTooLargeException; import com.azure.cosmos.implementation.RequestRateTooLargeException; import com.azure.cosmos.implementation.RequestTimeoutException; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.RetryWithException; import com.azure.cosmos.implementation.RxDocumentServiceRequest; import com.azure.cosmos.implementation.ServiceUnavailableException; @@ -1001,6 +1003,9 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes final String requestUriAsString = requestRecord.args().physicalAddressUri() != null ? requestRecord.args().physicalAddressUri().getURI().toString() : null; + OperationType operationType = requestRecord.args().serviceRequest().getOperationType(); + ResourceType resourceType = requestRecord.args().serviceRequest().getResourceType(); + if ((HttpResponseStatus.OK.code() <= statusCode && statusCode < HttpResponseStatus.MULTIPLE_CHOICES.code()) || statusCode == HttpResponseStatus.NOT_MODIFIED.code()) { @@ -1008,7 +1013,7 @@ private void messageReceived(final ChannelHandlerContext context, final RntbdRes if (rntbdCtx == null) { throw new IllegalStateException("Expecting non-null rntbd context."); } - final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString); + final StoreResponse storeResponse = response.toStoreResponse(rntbdCtx.serverVersion(), requestUriAsString, operationType, resourceType); if (this.serverErrorInjector != null) { Consumer completeWithInjectedDelayConsumer = diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java index 1fb8b43d3fe8..d3976381cc8e 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/rntbd/RntbdResponse.java @@ -3,6 +3,8 @@ package com.azure.cosmos.implementation.directconnectivity.rntbd; +import com.azure.cosmos.implementation.OperationType; +import com.azure.cosmos.implementation.ResourceType; import com.azure.cosmos.implementation.directconnectivity.StoreResponse; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -347,7 +349,7 @@ public static RntbdResponse decode(final ByteBuf in) { return new RntbdResponse(in.readSlice(end - start), frame, headers, content); } - StoreResponse toStoreResponse(final String serverVersion, final String endpoint) { + StoreResponse toStoreResponse(final String serverVersion, final String endpoint, OperationType operationType, ResourceType resourceType) { checkNotNull(serverVersion, "Argument 'serverVersion' must not be null."); @@ -359,7 +361,9 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint) this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), null, - 0); + 0, + operationType, + resourceType); } return new StoreResponse( @@ -367,7 +371,9 @@ StoreResponse toStoreResponse(final String serverVersion, final String endpoint) this.getStatus().code(), this.headers.asMap(serverVersion, this.getActivityId()), new ByteBufInputStream(this.content.retain(), true), - length); + length, + operationType, + resourceType); } // endregion