From 5a4b74d89759c53582c3efa7398ca984cad476b5 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Thu, 18 Jun 2026 11:38:33 -0700 Subject: [PATCH 1/8] Cosmos Java: share PartitionKeyRangeCache across CosmosClients targeting the same account Move the partition-key-range routing-map cache from per-CosmosClient to a process-wide, refcounted registry keyed by service endpoint. Multiple CosmosClient / CosmosAsyncClient instances in the same JVM targeting the same Cosmos account now share a single AsyncCacheNonBlocking instance for collection -> CollectionRoutingMap, eliminating duplicate routing-map memory and redundant /pkranges fetches. Design - New SharedRoutingMapCacheRegistry (process-wide singleton) holds an AsyncCacheNonBlocking per endpoint URL plus an AtomicInteger refcount. All state transitions go through ConcurrentHashMap.compute, giving atomic per-key check-and-update without a global lock. - RxPartitionKeyRangeCache: new ctor accepts the service endpoint; underlying routingMapCache is obtained from the registry. Implements Closeable; close() releases this client's reference and is idempotent. - RxDocumentClientImpl: passes serviceEndpoint to the cache ctor and releases the cache reference in its close() path. - Opt-out: COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false restores the pre-sharing behaviour (each client owns a private cache). Why this is safe - PK-range data is account-level metadata, not credential-bound. - AsyncCacheNonBlocking already enforces single-flight per key; sharing the instance strengthens that to "single in-flight /pkranges per (account, container) across all clients". - The two-arg back-compat ctor resolves the endpoint from the client, so existing mocked tests continue to work (mock returns null endpoint -> isolated cache, matching today's behaviour). Tests - New SharedRoutingMapCacheRegistryTest: acquire/release sharing, refcount eviction, idempotent release, null-endpoint isolation, opt-out flag, 32-thread concurrent acquire/release stress. - New RxPartitionKeyRangeCacheTest cases: two caches at same endpoint share storage (verified by mock /pkranges call count = 1, not 2), caches at different endpoints stay independent, close() is idempotent. - Existing 7 RxPartitionKeyRangeCacheTest cases unchanged and passing. Reference Pattern matches Python (sdk/cosmos/azure-cosmos/azure/cosmos/_routing/ routing_map_provider.py) which uses module-level endpoint-keyed dicts with refcounted cleanup. Adapted to Java idioms (ConcurrentHashMap.compute instead of explicit RLock, Closeable instead of __del__). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../caches/RxPartitionKeyRangeCacheTest.java | 162 ++++++++++++- .../SharedRoutingMapCacheRegistryTest.java | 228 ++++++++++++++++++ sdk/cosmos/azure-cosmos/CHANGELOG.md | 1 + .../azure/cosmos/implementation/Configs.java | 14 ++ .../implementation/RxDocumentClientImpl.java | 7 +- .../caches/RxPartitionKeyRangeCache.java | 38 ++- .../caches/SharedRoutingMapCacheRegistry.java | 157 ++++++++++++ 7 files changed, 603 insertions(+), 4 deletions(-) create mode 100644 sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java create mode 100644 sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java index ed0b4491dbd7..d2431a5dd327 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java @@ -20,10 +20,13 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -35,7 +38,7 @@ public class RxPartitionKeyRangeCacheTest { private RxDocumentClientImpl client; private RxCollectionCache collectionCache; private RxPartitionKeyRangeCache cache; - + @BeforeMethod(groups = "unit") public void before_test() { client = Mockito.mock(RxDocumentClientImpl.class); @@ -245,4 +248,161 @@ public void tryLookupAsync_RetriesOnceAndConvertsToNotFoundException() { .expectNextMatches(s -> s.v == null) .verifyComplete(); } + + @Test(groups = "unit") + public void twoCachesForSameEndpointShareRoutingMapStorage() throws Exception { + // Two RxPartitionKeyRangeCache instances pointing at the same endpoint should + // share their underlying AsyncCacheNonBlocking, so a routing map populated by + // one is immediately visible to the other without a second /pkranges call. + URI endpoint = new URI("https://test-shared-pkr-1.documents.azure.com:443/"); + + RxDocumentClientImpl clientA = Mockito.mock(RxDocumentClientImpl.class); + RxDocumentClientImpl clientB = Mockito.mock(RxDocumentClientImpl.class); + RxCollectionCache collA = Mockito.mock(RxCollectionCache.class); + RxCollectionCache collB = Mockito.mock(RxCollectionCache.class); + + String collectionRid = "shared-coll-1"; + DocumentCollection collection = new DocumentCollection(); + collection.setResourceId(collectionRid); + collection.setSelfLink("dbs/db1/colls/coll1"); + + PartitionKeyRange range = new PartitionKeyRange(); + range.setId("0"); + range.setMinInclusive(PartitionKeyRange.MINIMUM_INCLUSIVE_EFFECTIVE_PARTITION_KEY); + range.setMaxExclusive(PartitionKeyRange.MAXIMUM_EXCLUSIVE_EFFECTIVE_PARTITION_KEY); + + FeedResponse response = Mockito.mock(FeedResponse.class); + when(response.getResults()).thenReturn(Arrays.asList(range)); + when(response.getContinuationToken()).thenReturn("etag-1"); + + AtomicInteger clientACalls = new AtomicInteger(); + AtomicInteger clientBCalls = new AtomicInteger(); + + when(collA.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + when(clientA.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + clientACalls.incrementAndGet(); + return Flux.just(response); + }); + + when(collB.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + when(clientB.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + clientBCalls.incrementAndGet(); + return Flux.just(response); + }); + + RxPartitionKeyRangeCache cacheA = new RxPartitionKeyRangeCache(clientA, collA, endpoint); + RxPartitionKeyRangeCache cacheB = new RxPartitionKeyRangeCache(clientB, collB, endpoint); + + try { + // First client populates the cache. + StepVerifier.create(cacheA.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .expectNextMatches(v -> v != null && v.v != null) + .verifyComplete(); + + // Second client must hit the shared cache without issuing its own /pkranges read. + StepVerifier.create(cacheB.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .expectNextMatches(v -> v != null && v.v != null) + .verifyComplete(); + + assertThat(clientACalls.get()) + .as("client A populates the shared routing map") + .isEqualTo(1); + assertThat(clientBCalls.get()) + .as("client B must hit the shared cache without issuing its own /pkranges call") + .isZero(); + } finally { + cacheA.close(); + cacheB.close(); + } + + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + .as("close() releases the shared cache reference") + .isZero(); + } + + @Test(groups = "unit") + public void cachesForDifferentEndpointsDoNotShareStorage() throws Exception { + URI endpointA = new URI("https://test-shared-pkr-2a.documents.azure.com:443/"); + URI endpointB = new URI("https://test-shared-pkr-2b.documents.azure.com:443/"); + + RxDocumentClientImpl clientA = Mockito.mock(RxDocumentClientImpl.class); + RxDocumentClientImpl clientB = Mockito.mock(RxDocumentClientImpl.class); + RxCollectionCache collA = Mockito.mock(RxCollectionCache.class); + RxCollectionCache collB = Mockito.mock(RxCollectionCache.class); + + String collectionRid = "shared-coll-2"; + DocumentCollection collection = new DocumentCollection(); + collection.setResourceId(collectionRid); + collection.setSelfLink("dbs/db1/colls/coll2"); + + PartitionKeyRange range = new PartitionKeyRange(); + range.setId("0"); + range.setMinInclusive(PartitionKeyRange.MINIMUM_INCLUSIVE_EFFECTIVE_PARTITION_KEY); + range.setMaxExclusive(PartitionKeyRange.MAXIMUM_EXCLUSIVE_EFFECTIVE_PARTITION_KEY); + + FeedResponse response = Mockito.mock(FeedResponse.class); + when(response.getResults()).thenReturn(Arrays.asList(range)); + when(response.getContinuationToken()).thenReturn("etag-2"); + + AtomicInteger clientACalls = new AtomicInteger(); + AtomicInteger clientBCalls = new AtomicInteger(); + + when(collA.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + when(clientA.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + clientACalls.incrementAndGet(); + return Flux.just(response); + }); + when(collB.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + when(clientB.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + clientBCalls.incrementAndGet(); + return Flux.just(response); + }); + + RxPartitionKeyRangeCache cacheA = new RxPartitionKeyRangeCache(clientA, collA, endpointA); + RxPartitionKeyRangeCache cacheB = new RxPartitionKeyRangeCache(clientB, collB, endpointB); + + try { + StepVerifier.create(cacheA.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .expectNextMatches(v -> v != null && v.v != null) + .verifyComplete(); + + StepVerifier.create(cacheB.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .expectNextMatches(v -> v != null && v.v != null) + .verifyComplete(); + + // Two different endpoints → no sharing, each client issues its own read. + assertThat(clientACalls.get()).isEqualTo(1); + assertThat(clientBCalls.get()).isEqualTo(1); + } finally { + cacheA.close(); + cacheB.close(); + } + } + + @Test(groups = "unit") + public void closeIsIdempotent() throws Exception { + URI endpoint = new URI("https://test-shared-pkr-3.documents.azure.com:443/"); + RxDocumentClientImpl mockClient = Mockito.mock(RxDocumentClientImpl.class); + RxCollectionCache mockColl = Mockito.mock(RxCollectionCache.class); + + RxPartitionKeyRangeCache c = new RxPartitionKeyRangeCache(mockClient, mockColl, endpoint); + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + .isEqualTo(1); + + c.close(); + c.close(); // second call must be a no-op + c.close(); + + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + .as("repeated close() must not drive refcount negative") + .isZero(); + } } \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java new file mode 100644 index 000000000000..c920c92696aa --- /dev/null +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.implementation.caches; + +import com.azure.cosmos.implementation.Configs; +import com.azure.cosmos.implementation.routing.CollectionRoutingMap; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link SharedRoutingMapCacheRegistry}. + * + *

The registry is a process-wide singleton, so these tests must leave it + * in a clean state for whatever endpoints they touch. Each test uses a + * uniquely-named endpoint and releases every reference it acquires.

+ */ +public class SharedRoutingMapCacheRegistryTest { + + private static final String ENABLE_FLAG = "COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED"; + + private String savedFlag; + + @BeforeMethod(groups = "unit") + public void before() { + savedFlag = System.getProperty(ENABLE_FLAG); + System.clearProperty(ENABLE_FLAG); // default is enabled + } + + @AfterMethod(groups = "unit") + public void after() { + if (savedFlag == null) { + System.clearProperty(ENABLE_FLAG); + } else { + System.setProperty(ENABLE_FLAG, savedFlag); + } + } + + @Test(groups = "unit") + public void acquireReturnsSameInstanceForSameEndpoint() { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-1.documents.azure.com:443/"; + + AsyncCacheNonBlocking a = registry.acquire(endpoint); + AsyncCacheNonBlocking b = registry.acquire(endpoint); + + try { + assertThat(a).isSameAs(b); + assertThat(registry.referenceCount(endpoint)).isEqualTo(2); + } finally { + registry.release(endpoint, a); + registry.release(endpoint, b); + } + assertThat(registry.referenceCount(endpoint)).isZero(); + } + + @Test(groups = "unit") + public void acquireReturnsDifferentInstanceForDifferentEndpoints() { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String e1 = "https://test-acct-share-2a.documents.azure.com:443/"; + String e2 = "https://test-acct-share-2b.documents.azure.com:443/"; + + AsyncCacheNonBlocking a = registry.acquire(e1); + AsyncCacheNonBlocking b = registry.acquire(e2); + + try { + assertThat(a).isNotSameAs(b); + } finally { + registry.release(e1, a); + registry.release(e2, b); + } + } + + @Test(groups = "unit") + public void releaseEvictsAtZeroRefcount() { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-3.documents.azure.com:443/"; + + AsyncCacheNonBlocking a = registry.acquire(endpoint); + AsyncCacheNonBlocking b = registry.acquire(endpoint); + assertThat(registry.referenceCount(endpoint)).isEqualTo(2); + + registry.release(endpoint, a); + assertThat(registry.referenceCount(endpoint)).isEqualTo(1); + + registry.release(endpoint, b); + assertThat(registry.referenceCount(endpoint)).isZero(); + + // After eviction, a fresh acquire produces a brand-new cache (not the previous one). + AsyncCacheNonBlocking c = registry.acquire(endpoint); + try { + assertThat(c).isNotSameAs(a); + assertThat(registry.referenceCount(endpoint)).isEqualTo(1); + } finally { + registry.release(endpoint, c); + } + } + + @Test(groups = "unit") + public void releaseIsIdempotentWhenSuppliedSameCacheRepeatedly() { + // The registry's contract is that calling release with a cache instance + // that is not currently registered (e.g. already-evicted) is a no-op. + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-4.documents.azure.com:443/"; + + AsyncCacheNonBlocking a = registry.acquire(endpoint); + registry.release(endpoint, a); + assertThat(registry.referenceCount(endpoint)).isZero(); + + // Releasing again with the now-stale cache reference must not crash or go negative. + registry.release(endpoint, a); + assertThat(registry.referenceCount(endpoint)).isZero(); + } + + @Test(groups = "unit") + public void releaseIsNoOpWhenCacheIsNotTheRegisteredInstance() { + // After eviction and re-acquire, the registry holds a different instance. + // Releasing the old (stale) reference must not affect the new registered entry. + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-5.documents.azure.com:443/"; + + AsyncCacheNonBlocking stale = registry.acquire(endpoint); + registry.release(endpoint, stale); + + AsyncCacheNonBlocking current = registry.acquire(endpoint); + try { + registry.release(endpoint, stale); // stale != current → no-op + assertThat(registry.referenceCount(endpoint)).isEqualTo(1); + } finally { + registry.release(endpoint, current); + } + } + + @Test(groups = "unit") + public void nullEndpointReturnsIsolatedCacheAndDoesNotEnterRegistry() { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + int before = registry.registeredEndpointCount(); + + AsyncCacheNonBlocking a = registry.acquire(null); + AsyncCacheNonBlocking b = registry.acquire(null); + + assertThat(a).isNotSameAs(b); + assertThat(registry.registeredEndpointCount()).isEqualTo(before); + + // Release with null endpoint is a safe no-op. + registry.release(null, a); + registry.release(null, b); + } + + @Test(groups = "unit") + public void disabledFlagReturnsIsolatedCachesAndPreservesRegistryEmpty() { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-6.documents.azure.com:443/"; + int before = registry.registeredEndpointCount(); + + System.setProperty(ENABLE_FLAG, "false"); + assertThat(Configs.isSharedPartitionKeyRangeCacheEnabled()).isFalse(); + + AsyncCacheNonBlocking a = registry.acquire(endpoint); + AsyncCacheNonBlocking b = registry.acquire(endpoint); + + try { + // With sharing disabled, each acquire returns a fresh, isolated cache. + assertThat(a).isNotSameAs(b); + assertThat(registry.registeredEndpointCount()).isEqualTo(before); + assertThat(registry.referenceCount(endpoint)).isZero(); + } finally { + // Release should be safe (no-op) since these caches were never registered. + registry.release(endpoint, a); + registry.release(endpoint, b); + } + } + + @Test(groups = "unit") + public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Exception { + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + String endpoint = "https://test-acct-share-7.documents.azure.com:443/"; + + int threads = 32; + int opsPerThread = 200; + ExecutorService pool = Executors.newFixedThreadPool(threads); + CountDownLatch start = new CountDownLatch(1); + CountDownLatch done = new CountDownLatch(threads); + + try { + for (int t = 0; t < threads; t++) { + pool.submit(() -> { + try { + start.await(); + List> held = new ArrayList<>(); + for (int i = 0; i < opsPerThread; i++) { + held.add(registry.acquire(endpoint)); + if (i % 3 == 0 && !held.isEmpty()) { + AsyncCacheNonBlocking c = + held.remove(held.size() - 1); + registry.release(endpoint, c); + } + } + for (AsyncCacheNonBlocking c : held) { + registry.release(endpoint, c); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + done.countDown(); + } + }); + } + start.countDown(); + assertThat(done.await(30, TimeUnit.SECONDS)).isTrue(); + } finally { + pool.shutdownNow(); + } + + // All acquires matched by releases → refcount must be zero and entry evicted. + assertThat(registry.referenceCount(endpoint)).isZero(); + } +} diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md index eee4f87437f9..f8a6e307d9f7 100644 --- a/sdk/cosmos/azure-cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md @@ -10,6 +10,7 @@ #### Other Changes * Reduced memory footprint of deserialized `PartitionKeyRange` instances by stripping unused fields in the `PartitionKeyRange(ObjectNode)` constructor - See PR [49513](https://github.com/Azure/azure-sdk-for-java/pull/49513). +* Reduced memory footprint and redundant `/pkranges` reads when multiple `CosmosClient` / `CosmosAsyncClient` instances in the same JVM target the same Cosmos account. The partition-key-range routing-map cache (`RxPartitionKeyRangeCache`) is now process-scoped and shared across clients via a refcounted registry keyed by service endpoint; the shared entry is evicted when the last client closes. Sharing also strengthens the single-flight invariant: only one in-flight `/pkranges` fetch per `(account, container)` at any time, even across clients. Disable with system property `COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false` if needed. ### 4.81.0 (2026-06-08) 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 cf1a812e10f2..383e7ff77856 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 @@ -218,6 +218,14 @@ public class Configs { private static final String USE_LEGACY_TRACING = "COSMOS.USE_LEGACY_TRACING"; private static final boolean DEFAULT_USE_LEGACY_TRACING = false; + // Whether multiple CosmosClient instances in the same JVM that target the same + // service endpoint share a single partition-key-range routing-map cache. + // Enabled by default. Setting this to false restores the pre-sharing behaviour + // where every client owns a private cache (useful as a safety valve only). + private static final String SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED = + "COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED"; + private static final boolean DEFAULT_SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED = true; + // whether to enable replica addresses validation private static final String REPLICA_ADDRESS_VALIDATION_ENABLED = "COSMOS.REPLICA_ADDRESS_VALIDATION_ENABLED"; private static final boolean DEFAULT_REPLICA_ADDRESS_VALIDATION_ENABLED = true; @@ -1082,6 +1090,12 @@ public static boolean useLegacyTracing() { DEFAULT_USE_LEGACY_TRACING); } + public static boolean isSharedPartitionKeyRangeCacheEnabled() { + return getJVMConfigAsBoolean( + SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED, + DEFAULT_SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED); + } + private static int getJVMConfigAsInt(String propName, int defaultValue) { String propValue = System.getProperty(propName); return getIntValue(propValue, defaultValue); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java index d4f99c183c24..a561db304388 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java @@ -925,7 +925,7 @@ public void init(CosmosClientMetadataCachesSnapshot metadataCachesSnapshot, Func this.resetSessionTokenRetryPolicy = new ResetSessionTokenRetryPolicyFactory(this.sessionContainer, this.collectionCache, this.retryPolicy); this.partitionKeyRangeCache = new RxPartitionKeyRangeCache(RxDocumentClientImpl.this, - collectionCache); + collectionCache, this.serviceEndpoint); updateGatewayProxy(); updateThinProxy(); @@ -7465,6 +7465,11 @@ public void close() { this.throughputControlStore.close(); } + if (this.partitionKeyRangeCache != null) { + logger.info("Releasing shared PartitionKeyRangeCache reference ..."); + LifeCycleUtils.closeQuietly(this.partitionKeyRangeCache); + } + if (this.clientTelemetry != null) { logger.info("Closing ClientTelemetry ..."); this.clientTelemetry.close(); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index b869489c7d75..bcc088710cc9 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -31,34 +31,68 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.io.Closeable; +import java.net.URI; import java.time.Instant; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** * While this class is public, but it is not part of our published public APIs. * This is meant to be internally used only by our sdk. + * + *

The underlying routing-map storage ({@link AsyncCacheNonBlocking}) is + * obtained from {@link SharedRoutingMapCacheRegistry} keyed by the service + * endpoint, so multiple {@code CosmosClient} instances targeting the same + * account share a single routing-map cache. {@link #close()} releases this + * instance's reference; the shared cache is evicted only when the last + * reference is released. The fetching logic (network call, collection + * resolution, diagnostics) remains per-client.

**/ -public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache { +public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closeable { private final Logger logger = LoggerFactory.getLogger(RxPartitionKeyRangeCache.class); private final AsyncCacheNonBlocking routingMapCache; private final RxDocumentClientImpl client; private final RxCollectionCache collectionCache; private final DiagnosticsClientContext clientContext; + private final String sharedCacheEndpointKey; + private final AtomicBoolean closed = new AtomicBoolean(false); public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { - this.routingMapCache = new AsyncCacheNonBlocking<>(); + this(client, collectionCache, client == null ? null : client.getServiceEndpoint()); + } + + public RxPartitionKeyRangeCache( + RxDocumentClientImpl client, + RxCollectionCache collectionCache, + URI serviceEndpoint) { + + this.sharedCacheEndpointKey = serviceEndpoint == null ? null : serviceEndpoint.toString(); + this.routingMapCache = SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey); this.client = client; this.collectionCache = collectionCache; this.clientContext = client; } + /** + * Releases this instance's reference to the shared routing-map cache. + * Safe to call multiple times; only the first call has an effect. + * After {@code close()} the instance must not be used further. + */ + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + SharedRoutingMapCacheRegistry.getInstance().release(this.sharedCacheEndpointKey, this.routingMapCache); + } + } + /* (non-Javadoc) * @see IPartitionKeyRangeCache#tryLookupAsync(java.lang.STRING, com.azure.cosmos.internal.routing.CollectionRoutingMap) */ diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java new file mode 100644 index 000000000000..cfcc7706d7cf --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.cosmos.implementation.caches; + +import com.azure.cosmos.implementation.Configs; +import com.azure.cosmos.implementation.routing.CollectionRoutingMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Process-wide registry of {@link AsyncCacheNonBlocking} instances that hold + * the cached partition-key-range routing maps, keyed by the Cosmos service + * endpoint (account) the cache belongs to. + * + *

Historically, every {@link RxPartitionKeyRangeCache} owned its own + * private {@link AsyncCacheNonBlocking}. When many {@code CosmosAsyncClient} + * instances in the same JVM target the same account (a common multi-tenant / + * multi-credential pattern), each client paid an independent memory cost for + * the routing-map cache and independently issued {@code /pkranges} reads for + * the same containers. + * + *

This registry lets clients targeting the same account share a single + * cache instance. Sharing also strengthens the single-flight invariant + * already provided by {@link AsyncCacheNonBlocking}: only one in-flight + * {@code /pkranges} fetch per (account, container) at any time, even across + * clients.

+ * + *

Lifecycle. Callers obtain a shared cache via {@link #acquire(String)} + * during construction and return it via {@link #release(String, AsyncCacheNonBlocking)} + * during {@code close()}. A per-entry refcount tracks live callers; when the + * count reaches zero the entry is evicted so an idle endpoint does not pin + * memory forever.

+ * + *

Opt-out. Setting the system property + * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false} disables + * sharing; each {@link #acquire(String)} returns a fresh, isolated cache so + * behaviour matches the pre-sharing implementation. The opt-out is read once + * per {@link #acquire(String)} call so a test can toggle the property between + * client constructions without restarting the JVM.

+ * + *

Concurrency. All state transitions go through + * {@link ConcurrentHashMap#compute(Object, java.util.function.BiFunction)}, + * which gives atomic check-and-update under a per-key lock. No global lock + * is required.

+ */ +public final class SharedRoutingMapCacheRegistry { + private static final Logger logger = LoggerFactory.getLogger(SharedRoutingMapCacheRegistry.class); + + private static final SharedRoutingMapCacheRegistry INSTANCE = new SharedRoutingMapCacheRegistry(); + + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + + private SharedRoutingMapCacheRegistry() { + } + + public static SharedRoutingMapCacheRegistry getInstance() { + return INSTANCE; + } + + /** + * Returns the shared {@link AsyncCacheNonBlocking} for the given endpoint, + * creating it if necessary, and increments the refcount. + * + *

If {@code endpoint} is {@code null} or sharing is disabled via + * {@link Configs#isSharedPartitionKeyRangeCacheEnabled()}, returns a fresh + * isolated cache that the caller fully owns. {@link #release(String, AsyncCacheNonBlocking)} + * is still safe to call on such a cache (it is a no-op).

+ * + * @param endpoint The Cosmos service endpoint URL (e.g. + * {@code "https://my-account.documents.azure.com:443/"}), + * or {@code null} for an isolated cache. + * @return The shared (or isolated) cache instance to use as routing-map storage. + */ + public AsyncCacheNonBlocking acquire(String endpoint) { + if (endpoint == null || !Configs.isSharedPartitionKeyRangeCacheEnabled()) { + // Caller-owned cache; never enters the shared map. + return new AsyncCacheNonBlocking<>(); + } + + Entry entry = entries.compute(endpoint, (key, existing) -> { + if (existing == null) { + Entry created = new Entry(); + created.refCount.set(1); + logger.debug("Created shared partition key range cache for endpoint [{}]", key); + return created; + } + existing.refCount.incrementAndGet(); + return existing; + }); + return entry.cache; + } + + /** + * Releases a reference to the shared cache previously obtained via + * {@link #acquire(String)}. When the last reference is released the + * registry entry is evicted. + * + *

Safe to call when sharing was bypassed (null endpoint or sharing + * disabled): the call is a no-op if the supplied cache is not the one + * currently registered for {@code endpoint}. This makes the API safe to + * call unconditionally from client close paths.

+ * + *

Idempotency of the per-caller release contract is the caller's + * responsibility (typically guarded by an {@code AtomicBoolean} in + * {@link RxPartitionKeyRangeCache}). This method itself is safe to call + * concurrently from multiple threads for distinct callers.

+ * + * @param endpoint The endpoint the cache was acquired for, or {@code null} + * if it was an isolated cache. + * @param cache The cache instance returned by {@link #acquire(String)}. + */ + public void release(String endpoint, AsyncCacheNonBlocking cache) { + if (endpoint == null || cache == null) { + return; + } + + entries.compute(endpoint, (key, existing) -> { + if (existing == null || existing.cache != cache) { + // Either sharing was disabled when this cache was acquired + // (isolated cache, never registered) or another release() + // already evicted the entry. Nothing to do. + return existing; + } + int remaining = existing.refCount.decrementAndGet(); + if (remaining <= 0) { + logger.debug("Evicting shared partition key range cache for endpoint [{}]", key); + return null; + } + return existing; + }); + } + + /** + * Test-only: number of registered endpoints currently held by the registry. + * Visible-for-testing. + */ + int registeredEndpointCount() { + return entries.size(); + } + + /** + * Test-only: current refcount for an endpoint, or {@code 0} if no entry + * is registered. Visible-for-testing. + */ + int referenceCount(String endpoint) { + Entry entry = entries.get(endpoint); + return entry == null ? 0 : entry.refCount.get(); + } + + private static final class Entry { + final AsyncCacheNonBlocking cache = new AsyncCacheNonBlocking<>(); + final AtomicInteger refCount = new AtomicInteger(0); + } +} From f3fa638fcb219ad4e1266b2afd4fb21372458575 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 09:22:33 -0700 Subject: [PATCH 2/8] Cosmos Java: use URI (not String) as registry key for case-insensitive host matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch SharedRoutingMapCacheRegistry's key type from String to URI so URI.equals() — which is case-insensitive on the host component per RFC 3986 — is used for sharing identity. Previously, two clients built with 'https://Acct.documents.azure.com/' and 'https://acct.documents.azure.com/' would fragment into two registry entries even though they target the same account. With URI as the key the two collapse into a single shared entry. This matches the spirit of the Rust SDK, which uses Url-based equality on its AccountReference identity. Python uses raw string comparison; Java's URI gives us strictly better behaviour for free. Added a new test (acquireTreatsHostCaseInsensitivelyMatchingUriEquals) that asserts URI.equals() considers the two casings equal AND that the registry produces a single shared entry for them. Ran 34 cache unit tests, 0 failures. No public API change. RxPartitionKeyRangeCache's three-arg ctor still takes URI; only the internal field type changed (String -> URI). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Command line suite/Command line test.html | 129 +++++ .../Command line suite/Command line test.xml | 14 + .../test-output/bullet_point.png | Bin 0 -> 356 bytes .../test-output/collapseall.gif | Bin 0 -> 157 bytes .../test-output/emailable-report.html | 27 + .../test-output/failed.png | Bin 0 -> 977 bytes .../test-output/index.html | 463 ++++++++++++++++++ .../test-output/jquery.min.js | 2 + ...ntation.sink.SinkRecordTransformerTest.xml | 24 + .../test-output/navigator-bullet.png | Bin 0 -> 352 bytes .../Command line test.properties | 1 + .../old/Command line suite/classes.html | 74 +++ .../old/Command line suite/groups.html | 3 + .../old/Command line suite/index.html | 6 + .../old/Command line suite/main.html | 2 + .../methods-alphabetical.html | 24 + .../Command line suite/methods-not-run.html | 2 + .../old/Command line suite/methods.html | 24 + .../Command line suite/reporter-output.html | 1 + .../old/Command line suite/testng.xml.html | 1 + .../old/Command line suite/toc.html | 30 ++ .../test-output/old/index.html | 9 + .../test-output/passed.png | Bin 0 -> 1019 bytes .../test-output/skipped.png | Bin 0 -> 967 bytes .../test-output/testng-reports.css | 326 ++++++++++++ .../test-output/testng-reports.js | 122 +++++ .../test-output/testng-reports1.css | 344 +++++++++++++ .../test-output/testng-reports2.js | 76 +++ .../test-output/testng-results.xml | 65 +++ .../test-output/testng.css | 9 + .../caches/RxPartitionKeyRangeCacheTest.java | 6 +- .../SharedRoutingMapCacheRegistryTest.java | 46 +- .../caches/RxPartitionKeyRangeCache.java | 4 +- .../caches/SharedRoutingMapCacheRegistry.java | 35 +- 34 files changed, 1843 insertions(+), 26 deletions(-) create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/collapseall.gif create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/junitreports/TEST-com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest.xml create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/Command line test.properties create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/classes.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/skipped.png create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml create mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html new file mode 100644 index 000000000000..c53d83ca957a --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html @@ -0,0 +1,129 @@ + + +TestNG: Command line test + + + + + + + + +

Command line test

+ + + + + + + + + + + +
Tests passed/Failed/Skipped:10/0/0
Started on:Fri May 29 09:31:34 PDT 2026
Total time:0 seconds (490 ms)
Included groups:unit
Excluded groups:

+(Hover the method name to see the test class name)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PASSED TESTS
Test methodExceptionTime (seconds)Instance
allValidRecords_allInOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchToleranceNone_noReporter_exceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
reporterThrows_toleranceAll_recordSkippedProcessingContinues
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
toleranceNoneWithReporter_reportedToDlqAndExceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
reporterThrows_toleranceNone_originalExceptionRethrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
structConversionFailure_toleranceAll_reportedToDlqAndSkipped
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
structConversionFailure_toleranceNone_exceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchToleranceAll_noReporter_badRecordSkipped
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130

+ + \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml new file mode 100644 index 000000000000..205d4e6c554e --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png new file mode 100644 index 0000000000000000000000000000000000000000..176e6d5b3d64d032e76c493e5811a1cf839220b5 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hVR@^o z&n}1RKn7{UjfWZCs($|cfA#bKkH7!F`St(Z@BiQa{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtB(Pob+71*X+evXI>YLE;&}Fj8#mRE%&W?B30shyu13% zpT6C#3k-fJGjKF52@24V6I?%GvcZa|)%y<^9(-F=IB9W`k6g3(YLhfsMh0sDZC^x! literal 0 HcmV?d00001 diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html new file mode 100644 index 000000000000..4d3118b9c4e5 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html @@ -0,0 +1,27 @@ + + + + +TestNG Report + + + + + + + +
Test# Passed# Skipped# Retried# FailedTime (ms)Included GroupsExcluded Groups
Command line suite
Command line test10000490unit
+ +
ClassMethodStartTime (ms)
Command line suite
Command line test — passed
com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTestallBadRecordsWithReporterToleranceAll_allReportedEmptyOutput1780072294587448
allValidRecords_allInOutput17800722950361
mixedBatchToleranceAll_noReporter_badRecordSkipped17800722950370
mixedBatchToleranceNone_noReporter_exceptionThrown17800722950382
mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput17800722950401
reporterThrows_toleranceAll_recordSkippedProcessingContinues17800722950411
reporterThrows_toleranceNone_originalExceptionRethrown17800722950421
structConversionFailure_toleranceAll_reportedToDlqAndSkipped178007229504320
structConversionFailure_toleranceNone_exceptionThrown17800722950631
toleranceNoneWithReporter_reportedToDlqAndExceptionThrown17800722950641
+

Command line test

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#allValidRecords_allInOutput

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchToleranceAll_noReporter_badRecordSkipped

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchToleranceNone_noReporter_exceptionThrown

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#reporterThrows_toleranceAll_recordSkippedProcessingContinues

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#reporterThrows_toleranceNone_originalExceptionRethrown

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#structConversionFailure_toleranceAll_reportedToDlqAndSkipped

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#structConversionFailure_toleranceNone_exceptionThrown

back to summary

+

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#toleranceNoneWithReporter_reportedToDlqAndExceptionThrown

back to summary

+ + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png new file mode 100644 index 0000000000000000000000000000000000000000..c117be59a9ecd1da15ebf48f6b7f53496302a7cd GIT binary patch literal 977 zcmV;?11|iDP)4Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0B%V{K~yLeW4y>F|DS;bz(j&tuu`}Ny`K+o>P41= zYq-R&z$-w|z14sZ}6S`uM8b)lMhS`K{GDtB9px6Kr!cSsofH?!*c`##8 zG{6+YB(Z6NYd}|wOA}U4!xUqq;Wl8C#3lv+hIuOk>aOmJ00000NkvXXu0mjfn+D0# literal 0 HcmV?d00001 diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html new file mode 100644 index 000000000000..f3e0de8a2bb2 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html @@ -0,0 +1,463 @@ + + + + + + TestNG reports + + + + + + + + + + + +
+ Test results + +
+ 1 suite +
+ +
+
+
+
+
+ + com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest +
+
+
+
+ + + allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput +
+
+
+
+ + + allValidRecords_allInOutput +
+
+
+
+ + + mixedBatchToleranceAll_noReporter_badRecordSkipped +
+
+
+
+ + + mixedBatchToleranceNone_noReporter_exceptionThrown +
+
+
+
+ + + mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput +
+
+
+
+ + + reporterThrows_toleranceAll_recordSkippedProcessingContinues +
+
+
+
+ + + reporterThrows_toleranceNone_originalExceptionRethrown +
+
+
+
+ + + structConversionFailure_toleranceAll_reportedToDlqAndSkipped +
+
+
+
+ + + structConversionFailure_toleranceNone_exceptionThrown +
+
+
+
+ + + toleranceNoneWithReporter_reportedToDlqAndExceptionThrown +
+
+
+
+
+
+
+
+
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
+<suite name="Command line suite">
+  <groups>
+    <run>
+      <include name="unit"/>
+    </run>
+  </groups>
+  <test thread-count="5" name="Command line test">
+    <classes>
+      <class name="com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest"/>
+    </classes>
+  </test> <!-- Command line test -->
+</suite> <!-- Command line suite -->
+            
+
+
+
+
+ Tests for Command line suite +
+
+
    +
  • + Command line test (1 class) +
  • +
+
+
+
+
+ Groups for Command line suite +
+
+
+ unit +
+
+ allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput +
+
+
+ allValidRecords_allInOutput +
+
+
+ mixedBatchToleranceAll_noReporter_badRecordSkipped +
+
+
+ mixedBatchToleranceNone_noReporter_exceptionThrown +
+
+
+ mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput +
+
+
+ reporterThrows_toleranceAll_recordSkippedProcessingContinues +
+
+
+ reporterThrows_toleranceNone_originalExceptionRethrown +
+
+
+ structConversionFailure_toleranceAll_reportedToDlqAndSkipped +
+
+
+ structConversionFailure_toleranceNone_exceptionThrown +
+
+
+ toleranceNoneWithReporter_reportedToDlqAndExceptionThrown +
+
+
+
+
+
+
+ Times for Command line suite +
+
+
+ + Total running time: 476 ms +
+
+
+
+
+
+
+ Reporter output for Command line suite +
+
+
+
+
+
+ 0 ignored methods +
+
+
+
+
+
+ Methods in chronological order +
+
+
+
com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
+
+ allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput + 0 ms +
+
+ allValidRecords_allInOutput + 449 ms +
+
+ mixedBatchToleranceAll_noReporter_badRecordSkipped + 450 ms +
+
+ mixedBatchToleranceNone_noReporter_exceptionThrown + 451 ms +
+
+ mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput + 453 ms +
+
+ reporterThrows_toleranceAll_recordSkippedProcessingContinues + 454 ms +
+
+ reporterThrows_toleranceNone_originalExceptionRethrown + 455 ms +
+
+ structConversionFailure_toleranceAll_reportedToDlqAndSkipped + 456 ms +
+
+ structConversionFailure_toleranceNone_exceptionThrown + 476 ms +
+
+ toleranceNoneWithReporter_reportedToDlqAndExceptionThrown + 477 ms +
+
+
+
+
+ + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js new file mode 100644 index 000000000000..b0614034ad3a --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png new file mode 100644 index 0000000000000000000000000000000000000000..36d90d395c51912e718b89dd88b4a3fb53aa1d85 GIT binary patch literal 352 zcmV-m0iXVfP)G5@hw44>$jtc^drBsEhr7 z^X9?-KzfCWMC0vWtek#CBxB+XG+nX0$0e)!py)g%*!C9F3xb^$q9zV zJJ-RS;)J3Q3>X<0IJnsvq?E-OUUR%-Sh{}$*!>`a1>MbzjEoGd?5qriD%uRz5+)#_ z=~xvqF)}e2@@p|@3aYFDDdOf=+lQf0fP;_0P2842gi~-LkXsB?^cOvN)>U@o{(tlO y5-4a&(SrsYdr*b0AjKdWn<5ZqBsQ)A0t^5xc9&6bK}yU30000 + +Class name +Method name +Groups + +com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest +   + +@Test + + +  +allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput +unit + + +  +reporterThrows_toleranceNone_originalExceptionRethrown +unit + + +  +mixedBatchToleranceNone_noReporter_exceptionThrown +unit + + +  +structConversionFailure_toleranceNone_exceptionThrown +unit + + +  +structConversionFailure_toleranceAll_reportedToDlqAndSkipped +unit + + +  +reporterThrows_toleranceAll_recordSkippedProcessingContinues +unit + + +  +mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput +unit + + +  +allValidRecords_allInOutput +unit + + +  +toleranceNoneWithReporter_reportedToDlqAndExceptionThrown +unit + + +  +mixedBatchToleranceAll_noReporter_badRecordSkipped +unit + + +@BeforeClass + + +@BeforeMethod + + +@AfterMethod + + +@AfterClass + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html new file mode 100644 index 000000000000..2cd12182e1b4 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html @@ -0,0 +1,3 @@ +

Groups used for this test run

+ +
Group nameMethods
unitSinkRecordTransformerTest.mixedBatchToleranceAll_noReporter_badRecordSkipped()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.reporterThrows_toleranceNone_originalExceptionRethrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.structConversionFailure_toleranceAll_reportedToDlqAndSkipped()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.structConversionFailure_toleranceNone_exceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.mixedBatchToleranceNone_noReporter_exceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.allValidRecords_allInOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.reporterThrows_toleranceAll_recordSkippedProcessingContinues()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.toleranceNoneWithReporter_reportedToDlqAndExceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html new file mode 100644 index 000000000000..3577bd28a9f6 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html @@ -0,0 +1,6 @@ +Results for Command line suite + + + + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html new file mode 100644 index 000000000000..0ee4b5b499ac --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html @@ -0,0 +1,2 @@ +Results for Command line suite +Select a result on the left-hand pane. diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html new file mode 100644 index 000000000000..b337d595b0d7 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html @@ -0,0 +1,24 @@ +

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hover the method name to see the test class name)

+ + + + + + + + + + + + + + + + + + + + + + +
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
26/05/29 09:31:34 0      allBadRecordsWithReporterToleranceAll_allReportedEmptyOutputmain@1983025922
26/05/29 09:31:35 450      allValidRecords_allInOutputmain@1983025922
26/05/29 09:31:35 451      mixedBatchToleranceAll_noReporter_badRecordSkippedmain@1983025922
26/05/29 09:31:35 452      mixedBatchToleranceNone_noReporter_exceptionThrownmain@1983025922
26/05/29 09:31:35 454      mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutputmain@1983025922
26/05/29 09:31:35 455      reporterThrows_toleranceAll_recordSkippedProcessingContinuesmain@1983025922
26/05/29 09:31:35 456      reporterThrows_toleranceNone_originalExceptionRethrownmain@1983025922
26/05/29 09:31:35 457      structConversionFailure_toleranceAll_reportedToDlqAndSkippedmain@1983025922
26/05/29 09:31:35 477      structConversionFailure_toleranceNone_exceptionThrownmain@1983025922
26/05/29 09:31:35 478      toleranceNoneWithReporter_reportedToDlqAndExceptionThrownmain@1983025922
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html new file mode 100644 index 000000000000..54b14cb854b6 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html @@ -0,0 +1,2 @@ +

Methods that were not run

+
\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html new file mode 100644 index 000000000000..8445f609ae72 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html @@ -0,0 +1,24 @@ +

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hover the method name to see the test class name)

+ + + + + + + + + + + + + + + + + + + + + + +
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
26/05/29 09:31:35 0      allValidRecords_allInOutputmain@1983025922
26/05/29 09:31:35 2      mixedBatchToleranceNone_noReporter_exceptionThrownmain@1983025922
26/05/29 09:31:35 5      reporterThrows_toleranceAll_recordSkippedProcessingContinuesmain@1983025922
26/05/29 09:31:35 28      toleranceNoneWithReporter_reportedToDlqAndExceptionThrownmain@1983025922
26/05/29 09:31:34 -450      allBadRecordsWithReporterToleranceAll_allReportedEmptyOutputmain@1983025922
26/05/29 09:31:35 4      mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutputmain@1983025922
26/05/29 09:31:35 6      reporterThrows_toleranceNone_originalExceptionRethrownmain@1983025922
26/05/29 09:31:35 7      structConversionFailure_toleranceAll_reportedToDlqAndSkippedmain@1983025922
26/05/29 09:31:35 27      structConversionFailure_toleranceNone_exceptionThrownmain@1983025922
26/05/29 09:31:35 1      mixedBatchToleranceAll_noReporter_badRecordSkippedmain@1983025922
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html new file mode 100644 index 000000000000..063bc2e96fd0 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html @@ -0,0 +1 @@ +

Reporter output

\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html new file mode 100644 index 000000000000..0e5e7a37e49a --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html @@ -0,0 +1 @@ +testng.xml for Command line suite<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Command line suite">
  <groups>
    <run>
      <include name="unit"/>
    </run>
  </groups>
  <test thread-count="5" name="Command line test">
    <classes>
      <class name="com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest"/>
    </classes>
  </test> <!-- Command line test -->
</suite> <!-- Command line suite -->
\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html new file mode 100644 index 000000000000..77503f658dfb --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html @@ -0,0 +1,30 @@ + + +Results for Command line suite + + + + +

Results for
Command line suite

+ + + + + + + + + + +
1 test1 class10 methods:
+  chronological
+  alphabetical
+  not run (0)
1 groupreporter outputtestng.xml
+ +

+

+
Command line test (10/0/0) + Results +
+
+ \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html new file mode 100644 index 000000000000..436304d71d22 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html @@ -0,0 +1,9 @@ + + + + +

Test results

+ + + +
SuitePassedFailedSkippedtestng.xml
Total1000 
Command line suite1000Link
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png new file mode 100644 index 0000000000000000000000000000000000000000..45e85bbfd0f5e85def14b896cfd4331675be2759 GIT binary patch literal 1019 zcmV4Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0GLTcK~yLeW0ahz`=5aXz(j&tuu_sWu%O#uE8~VD zl&lrR;HF{4AT>#kuni$fu3*LaYg^!kpg8GS-X(?~-@n6gsDV2}@4opAtDmldYd~=l z$fS+YQyErY*vatm`)9DCL(k8^6@wTk8o(y4Wnh>XTmx2AyLA%7m+#+DG@v*MBy;8c pT?UXs5IFYyJeWo%7zba(0RWt9G$oT4y{G^H002ovPDHLkV1nS74Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0Axu-K~yLeV|;sz;XeZjfQbaPV5M*kLYBBKLY9MT zcz2wU0a*fOGe`_12Lo^oAOUnu=!!vVSU?0aK-Pq8GE5DM4KP7`G=>J4GmvdUHULEf pOfgIWHcfC1=!$V^Vx)OY0{~v*D#slo71{s*002ovPDHLkV1jLYy!8M8 literal 0 HcmV?d00001 diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css new file mode 100644 index 000000000000..d7b75c404782 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css @@ -0,0 +1,326 @@ +body { + margin: 0 0 5px 5px; +} + +ul { + margin: 0; +} + +li { + list-style-type: none; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.navigator-selected { + background: #ffa500; +} + +.wrapper { + position: absolute; + top: 60px; + bottom: 0; + left: 400px; + right: 0; + overflow: auto; +} + +.navigator-root { + position: absolute; + top: 60px; + bottom: 0; + left: 0; + width: 400px; + overflow-y: auto; +} + +.suite { + margin: 0 10px 10px 0; + background-color: #fff8dc; +} + +.suite-name { + padding-left: 10px; + font-size: 25px; + font-family: Times, sans-serif; +} + +.main-panel-header { + padding: 5px; + background-color: #9FB4D9; /*afeeee*/; + font-family: monospace; + font-size: 18px; +} + +.main-panel-content { + padding: 5px; + margin-bottom: 10px; + background-color: #DEE8FC; /*d0ffff*/; +} + +.rounded-window { + border-radius: 10px; + border-style: solid; + border-width: 1px; +} + +.rounded-window-top { + border-top-right-radius: 10px 10px; + border-top-left-radius: 10px 10px; + border-style: solid; + border-width: 1px; + overflow: auto; +} + +.light-rounded-window-top { + border-top-right-radius: 10px 10px; + border-top-left-radius: 10px 10px; +} + +.rounded-window-bottom { + border-style: solid; + border-width: 0 1px 1px 1px; + border-bottom-right-radius: 10px 10px; + border-bottom-left-radius: 10px 10px; + overflow: auto; +} + +.method-name { + font-size: 12px; + font-family: monospace; +} + +.method-content { + border-style: solid; + border-width: 0 0 1px 0; + margin-bottom: 10px; + padding-bottom: 5px; + width: 80%; +} + +.parameters { + font-size: 14px; + font-family: monospace; +} + +.stack-trace { + white-space: pre; + font-family: monospace; + font-size: 12px; + font-weight: bold; + margin-top: 0; + margin-left: 20px; +} + +.testng-xml { + font-family: monospace; +} + +.method-list-content { + margin-left: 10px; +} + +.navigator-suite-content { + margin-left: 10px; + font: 12px 'Lucida Grande'; +} + +.suite-section-title { + margin-top: 10px; + width: 80%; + border-style: solid; + border-width: 1px 0 0 0; + font-family: Times, sans-serif; + font-size: 18px; + font-weight: bold; +} + +.suite-section-content { + list-style-image: url(bullet_point.png); +} + +.top-banner-root { + position: absolute; + top: 0; + height: 45px; + left: 0; + right: 0; + padding: 5px; + margin: 0 0 5px 0; + background-color: #0066ff; + font-family: Times, sans-serif; + color: #fff; + text-align: center; +} +.button{ + position: absolute; + margin-left:500px; + margin-top:8px; + background-color: white; + color:#0066ff; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight:bold; + border-color:#0066ff ; + border-radius:25px; + cursor: pointer; + height:30px; + width:150px; + outline:none; + +} + +.top-banner-title-font { + font-size: 25px; +} + +.test-name { + font-family: 'Lucida Grande', sans-serif; + font-size: 16px; +} + +.suite-icon { + padding: 5px; + float: right; + height: 20px; +} + +.test-group { + font: 20px 'Lucida Grande'; + margin: 5px 5px 10px 5px; + border-width: 0 0 1px 0; + border-style: solid; + padding: 5px; +} + +.test-group-name { + font-weight: bold; +} + +.method-in-group { + font-size: 16px; + margin-left: 80px; +} + +table.google-visualization-table-table { + width: 100%; +} + +.reporter-method-name { + font-size: 14px; + font-family: monospace; +} + +.reporter-method-output-div { + padding: 5px; + margin: 0 0 5px 20px; + font-size: 12px; + font-family: monospace; + border-width: 0 0 0 1px; + border-style: solid; +} + +.ignored-class-div { + font-size: 14px; + font-family: monospace; +} + +.ignored-methods-div { + padding: 5px; + margin: 0 0 5px 20px; + font-size: 12px; + font-family: monospace; + border-width: 0 0 0 1px; + border-style: solid; +} + +.border-failed { + border-top-left-radius: 10px 10px; + border-bottom-left-radius: 10px 10px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #f00; +} + +.border-skipped { + border-top-left-radius: 10px 10px; + border-bottom-left-radius: 10px 10px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #edc600; +} + +.border-passed { + border-top-left-radius: 10px 10px; + border-bottom-left-radius: 10px 10px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #19f52d; +} + +.times-div { + text-align: center; + padding: 5px; +} + +.suite-total-time { + font: 16px 'Lucida Grande'; +} + +.configuration-suite { + margin-left: 20px; +} + +.configuration-test { + margin-left: 40px; +} + +.configuration-class { + margin-left: 60px; +} + +.configuration-method { + margin-left: 80px; +} + +.test-method { + margin-left: 100px; +} + +.chronological-class { + background-color: skyblue; + border-style: solid; + border-width: 0 0 1px 1px; +} + +.method-start { + float: right; +} + +.chronological-class-name { + padding: 0 0 0 5px; + color: #008; +} + +.after, .before, .test-method { + font-family: monospace; + font-size: 14px; +} + +.navigator-suite-header { + font-size: 22px; + margin: 0 10px 5px 0; + background-color: #deb887; + text-align: center; +} + +.collapse-all-icon { + padding: 5px; + float: right; +} +/*retro Theme*/ diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js new file mode 100644 index 000000000000..c1a84a35d453 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js @@ -0,0 +1,122 @@ +$(document).ready(function() { + $('a.navigator-link').on("click", function() { + // Extract the panel for this link + var panel = getPanelName($(this)); + + // Mark this link as currently selected + $('.navigator-link').parent().removeClass('navigator-selected'); + $(this).parent().addClass('navigator-selected'); + + showPanel(panel); + }); + + installMethodHandlers('failed'); + installMethodHandlers('skipped'); + installMethodHandlers('passed', true); // hide passed methods by default + + $('a.method').on("click", function() { + showMethod($(this)); + return false; + }); + + // Hide all the panels and display the first one (do this last + // to make sure the click() will invoke the listeners) + $('.panel').hide(); + $('.navigator-link').first().trigger("click"); + + // Collapse/expand the suites + $('a.collapse-all-link').on("click", function() { + var contents = $('.navigator-suite-content'); + if (contents.css('display') == 'none') { + contents.show(); + } else { + contents.hide(); + } + }); +}); + +// The handlers that take care of showing/hiding the methods +function installMethodHandlers(name, hide) { + function getContent(t) { + return $('.method-list-content.' + name + "." + t.attr('panel-name')); + } + + function getHideLink(t, name) { + var s = 'a.hide-methods.' + name + "." + t.attr('panel-name'); + return $(s); + } + + function getShowLink(t, name) { + return $('a.show-methods.' + name + "." + t.attr('panel-name')); + } + + function getMethodPanelClassSel(element, name) { + var panelName = getPanelName(element); + var sel = '.' + panelName + "-class-" + name; + return $(sel); + } + + $('a.hide-methods.' + name).on("click", function() { + var w = getContent($(this)); + w.hide(); + getHideLink($(this), name).hide(); + getShowLink($(this), name).show(); + getMethodPanelClassSel($(this), name).hide(); + }); + + $('a.show-methods.' + name).on("click", function() { + var w = getContent($(this)); + w.show(); + getHideLink($(this), name).show(); + getShowLink($(this), name).hide(); + showPanel(getPanelName($(this))); + getMethodPanelClassSel($(this), name).show(); + }); + + if (hide) { + $('a.hide-methods.' + name).trigger("click"); + } else { + $('a.show-methods.' + name).trigger("click"); + } +} + +function getHashForMethod(element) { + return element.attr('hash-for-method'); +} + +function getPanelName(element) { + return element.attr('panel-name'); +} + +function showPanel(panelName) { + $('.panel').hide(); + var panel = $('.panel[panel-name="' + panelName + '"]'); + panel.show(); +} + +function showMethod(element) { + var hashTag = getHashForMethod(element); + var panelName = getPanelName(element); + showPanel(panelName); + var current = document.location.href; + var base = current.substring(0, current.indexOf('#')) + document.location.href = base + '#' + hashTag; + var newPosition = $(document).scrollTop() - 65; + $(document).scrollTop(newPosition); +} + +function drawTable() { + for (var i = 0; i < suiteTableInitFunctions.length; i++) { + window[suiteTableInitFunctions[i]](); + } + + for (var k in window.suiteTableData) { + var v = window.suiteTableData[k]; + var div = v.tableDiv; + var data = v.tableData + var table = new google.visualization.Table(document.getElementById(div)); + table.draw(data, { + showRowNumber : false + }); + } +} diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css new file mode 100644 index 000000000000..570323ffb8fe --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css @@ -0,0 +1,344 @@ +body { + background-color: whitesmoke; + margin: 0 0 5px 5px; +} +ul { + margin-top: 10px; + margin-left:-10px; +} + li { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + padding:5px 5px; + } + a { + text-decoration: none; + color: black; + font-size: 14px; + } + + a:hover { + color:black ; + text-decoration: underline; + } + + .navigator-selected { + /* #ffa500; Mouse hover color after click Orange.*/ + background:#027368 + } + + .wrapper { + position: absolute; + top: 60px; + bottom: 0; + left: 400px; + right: 0; + margin-right:9px; + overflow: auto;/*imortant*/ + } + + .navigator-root { + position: absolute; + top: 60px; + bottom: 0; + left: 0; + width: 400px; + overflow-y: auto;/*important*/ + } + + .suite { + margin: -5px 10px 10px 5px; + background-color: whitesmoke ;/*Colour of the left bside box*/ + } + + .suite-name { + font-size: 24px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;/*All TEST SUITE*/ + color: white; + } + + .main-panel-header { + padding: 5px; + background-color: #027368; /*afeeee*/; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color:white; + font-size: 18px; + } + + .main-panel-content { + padding: 5px; + margin-bottom: 10px; + background-color: #CCD0D1; /*d0ffff*/; /*Belongs to backGround of rightSide boxes*/ + } + + .rounded-window { + border-style: dotted; + border-width: 1px;/*Border of left Side box*/ + background-color: whitesmoke; + border-radius: 10px; + } + + .rounded-window-top { + border-top-right-radius: 10px 10px; + border-top-left-radius: 10px 10px; + border-style: solid; + border-width: 1px; + overflow: auto;/*Top of RightSide box*/ + } + + .light-rounded-window-top { + background-color: #027368; + padding-left:120px; + border-radius: 10px; + + } + + .rounded-window-bottom { + border-bottom-right-radius: 10px 10px; + border-bottom-left-radius: 10px 10px; + overflow: auto;/*Bottom of rightSide box*/ + } + + .method-name { + font-size: 14px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: bold; + } + + .method-content { + border-style: solid; + border-width: 0 0 1px 0; + margin-bottom: 10px; + padding-bottom: 5px; + width: 100%; + } + + .parameters { + font-size: 14px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + .stack-trace { + white-space: pre; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-weight: bold; + margin-top: 0; + margin-left: 20px; /*Error Stack Trace Message*/ + } + + .testng-xml { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + .method-list-content { + margin-left: 10px; + } + + .navigator-suite-content { + margin-left: 10px; + font: 12px 'Lucida Grande'; + } + + .suite-section-title { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight:bold; + background-color: #8C8887; + margin-left: -10px; + margin-top:10px; + padding:6px; + } + + .suite-section-content { + list-style-image: url(bullet_point.png); + background-color: whitesmoke; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + overflow: hidden; + } + + .top-banner-root { + position: absolute; + top: 0; + height: 45px; + left: 0; + right: 0; + padding: 5px; + margin: 0 0 5px 0; + background-color: #027368; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 18px; + color: #fff; + text-align: center;/*Belongs to the Top of Report*//*Status: - Completed*/ + } + + .top-banner-title-font { + font-size: 25px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 3px; + float: right; + } + + .test-name { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + } + + .suite-icon { + padding: 5px; + float: right; + height: 20px; + } + + .test-group { + font: 20px 'Lucida Grande'; + margin: 5px 5px 10px 5px; + border-width: 0 0 1px 0; + border-style: solid; + padding: 5px; + } + + .test-group-name { + font-weight: bold; + } + + .method-in-group { + font-size: 16px; + margin-left: 80px; + } + + table.google-visualization-table-table { + width: 100%; + } + + .reporter-method-name { + font-size: 14px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + .reporter-method-output-div { + padding: 5px; + margin: 0 0 5px 20px; + font-size: 12px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + border-width: 0 0 0 1px; + border-style: solid; + } + + .ignored-class-div { + font-size: 14px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + .ignored-methods-div { + padding: 5px; + margin: 0 0 5px 20px; + font-size: 12px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + border-width: 0 0 0 1px; + border-style: solid; + } + + .border-failed { + border-radius:2px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #F20505; + } + + .border-skipped { + border-radius:2px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #F2BE22; + } + + .border-passed { + border-radius:2px; + border-style: solid; + border-width: 0 0 0 10px; + border-color: #038C73; + } + + .times-div { + text-align: center; + padding: 5px; + } + + .suite-total-time { + font: 16px 'Lucida Grande'; + } + + .configuration-suite { + margin-left: 20px; + } + + .configuration-test { + margin-left: 40px; + } + + .configuration-class { + margin-left: 60px; + } + + .configuration-method { + margin-left: 80px; + } + + .test-method { + margin-left: 100px; + } + + .chronological-class { + background-color: #CCD0D1; + border-width: 0 0 1px 1px;/*Chronological*/ + } + + .method-start { + float: right; + } + + .chronological-class-name { + padding: 0 0 0 5px; + margin-top:5px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #008; + } + + .after, .before, .test-method { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + margin-top:5px; + } + + .navigator-suite-header { + font-size: 18px; + margin: 0px 10px 10px 5px; + padding: 5px; + border-radius: 10px; + background-color: #027368; + color: white; + font-weight:bold; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + text-align: center; /*All Suites on top of left box*//*Status: -Completed*/ + } + + .collapse-all-icon { + padding: 3px; + float: right; + } + .button{ + position: absolute; + margin-left:500px; + margin-top:8px; + background-color: white; + color:#027368; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight:bold; + border-color:#027368; + border-radius:25px; + cursor: pointer; + height:30px; + width:150px; + outline: none; +} +/*Author: - Akhil Gullapalli*/ \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js new file mode 100644 index 000000000000..5342859fa4df --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js @@ -0,0 +1,76 @@ +window.onload = function () { + let cookies = document.cookie; + let cookieValue = cookies.split('='); + if (cookieValue[1] === 'null' || localStorage.getItem('Theme') === 'null') { + document.getElementById('retro').setAttribute('disabled', 'false'); + } else if (cookieValue[1] === 'Switch Ultra Theme' || + localStorage.getItem('Theme') === 'Switch Ultra Theme') { + document.getElementById('button').innerText = "Switch Retro Theme"; + document.getElementById('retro').setAttribute('disabled', 'false'); + + } else if (cookieValue[1] === 'Switch Retro Theme' || + localStorage.getItem('Theme') === 'Switch Retro Theme') { + if (cookieValue[1] === 'Switch Ultra Theme' || + localStorage.getItem('Theme') === 'Switch Ultra Theme') { + document.getElementById('button').innerText = "Switch Retro Theme"; + document.getElementById('retro').setAttribute('disabled', 'false'); + + document.getElementById('button').innerText = "Switch Ultra Theme"; + document.getElementById('retro').removeAttribute('disabled'); + document.getElementById('ultra').setAttribute('disabled', 'false'); + localStorage.setItem('Theme', select); + + } else if (select === 'Switch Ultra Theme') { + document.getElementById('button').innerText = "Switch Retro Theme"; + document.getElementById('ultra').removeAttribute('disabled'); + document.getElementById('retro').setAttribute('disabled', 'false'); + localStorage.setItem('Theme', select); + } + } else if (cookieValue[1] === 'Switch Retro Theme' || + localStorage.getItem('Theme') === 'Switch Retro Theme') { + document.getElementById('button').innerText = "Switch Ultra Theme"; + document.getElementById('ultra').setAttribute('disabled', 'false'); + } +} +document.getElementById('button').onclick = function () { + let select = document.getElementById('button').innerText; + if (select === 'Switch Retro Theme') { + let d = new Date(); + days = 365; + d.setTime(+d + (days * 86400000)); //24 * 60 * 60 * 1000 + document.cookie = "Theme =" + select + "; expires=" + d.toGMTString() + ";"; + document.getElementById('button').innerText = "Switch Ultra Theme"; + document.getElementById('retro').removeAttribute('disabled'); + document.getElementById('ultra').setAttribute('disabled', 'false'); + localStorage.setItem('Theme', select); + + } else if (select === 'Switch Ultra Theme') { + let d = new Date(); + days = 365; + d.setTime(+d + (days * 86400000)); //24 * 60 * 60 * 1000 + document.cookie = "Theme =" + select + "; expires=" + d.toGMTString() + ";"; + document.getElementById('button').innerText = "Switch Retro Theme"; + document.getElementById('ultra').removeAttribute('disabled'); + document.getElementById('retro').setAttribute('disabled', 'false'); + localStorage.setItem('Theme', select); + } +} +//Function to mouse hovering affect. +document.getElementById('button').onmouseover = function () { + document.getElementById('button').style.borderRadius = "25px"; + document.getElementById('button').style.width = "180px"; + document.getElementById('button').style.height = "45px"; + document.getElementById('button').style.marginTop = "1px"; + +} +//Function to mouse out affect +document.getElementById('button').onmouseout = function () { + document.getElementById('button').style.borderRadius = "25px"; + document.getElementById('button').style.width = "150px"; + document.getElementById('button').style.height = "30px"; + document.getElementById('button').style.marginTop = "8px"; + +} + +//This is the file where we handle the switching of the Themes. +/*Author:- Akhil Gullapalli*/ diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml new file mode 100644 index 000000000000..efe99aa4b70a --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css new file mode 100644 index 000000000000..5124ba863b37 --- /dev/null +++ b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css @@ -0,0 +1,9 @@ +.invocation-failed, .test-failed { background-color: #DD0000; } +.invocation-percent, .test-percent { background-color: #006600; } +.invocation-passed, .test-passed { background-color: #00AA00; } +.invocation-skipped, .test-skipped { background-color: #CCCC00; } + +.main-page { + font-size: x-large; +} + diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java index d2431a5dd327..b2ab667200cd 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java @@ -319,7 +319,7 @@ public void twoCachesForSameEndpointShareRoutingMapStorage() throws Exception { cacheB.close(); } - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) .as("close() releases the shared cache reference") .isZero(); } @@ -394,14 +394,14 @@ public void closeIsIdempotent() throws Exception { RxCollectionCache mockColl = Mockito.mock(RxCollectionCache.class); RxPartitionKeyRangeCache c = new RxPartitionKeyRangeCache(mockClient, mockColl, endpoint); - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) .isEqualTo(1); c.close(); c.close(); // second call must be a no-op c.close(); - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint.toString())) + assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) .as("repeated close() must not drive refcount negative") .isZero(); } diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java index c920c92696aa..d10b49707d85 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java @@ -9,6 +9,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -49,7 +50,7 @@ public void after() { @Test(groups = "unit") public void acquireReturnsSameInstanceForSameEndpoint() { SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-1.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-1.documents.azure.com:443/"); AsyncCacheNonBlocking a = registry.acquire(endpoint); AsyncCacheNonBlocking b = registry.acquire(endpoint); @@ -67,8 +68,8 @@ public void acquireReturnsSameInstanceForSameEndpoint() { @Test(groups = "unit") public void acquireReturnsDifferentInstanceForDifferentEndpoints() { SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String e1 = "https://test-acct-share-2a.documents.azure.com:443/"; - String e2 = "https://test-acct-share-2b.documents.azure.com:443/"; + URI e1 = URI.create("https://test-acct-share-2a.documents.azure.com:443/"); + URI e2 = URI.create("https://test-acct-share-2b.documents.azure.com:443/"); AsyncCacheNonBlocking a = registry.acquire(e1); AsyncCacheNonBlocking b = registry.acquire(e2); @@ -81,10 +82,39 @@ public void acquireReturnsDifferentInstanceForDifferentEndpoints() { } } + @Test(groups = "unit") + public void acquireTreatsHostCaseInsensitivelyMatchingUriEquals() { + // URI.equals is case-insensitive on host (RFC 3986). Two clients built with + // mixed-case host names must collapse to a single shared cache entry — this + // is the reason we key on URI rather than URI.toString(). + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + URI lower = URI.create("https://test-acct-share-case.documents.azure.com:443/"); + URI mixed = URI.create("https://Test-Acct-Share-Case.documents.azure.com:443/"); + + // Sanity: URI.equals must consider these equal; otherwise the test below + // would silently regress without anyone noticing. + assertThat(lower).isEqualTo(mixed); + + AsyncCacheNonBlocking a = registry.acquire(lower); + AsyncCacheNonBlocking b = registry.acquire(mixed); + + try { + assertThat(a) + .as("lower-case and mixed-case host must share the same registry entry") + .isSameAs(b); + assertThat(registry.referenceCount(lower)).isEqualTo(2); + assertThat(registry.referenceCount(mixed)).isEqualTo(2); + } finally { + registry.release(lower, a); + registry.release(mixed, b); + } + assertThat(registry.referenceCount(lower)).isZero(); + } + @Test(groups = "unit") public void releaseEvictsAtZeroRefcount() { SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-3.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-3.documents.azure.com:443/"); AsyncCacheNonBlocking a = registry.acquire(endpoint); AsyncCacheNonBlocking b = registry.acquire(endpoint); @@ -111,7 +141,7 @@ public void releaseIsIdempotentWhenSuppliedSameCacheRepeatedly() { // The registry's contract is that calling release with a cache instance // that is not currently registered (e.g. already-evicted) is a no-op. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-4.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-4.documents.azure.com:443/"); AsyncCacheNonBlocking a = registry.acquire(endpoint); registry.release(endpoint, a); @@ -127,7 +157,7 @@ public void releaseIsNoOpWhenCacheIsNotTheRegisteredInstance() { // After eviction and re-acquire, the registry holds a different instance. // Releasing the old (stale) reference must not affect the new registered entry. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-5.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-5.documents.azure.com:443/"); AsyncCacheNonBlocking stale = registry.acquire(endpoint); registry.release(endpoint, stale); @@ -160,7 +190,7 @@ public void nullEndpointReturnsIsolatedCacheAndDoesNotEnterRegistry() { @Test(groups = "unit") public void disabledFlagReturnsIsolatedCachesAndPreservesRegistryEmpty() { SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-6.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-6.documents.azure.com:443/"); int before = registry.registeredEndpointCount(); System.setProperty(ENABLE_FLAG, "false"); @@ -184,7 +214,7 @@ public void disabledFlagReturnsIsolatedCachesAndPreservesRegistryEmpty() { @Test(groups = "unit") public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Exception { SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); - String endpoint = "https://test-acct-share-7.documents.azure.com:443/"; + URI endpoint = URI.create("https://test-acct-share-7.documents.azure.com:443/"); int threads = 32; int opsPerThread = 200; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index bcc088710cc9..a1e04894d2c4 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -62,7 +62,7 @@ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closea private final RxDocumentClientImpl client; private final RxCollectionCache collectionCache; private final DiagnosticsClientContext clientContext; - private final String sharedCacheEndpointKey; + private final URI sharedCacheEndpointKey; private final AtomicBoolean closed = new AtomicBoolean(false); public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { @@ -74,7 +74,7 @@ public RxPartitionKeyRangeCache( RxCollectionCache collectionCache, URI serviceEndpoint) { - this.sharedCacheEndpointKey = serviceEndpoint == null ? null : serviceEndpoint.toString(); + this.sharedCacheEndpointKey = serviceEndpoint; this.routingMapCache = SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey); this.client = client; this.collectionCache = collectionCache; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java index cfcc7706d7cf..4d880533bbae 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URI; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -28,17 +29,25 @@ * {@code /pkranges} fetch per (account, container) at any time, even across * clients.

* - *

Lifecycle. Callers obtain a shared cache via {@link #acquire(String)} - * during construction and return it via {@link #release(String, AsyncCacheNonBlocking)} + *

Key identity. {@link URI} is used directly (not {@link URI#toString()}) + * so {@link URI#equals(Object)}'s case-insensitive host comparison applies — two + * clients built with {@code https://Acct.documents.azure.com/} and + * {@code https://acct.documents.azure.com/} share a single cache entry as + * intended. Port, scheme, and path are compared as the user supplied them + * (matching the Python SDK's behaviour: do not auto-normalise the user's input + * URL beyond what the language's URI type already does).

+ * + *

Lifecycle. Callers obtain a shared cache via {@link #acquire(URI)} + * during construction and return it via {@link #release(URI, AsyncCacheNonBlocking)} * during {@code close()}. A per-entry refcount tracks live callers; when the * count reaches zero the entry is evicted so an idle endpoint does not pin * memory forever.

* *

Opt-out. Setting the system property * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false} disables - * sharing; each {@link #acquire(String)} returns a fresh, isolated cache so + * sharing; each {@link #acquire(URI)} returns a fresh, isolated cache so * behaviour matches the pre-sharing implementation. The opt-out is read once - * per {@link #acquire(String)} call so a test can toggle the property between + * per {@link #acquire(URI)} call so a test can toggle the property between * client constructions without restarting the JVM.

* *

Concurrency. All state transitions go through @@ -51,7 +60,7 @@ public final class SharedRoutingMapCacheRegistry { private static final SharedRoutingMapCacheRegistry INSTANCE = new SharedRoutingMapCacheRegistry(); - private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); private SharedRoutingMapCacheRegistry() { } @@ -66,15 +75,15 @@ public static SharedRoutingMapCacheRegistry getInstance() { * *

If {@code endpoint} is {@code null} or sharing is disabled via * {@link Configs#isSharedPartitionKeyRangeCacheEnabled()}, returns a fresh - * isolated cache that the caller fully owns. {@link #release(String, AsyncCacheNonBlocking)} + * isolated cache that the caller fully owns. {@link #release(URI, AsyncCacheNonBlocking)} * is still safe to call on such a cache (it is a no-op).

* - * @param endpoint The Cosmos service endpoint URL (e.g. - * {@code "https://my-account.documents.azure.com:443/"}), + * @param endpoint The Cosmos service endpoint URI (e.g. + * {@code https://my-account.documents.azure.com:443/}), * or {@code null} for an isolated cache. * @return The shared (or isolated) cache instance to use as routing-map storage. */ - public AsyncCacheNonBlocking acquire(String endpoint) { + public AsyncCacheNonBlocking acquire(URI endpoint) { if (endpoint == null || !Configs.isSharedPartitionKeyRangeCacheEnabled()) { // Caller-owned cache; never enters the shared map. return new AsyncCacheNonBlocking<>(); @@ -95,7 +104,7 @@ public AsyncCacheNonBlocking acquire(String endpoi /** * Releases a reference to the shared cache previously obtained via - * {@link #acquire(String)}. When the last reference is released the + * {@link #acquire(URI)}. When the last reference is released the * registry entry is evicted. * *

Safe to call when sharing was bypassed (null endpoint or sharing @@ -110,9 +119,9 @@ public AsyncCacheNonBlocking acquire(String endpoi * * @param endpoint The endpoint the cache was acquired for, or {@code null} * if it was an isolated cache. - * @param cache The cache instance returned by {@link #acquire(String)}. + * @param cache The cache instance returned by {@link #acquire(URI)}. */ - public void release(String endpoint, AsyncCacheNonBlocking cache) { + public void release(URI endpoint, AsyncCacheNonBlocking cache) { if (endpoint == null || cache == null) { return; } @@ -145,7 +154,7 @@ int registeredEndpointCount() { * Test-only: current refcount for an endpoint, or {@code 0} if no entry * is registered. Visible-for-testing. */ - int referenceCount(String endpoint) { + int referenceCount(URI endpoint) { Entry entry = entries.get(endpoint); return entry == null ? 0 : entry.refCount.get(); } From 75f93d53b38ec4927120ac7fada5bdee80a62bae Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 09:31:00 -0700 Subject: [PATCH 3/8] Cosmos Java: keep registry key as serviceEndpoint URI (not _rid) for cross-SDK consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed via cross-SDK review that both peer Cosmos SDKs key sharing on the user-supplied account endpoint URL, not on the account _rid: - Python (sdk/cosmos/azure-cosmos/azure/cosmos/_routing/_routing_map_provider_common.py): _resolve_endpoint() returns client.url_connection (the input endpoint string) with no normalisation and no _rid lookup. - Rust (sdk/cosmos/azure_data_cosmos_driver/src/models/account_reference.rs): AccountReference identity is endpoint-only via AccountEndpoint(Url) which Hash/Eq on the Url; PartialEq deliberately excludes credentials and backup endpoints. No _rid involvement. This SDK should match. The "regional vs global endpoint to the same account" case stays a known fragmentation case across all three SDKs rather than something Java solves alone via _rid. Why _rid keying was rejected after exploration: 1. Diverges from Python and Rust — increases mental-model and maintenance cost for cross-SDK contributors. 2. DatabaseAccount.getResourceId() returns the empty string in emulator and some service paths where the account JSON has no _rid (Resource.java:130 delegates to JsonSerializable.getString(R_ID)). Would silently fall back and fragment differently than peers. 3. Brittle to init reorders: today GlobalEndpointManager.init() runs before cache construction, but any future refactor (lazy account fetch, offline-mode init) would silently break sharing. Endpoint URI is constructor-immutable; _rid depends on a successful prior network call. Final shape: - Registry keyed by URI (case-insensitive host via URI.equals). - RxPartitionKeyRangeCache 3-arg ctor takes (client, collectionCache, serviceEndpoint URI). Two-arg ctor delegates with client.getServiceEndpoint(). - JavaDoc on SharedRoutingMapCacheRegistry now explicitly documents the cross-SDK alignment and the regional-endpoint fragmentation tradeoff. All 34 cache unit tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../caches/RxPartitionKeyRangeCache.java | 10 ++++----- .../caches/SharedRoutingMapCacheRegistry.java | 21 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index a1e04894d2c4..69b7e5523706 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -49,11 +49,11 @@ * *

The underlying routing-map storage ({@link AsyncCacheNonBlocking}) is * obtained from {@link SharedRoutingMapCacheRegistry} keyed by the service - * endpoint, so multiple {@code CosmosClient} instances targeting the same - * account share a single routing-map cache. {@link #close()} releases this - * instance's reference; the shared cache is evicted only when the last - * reference is released. The fetching logic (network call, collection - * resolution, diagnostics) remains per-client.

+ * endpoint URI. Multiple {@code CosmosClient} instances targeting the same + * service endpoint share a single routing-map cache. {@link #close()} + * releases this instance's reference; the shared cache is evicted only + * when the last reference is released. The fetching logic (network call, + * collection resolution, diagnostics) remains per-client.

**/ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closeable { private final Logger logger = LoggerFactory.getLogger(RxPartitionKeyRangeCache.class); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java index 4d880533bbae..fed1291dc1e5 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -14,7 +14,7 @@ /** * Process-wide registry of {@link AsyncCacheNonBlocking} instances that hold * the cached partition-key-range routing maps, keyed by the Cosmos service - * endpoint (account) the cache belongs to. + * endpoint URI. * *

Historically, every {@link RxPartitionKeyRangeCache} owned its own * private {@link AsyncCacheNonBlocking}. When many {@code CosmosAsyncClient} @@ -29,13 +29,22 @@ * {@code /pkranges} fetch per (account, container) at any time, even across * clients.

* - *

Key identity. {@link URI} is used directly (not {@link URI#toString()}) - * so {@link URI#equals(Object)}'s case-insensitive host comparison applies — two + *

Key identity. The {@link URI} is used directly so + * {@link URI#equals(Object)}'s case-insensitive host comparison applies — two * clients built with {@code https://Acct.documents.azure.com/} and * {@code https://acct.documents.azure.com/} share a single cache entry as - * intended. Port, scheme, and path are compared as the user supplied them - * (matching the Python SDK's behaviour: do not auto-normalise the user's input - * URL beyond what the language's URI type already does).

+ * intended.

+ * + *

Cross-SDK consistency. The peer Cosmos DB SDKs key sharing on the + * user-supplied account endpoint URL: Python uses {@code client.url_connection} + * (raw string compare); Rust uses {@code AccountEndpoint(Url)} (URL-based + * equality). This implementation matches that contract. As a consequence, + * two clients to the same account that bootstrap from different regional + * endpoints (e.g. {@code my-acct-westus.documents.azure.com} vs + * {@code my-acct.documents.azure.com}) do not share a cache entry. + * Routing this through the account's resource-id ({@code _rid}) would fix the + * regional-endpoint case but would diverge from Python/Rust, and the + * {@code _rid} is not always populated (notably for the emulator).

* *

Lifecycle. Callers obtain a shared cache via {@link #acquire(URI)} * during construction and return it via {@link #release(URI, AsyncCacheNonBlocking)} From 05f678049c836e4ece3d44656075d666cc542b75 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 09:54:03 -0700 Subject: [PATCH 4/8] Cosmos Java: add PhantomReference-based leak safety net for unclosed clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this safety net, a customer that forgets to call CosmosClient.close() would pin the shared partition-key-range cache entry for the lifetime of the JVM. The owning RxPartitionKeyRangeCache holds a strong reference to the shared AsyncCacheNonBlocking and the registry's refcount stays > 0 forever. Peer SDKs handle this: - Python: __del__ in PartitionKeyRangeCache calls release() as a GC fallback (sdk/cosmos/azure-cosmos/azure/cosmos/_routing/routing_map_provider.py L192). - Rust: no Drop impl needed — the cache lives as a field on the driver and Rust ownership guarantees cleanup on driver drop. Java cannot use java.lang.ref.Cleaner because azure-cosmos targets Java 8 (verified: sdk/parents/azure-client-sdk-parent/pom.xml 1.8). Solution uses the pre-Cleaner pattern: PhantomReference + ReferenceQueue + daemon reaper thread. All Java 1.2+ APIs. Design - SharedRoutingMapCacheRegistry holds: * ReferenceQueue reaperQueue * Set livePhantoms (concurrent) — critical for correctness: the JVM only enqueues phantoms that are themselves still strongly reachable, so the registry must hold them alive until processed. * One daemon thread (cosmos-shared-pkr-cache-reaper) blocking on reaperQueue.remove(). - acquire(URI endpoint, Object owner): registers an OwnerPhantom on the owner, adds it to livePhantoms, returns AcquireResult { cache, phantom }. - release(URI, cache, PhantomReference) — new 3-arg overload — clears the phantom and removes it from livePhantoms in addition to decrementing the refcount. This is the path RxPartitionKeyRangeCache.close() uses. - When the owner becomes phantom-reachable, the reaper drains the queue, logs a WARN ("Leaked (unclosed) RxPartitionKeyRangeCache detected..."), calls release(endpoint, cache) to decrement refcount, then removes the phantom from livePhantoms. - close() is still the right primary path; the reaper is a safety net that prevents permanent JVM-lifetime cache pinning, not a substitute. Tests - reaperReleasesSharedCacheWhenOwnerIsGarbageCollected: acquires in a helper method (so the test frame cannot keep owner alive), polls referenceCount while forcing System.gc() in a 15s window. Reaper warning is observable in test output. - promptCloseClearsPhantomSoReaperDoesNotDoubleRelease: validates the prompt-close path clears the phantom and a subsequent GC produces no extra release. 36 cache unit tests pass (was 34, +2 new leak tests). Key correctness note in code The first attempt at this had a subtle bug: acquire() returned the phantom in AcquireResult but the registry didn't hold it. Once the test discarded the AcquireResult, the phantom became unreachable and the JVM never enqueued it — the reaper sat idle forever. The livePhantoms set fixes this. The fields/JavaDoc explicitly document the why. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SharedRoutingMapCacheRegistryTest.java | 76 ++++++ .../caches/RxPartitionKeyRangeCache.java | 23 +- .../caches/SharedRoutingMapCacheRegistry.java | 223 +++++++++++++++--- 3 files changed, 292 insertions(+), 30 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java index d10b49707d85..1a529af7f0bd 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java @@ -255,4 +255,80 @@ public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Excep // All acquires matched by releases → refcount must be zero and entry evicted. assertThat(registry.referenceCount(endpoint)).isZero(); } + + @Test(groups = "unit") + public void reaperReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Exception { + // Simulates a customer that forgot to call CosmosClient.close(). The owning + // object (here: an opaque Object stand-in for RxPartitionKeyRangeCache) is + // discarded; the registry's PhantomReference is enqueued on the next GC; the + // reaper drains it and decrements the refcount. + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + URI endpoint = URI.create("https://test-acct-leak-1.documents.azure.com:443/"); + + // Acquire and immediately discard the owner in a separate stack frame so the + // method's frame cannot keep a hidden strong reference alive past `owner = null`. + // We also avoid holding the cache in this method to remove any chance of + // accidentally keeping the entry alive via the test frame. + acquireAndLeakOwner(registry, endpoint); + assertThat(registry.referenceCount(endpoint)).isEqualTo(1); + + // Wait for the reaper to observe the GC and decrement the refcount. + boolean released = false; + long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(15); + while (System.nanoTime() < deadlineNanos) { + System.gc(); + System.runFinalization(); + Thread.sleep(100); + if (registry.referenceCount(endpoint) == 0) { + released = true; + break; + } + } + + assertThat(released) + .as("Reaper should release the shared cache reference after the owner is GC'd " + + "(refcount=%d)", registry.referenceCount(endpoint)) + .isTrue(); + } + + /** + * Helper that allocates an owner, registers it with the registry, and returns — + * letting the owner immediately become eligible for GC. Living in its own stack + * frame guarantees the caller's frame cannot keep the owner alive. + */ + private static void acquireAndLeakOwner(SharedRoutingMapCacheRegistry registry, URI endpoint) { + Object owner = new Object(); + registry.acquire(endpoint, owner); + // owner falls out of scope on return; nothing else references it. + } + + @Test(groups = "unit") + public void promptCloseClearsPhantomSoReaperDoesNotDoubleRelease() throws Exception { + // After a prompt close() the registry's refcount is already zero and the + // entry already evicted. Even if the GC enqueues the phantom later, the + // reaper's release(endpoint, cache) is a no-op because the registry no + // longer holds that cache instance — exercised by referenceCount staying + // at zero across a forced GC cycle. + SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + URI endpoint = URI.create("https://test-acct-leak-2.documents.azure.com:443/"); + + acquireAndPromptlyClose(registry, endpoint); + assertThat(registry.referenceCount(endpoint)).isZero(); + + // Force GC; refcount must remain zero, no exception. + for (int i = 0; i < 5; i++) { + System.gc(); + Thread.sleep(50); + } + assertThat(registry.referenceCount(endpoint)).isZero(); + } + + private static void acquireAndPromptlyClose(SharedRoutingMapCacheRegistry registry, URI endpoint) { + Object owner = new Object(); + SharedRoutingMapCacheRegistry.AcquireResult result = registry.acquire(endpoint, owner); + // Simulate the prompt RxPartitionKeyRangeCache.close() path via the + // phantom-aware overload — this clears the phantom and removes it from the + // live-phantom set so the reaper does not later try to double-release. + registry.release(endpoint, result.cache, result.ownerPhantom); + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index 69b7e5523706..79eb38e9356d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -32,6 +32,7 @@ import reactor.core.publisher.Mono; import java.io.Closeable; +import java.lang.ref.PhantomReference; import java.net.URI; import java.time.Instant; import java.util.ArrayList; @@ -64,6 +65,16 @@ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closea private final DiagnosticsClientContext clientContext; private final URI sharedCacheEndpointKey; private final AtomicBoolean closed = new AtomicBoolean(false); + /** + * Phantom registered with {@link SharedRoutingMapCacheRegistry} so that an + * unclosed (leaked) cache instance gets cleaned up by the reaper when it + * becomes phantom-reachable. Held as a field to keep the phantom itself + * reachable for as long as this instance is reachable — otherwise the GC + * would collect the phantom before its referent and the queue would never + * be notified. May be {@code null} for isolated caches. + */ + @SuppressWarnings("unused") // referenced via the field for liveness; not read at runtime. + private final PhantomReference ownerPhantom; public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { this(client, collectionCache, client == null ? null : client.getServiceEndpoint()); @@ -75,7 +86,10 @@ public RxPartitionKeyRangeCache( URI serviceEndpoint) { this.sharedCacheEndpointKey = serviceEndpoint; - this.routingMapCache = SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey); + SharedRoutingMapCacheRegistry.AcquireResult acquired = + SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey, this); + this.routingMapCache = acquired.cache; + this.ownerPhantom = acquired.ownerPhantom; this.client = client; this.collectionCache = collectionCache; this.clientContext = client; @@ -85,11 +99,16 @@ public RxPartitionKeyRangeCache( * Releases this instance's reference to the shared routing-map cache. * Safe to call multiple times; only the first call has an effect. * After {@code close()} the instance must not be used further. + * + *

Also clears the {@link PhantomReference} registered on construction + * so the registry's reaper does not attempt a redundant release later + * when this instance is GC'd.

*/ @Override public void close() { if (closed.compareAndSet(false, true)) { - SharedRoutingMapCacheRegistry.getInstance().release(this.sharedCacheEndpointKey, this.routingMapCache); + SharedRoutingMapCacheRegistry.getInstance().release( + this.sharedCacheEndpointKey, this.routingMapCache, this.ownerPhantom); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java index fed1291dc1e5..6c08cd35c4af 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -7,7 +7,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; import java.net.URI; +import java.util.Collections; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -41,23 +46,32 @@ * equality). This implementation matches that contract. As a consequence, * two clients to the same account that bootstrap from different regional * endpoints (e.g. {@code my-acct-westus.documents.azure.com} vs - * {@code my-acct.documents.azure.com}) do not share a cache entry. - * Routing this through the account's resource-id ({@code _rid}) would fix the - * regional-endpoint case but would diverge from Python/Rust, and the - * {@code _rid} is not always populated (notably for the emulator).

+ * {@code my-acct.documents.azure.com}) do not share a cache entry.

* - *

Lifecycle. Callers obtain a shared cache via {@link #acquire(URI)} + *

Lifecycle. Callers obtain a shared cache via {@link #acquire(URI, Object)} * during construction and return it via {@link #release(URI, AsyncCacheNonBlocking)} * during {@code close()}. A per-entry refcount tracks live callers; when the * count reaches zero the entry is evicted so an idle endpoint does not pin * memory forever.

* + *

Leaked-client safety net. A caller may forget to {@code close()} + * its {@code CosmosAsyncClient}. Without protection the unclosed client would + * keep a strong reference to the shared cache and pin it for the JVM's + * lifetime. To handle that, every {@link #acquire(URI, Object)} also + * registers a {@link PhantomReference} for the owner. When the owner becomes + * unreachable the GC enqueues the phantom; a single daemon reaper thread + * drains the queue and calls {@link #release(URI, AsyncCacheNonBlocking)} + * to clean up. The reaper is not a substitute for + * {@link java.io.Closeable#close()} (no guaranteed promptness) but it + * prevents the cache from leaking forever. This mirrors the safety net + * Python provides via its {@code __del__} fallback and Rust gets for free + * via {@code Drop}; we cannot use {@code java.lang.ref.Cleaner} because + * this SDK still supports Java 8.

+ * *

Opt-out. Setting the system property * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false} disables - * sharing; each {@link #acquire(URI)} returns a fresh, isolated cache so - * behaviour matches the pre-sharing implementation. The opt-out is read once - * per {@link #acquire(URI)} call so a test can toggle the property between - * client constructions without restarting the JVM.

+ * sharing; each {@link #acquire(URI, Object)} returns a fresh, isolated cache + * (no registry entry, no phantom registration).

* *

Concurrency. All state transitions go through * {@link ConcurrentHashMap#compute(Object, java.util.function.BiFunction)}, @@ -71,7 +85,29 @@ public final class SharedRoutingMapCacheRegistry { private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + /** + * Queue the GC enqueues {@link OwnerPhantom} instances onto when their + * referent (the owning {@link RxPartitionKeyRangeCache}) becomes + * unreachable without {@code close()} having been called. + */ + private final ReferenceQueue reaperQueue = new ReferenceQueue<>(); + + /** + * Strong-references every live {@link OwnerPhantom} so the JVM does not + * collect the phantom before its referent. The GC only enqueues phantoms + * that are themselves still reachable; without this set the phantoms + * registered in {@link #acquire(URI, Object)} would be garbage-collected + * together with their owners and the reaper would never observe the leak. + * Entries are removed either by the reaper after processing or by + * {@link #release(URI, AsyncCacheNonBlocking, PhantomReference)} on prompt close. + */ + private final Set livePhantoms = + Collections.newSetFromMap(new ConcurrentHashMap<>()); + private SharedRoutingMapCacheRegistry() { + Thread reaper = new Thread(this::runReaper, "cosmos-shared-pkr-cache-reaper"); + reaper.setDaemon(true); + reaper.start(); } public static SharedRoutingMapCacheRegistry getInstance() { @@ -84,18 +120,25 @@ public static SharedRoutingMapCacheRegistry getInstance() { * *

If {@code endpoint} is {@code null} or sharing is disabled via * {@link Configs#isSharedPartitionKeyRangeCacheEnabled()}, returns a fresh - * isolated cache that the caller fully owns. {@link #release(URI, AsyncCacheNonBlocking)} - * is still safe to call on such a cache (it is a no-op).

+ * isolated cache that the caller fully owns. + * {@link #release(URI, AsyncCacheNonBlocking)} is still safe to call on + * such a cache (it is a no-op).

+ * + *

When {@code owner} is non-null and sharing is enabled, a + * {@link PhantomReference} to {@code owner} is registered so that an + * unreferenced (leaked) owner triggers a deferred release.

* - * @param endpoint The Cosmos service endpoint URI (e.g. - * {@code https://my-account.documents.azure.com:443/}), - * or {@code null} for an isolated cache. - * @return The shared (or isolated) cache instance to use as routing-map storage. + * @param endpoint The Cosmos service endpoint URI, or {@code null} for an isolated cache. + * @param owner The object whose unreachability should trigger a deferred release + * (typically the {@link RxPartitionKeyRangeCache} caller). May be {@code null} + * to skip phantom registration (e.g. tests calling acquire directly). + * @return A handle that exposes the shared cache instance plus a token used by + * {@link #release(URI, AsyncCacheNonBlocking)} for prompt cleanup. */ - public AsyncCacheNonBlocking acquire(URI endpoint) { + public AcquireResult acquire(URI endpoint, Object owner) { if (endpoint == null || !Configs.isSharedPartitionKeyRangeCacheEnabled()) { // Caller-owned cache; never enters the shared map. - return new AsyncCacheNonBlocking<>(); + return new AcquireResult(new AsyncCacheNonBlocking<>(), null); } Entry entry = entries.compute(endpoint, (key, existing) -> { @@ -108,27 +151,59 @@ public AsyncCacheNonBlocking acquire(URI endpoint) existing.refCount.incrementAndGet(); return existing; }); - return entry.cache; + + OwnerPhantom phantom = null; + if (owner != null) { + phantom = new OwnerPhantom(owner, reaperQueue, endpoint, entry.cache); + // The phantom MUST be strongly reachable until it is either processed by + // the reaper or cleared on prompt close, otherwise the GC will collect it + // alongside the owner without ever enqueueing it. + livePhantoms.add(phantom); + } + return new AcquireResult(entry.cache, phantom); + } + + /** + * Convenience overload used by tests that do not need phantom registration. + */ + AsyncCacheNonBlocking acquire(URI endpoint) { + return acquire(endpoint, null).cache; } /** * Releases a reference to the shared cache previously obtained via - * {@link #acquire(URI)}. When the last reference is released the + * {@link #acquire(URI, Object)}. When the last reference is released the * registry entry is evicted. * *

Safe to call when sharing was bypassed (null endpoint or sharing * disabled): the call is a no-op if the supplied cache is not the one - * currently registered for {@code endpoint}. This makes the API safe to - * call unconditionally from client close paths.

+ * currently registered for {@code endpoint}.

* - *

Idempotency of the per-caller release contract is the caller's - * responsibility (typically guarded by an {@code AtomicBoolean} in - * {@link RxPartitionKeyRangeCache}). This method itself is safe to call - * concurrently from multiple threads for distinct callers.

+ *

If {@code phantom} is non-null it is cleared and dropped from the + * registry's live-phantom set, so the reaper does not later double-release + * the same reference.

* * @param endpoint The endpoint the cache was acquired for, or {@code null} * if it was an isolated cache. - * @param cache The cache instance returned by {@link #acquire(URI)}. + * @param cache The cache instance returned by {@link #acquire(URI, Object)}. + * @param phantom The phantom returned by {@link #acquire(URI, Object)}, or + * {@code null} if none was registered. + */ + public void release(URI endpoint, + AsyncCacheNonBlocking cache, + PhantomReference phantom) { + if (phantom instanceof OwnerPhantom) { + OwnerPhantom op = (OwnerPhantom) phantom; + livePhantoms.remove(op); + op.clear(); + } + release(endpoint, cache); + } + + /** + * Internal release used by the reaper (which already holds the phantom + * reference it processed) and by the two-arg overload above. Test code + * also calls this overload directly to simulate close paths. */ public void release(URI endpoint, AsyncCacheNonBlocking cache) { if (endpoint == null || cache == null) { @@ -151,9 +226,42 @@ public void release(URI endpoint, AsyncCacheNonBlocking ref = reaperQueue.remove(); + if (ref instanceof OwnerPhantom) { + OwnerPhantom phantom = (OwnerPhantom) ref; + logger.warn( + "Leaked (unclosed) RxPartitionKeyRangeCache detected for endpoint [{}]" + + " — releasing shared cache reference via reaper. Always close CosmosClient" + + " / CosmosAsyncClient to avoid relying on this safety net.", + phantom.endpoint); + try { + release(phantom.endpoint, phantom.cache); + } catch (RuntimeException ex) { + logger.error("Reaper failed to release shared cache for endpoint [{}]", + phantom.endpoint, ex); + } finally { + livePhantoms.remove(phantom); + phantom.clear(); + } + } + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + } + } + /** * Test-only: number of registered endpoints currently held by the registry. - * Visible-for-testing. */ int registeredEndpointCount() { return entries.size(); @@ -161,15 +269,74 @@ int registeredEndpointCount() { /** * Test-only: current refcount for an endpoint, or {@code 0} if no entry - * is registered. Visible-for-testing. + * is registered. */ int referenceCount(URI endpoint) { Entry entry = entries.get(endpoint); return entry == null ? 0 : entry.refCount.get(); } + /** + * Test-only: number of live phantoms currently retained by the registry. + */ + int livePhantomCount() { + return livePhantoms.size(); + } + + /** + * Returned by {@link #acquire(URI, Object)}. Holds the shared cache and + * the {@link PhantomReference} (when one was registered) so the caller + * can clear it on prompt {@code close()} and avoid a redundant reaper + * release later. + */ + public static final class AcquireResult { + public final AsyncCacheNonBlocking cache; + public final PhantomReference ownerPhantom; + + AcquireResult(AsyncCacheNonBlocking cache, + PhantomReference ownerPhantom) { + this.cache = cache; + this.ownerPhantom = ownerPhantom; + } + } + private static final class Entry { final AsyncCacheNonBlocking cache = new AsyncCacheNonBlocking<>(); final AtomicInteger refCount = new AtomicInteger(0); } + + /** + * PhantomReference whose enqueueing on GC of its referent (the owning + * {@link RxPartitionKeyRangeCache}) signals the reaper to release the + * matching shared-cache entry. The captured endpoint + cache are strong + * fields on the phantom itself — that's fine: once the reaper clears the + * phantom, the phantom itself becomes unreachable and the captured cache + * loses one more strong holder. + * + *

Why is this safe? The phantom is registered on the + * {@code RxPartitionKeyRangeCache} (which is the field of the + * {@code RxDocumentClientImpl}). It does not reference the client + * or any field of it. So the phantom does not prevent the owning client + * from being GC'd; it is enqueued exactly when the owning cache + * becomes phantom-reachable.

+ * + *

The phantom is kept reachable by being assigned to a field on the + * owning {@code RxPartitionKeyRangeCache}. If the phantom itself were + * unreachable before its referent became unreachable, the GC would never + * enqueue it. Holding it on the owner ties phantom liveness to owner + * liveness, which is precisely what we want.

+ */ + static final class OwnerPhantom extends PhantomReference { + final URI endpoint; + final AsyncCacheNonBlocking cache; + + OwnerPhantom(Object owner, + ReferenceQueue queue, + URI endpoint, + AsyncCacheNonBlocking cache) { + super(owner, queue); + this.endpoint = endpoint; + this.cache = cache; + } + } } From cbd47a8e52d6b98be97dce2d65d5151d99374e22 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 11:38:09 -0700 Subject: [PATCH 5/8] Cosmos Java: use azure-core ReferenceManager for leaked-client safety net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the bespoke PhantomReference + ReferenceQueue + daemon-thread reaper with com.azure.core.util.ReferenceManager.INSTANCE, the SDK-wide singleton that already encapsulates this pattern. ReferenceManagerImpl: - On Java 9+ delegates reflectively to java.lang.ref.Cleaner. - On Java 8 (our baseline) uses an internal PhantomReference + daemon thread named "azure-sdk-referencemanager" — exactly the same mechanism this PR was reimplementing. Confirmed in test output: the leak WARN is logged on the "azure-sdk-referencemanager" thread, proving the azure-core path is wired. Why this is better: - Reuses supported, well-tested azure-core machinery instead of rolling our own. One thread per JVM regardless of how many SDK components opt into the pattern, instead of cosmos adding its own competing thread. - Java 9+ automatically gets the Cleaner-based implementation (better shutdown semantics, less thread-stack overhead). - Drops ~100 lines of bespoke phantom plumbing from SharedRoutingMapCacheRegistry (OwnerPhantom inner class, livePhantoms set, reaper loop). Net negative on code we maintain. Design notes preserved: - The lambda registered with ReferenceManager.INSTANCE.register MUST NOT capture `owner`, otherwise the owner never becomes phantom-reachable. We capture only the endpoint URI and the cache reference (both independent of the owner) and document this constraint in code. - ReleaseHandle is a one-shot AtomicBoolean fulfilment flag shared between the prompt close() path and the deferred ReferenceManager cleanup, so whichever runs first wins via compareAndSet and the refcount is decremented exactly once. 36 cache unit tests still pass; the leak test was renamed to referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected to reflect the new mechanism. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SharedRoutingMapCacheRegistryTest.java | 33 ++- .../caches/RxPartitionKeyRangeCache.java | 24 +- .../caches/SharedRoutingMapCacheRegistry.java | 245 ++++++------------ 3 files changed, 105 insertions(+), 197 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java index 1a529af7f0bd..0f5982a5417d 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java @@ -257,22 +257,21 @@ public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Excep } @Test(groups = "unit") - public void reaperReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Exception { + public void referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Exception { // Simulates a customer that forgot to call CosmosClient.close(). The owning // object (here: an opaque Object stand-in for RxPartitionKeyRangeCache) is - // discarded; the registry's PhantomReference is enqueued on the next GC; the - // reaper drains it and decrements the refcount. + // discarded; azure-core's ReferenceManager observes that the owner is + // phantom-reachable and runs the registered cleanup which decrements the refcount. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-1.documents.azure.com:443/"); // Acquire and immediately discard the owner in a separate stack frame so the - // method's frame cannot keep a hidden strong reference alive past `owner = null`. - // We also avoid holding the cache in this method to remove any chance of - // accidentally keeping the entry alive via the test frame. + // test frame cannot keep a hidden strong reference alive past the call. acquireAndLeakOwner(registry, endpoint); assertThat(registry.referenceCount(endpoint)).isEqualTo(1); - // Wait for the reaper to observe the GC and decrement the refcount. + // Wait for the ReferenceManager to observe the GC and run the cleanup action. + // Poll up to 15 s; on most JVMs this completes within a few GC cycles. boolean released = false; long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(15); while (System.nanoTime() < deadlineNanos) { @@ -286,7 +285,7 @@ public void reaperReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Except } assertThat(released) - .as("Reaper should release the shared cache reference after the owner is GC'd " + .as("ReferenceManager should release the shared cache reference after the owner is GC'd " + "(refcount=%d)", registry.referenceCount(endpoint)) .isTrue(); } @@ -303,12 +302,12 @@ private static void acquireAndLeakOwner(SharedRoutingMapCacheRegistry registry, } @Test(groups = "unit") - public void promptCloseClearsPhantomSoReaperDoesNotDoubleRelease() throws Exception { + public void promptCloseFulfillsHandleSoReferenceManagerCleanupIsANoop() throws Exception { // After a prompt close() the registry's refcount is already zero and the - // entry already evicted. Even if the GC enqueues the phantom later, the - // reaper's release(endpoint, cache) is a no-op because the registry no - // longer holds that cache instance — exercised by referenceCount staying - // at zero across a forced GC cycle. + // entry already evicted. When the JVM later runs the ReferenceManager cleanup + // action after the owner is GC'd, the ReleaseHandle is already fulfilled so + // the action is a no-op — exercised by referenceCount staying at zero across + // a forced GC cycle. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-2.documents.azure.com:443/"); @@ -318,6 +317,7 @@ public void promptCloseClearsPhantomSoReaperDoesNotDoubleRelease() throws Except // Force GC; refcount must remain zero, no exception. for (int i = 0; i < 5; i++) { System.gc(); + System.runFinalization(); Thread.sleep(50); } assertThat(registry.referenceCount(endpoint)).isZero(); @@ -326,9 +326,8 @@ public void promptCloseClearsPhantomSoReaperDoesNotDoubleRelease() throws Except private static void acquireAndPromptlyClose(SharedRoutingMapCacheRegistry registry, URI endpoint) { Object owner = new Object(); SharedRoutingMapCacheRegistry.AcquireResult result = registry.acquire(endpoint, owner); - // Simulate the prompt RxPartitionKeyRangeCache.close() path via the - // phantom-aware overload — this clears the phantom and removes it from the - // live-phantom set so the reaper does not later try to double-release. - registry.release(endpoint, result.cache, result.ownerPhantom); + // Simulate the prompt RxPartitionKeyRangeCache.close() path. This fulfils + // the handle so the ReferenceManager-registered cleanup later becomes a no-op. + registry.release(endpoint, result.cache, result.releaseHandle); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index 79eb38e9356d..61440a88a949 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -32,7 +32,6 @@ import reactor.core.publisher.Mono; import java.io.Closeable; -import java.lang.ref.PhantomReference; import java.net.URI; import java.time.Instant; import java.util.ArrayList; @@ -66,15 +65,12 @@ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closea private final URI sharedCacheEndpointKey; private final AtomicBoolean closed = new AtomicBoolean(false); /** - * Phantom registered with {@link SharedRoutingMapCacheRegistry} so that an - * unclosed (leaked) cache instance gets cleaned up by the reaper when it - * becomes phantom-reachable. Held as a field to keep the phantom itself - * reachable for as long as this instance is reachable — otherwise the GC - * would collect the phantom before its referent and the queue would never - * be notified. May be {@code null} for isolated caches. + * Handle returned by {@link SharedRoutingMapCacheRegistry#acquire(URI, Object)} + * that lets {@link #close()} mark the deferred cleanup action (registered with + * {@link com.azure.core.util.ReferenceManager}) as already-fulfilled. May be + * {@code null} for isolated caches (sharing disabled / null endpoint). */ - @SuppressWarnings("unused") // referenced via the field for liveness; not read at runtime. - private final PhantomReference ownerPhantom; + private final SharedRoutingMapCacheRegistry.ReleaseHandle releaseHandle; public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { this(client, collectionCache, client == null ? null : client.getServiceEndpoint()); @@ -89,7 +85,7 @@ public RxPartitionKeyRangeCache( SharedRoutingMapCacheRegistry.AcquireResult acquired = SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey, this); this.routingMapCache = acquired.cache; - this.ownerPhantom = acquired.ownerPhantom; + this.releaseHandle = acquired.releaseHandle; this.client = client; this.collectionCache = collectionCache; this.clientContext = client; @@ -100,15 +96,15 @@ public RxPartitionKeyRangeCache( * Safe to call multiple times; only the first call has an effect. * After {@code close()} the instance must not be used further. * - *

Also clears the {@link PhantomReference} registered on construction - * so the registry's reaper does not attempt a redundant release later - * when this instance is GC'd.

+ *

Marks the {@link com.azure.core.util.ReferenceManager}-registered + * cleanup action as fulfilled so it becomes a no-op when the JVM later + * runs it after this instance is GC'd.

*/ @Override public void close() { if (closed.compareAndSet(false, true)) { SharedRoutingMapCacheRegistry.getInstance().release( - this.sharedCacheEndpointKey, this.routingMapCache, this.ownerPhantom); + this.sharedCacheEndpointKey, this.routingMapCache, this.releaseHandle); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java index 6c08cd35c4af..c08216b554e2 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -2,18 +2,15 @@ // Licensed under the MIT License. package com.azure.cosmos.implementation.caches; +import com.azure.core.util.ReferenceManager; import com.azure.cosmos.implementation.Configs; import com.azure.cosmos.implementation.routing.CollectionRoutingMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.ref.PhantomReference; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; import java.net.URI; -import java.util.Collections; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** @@ -44,34 +41,36 @@ * user-supplied account endpoint URL: Python uses {@code client.url_connection} * (raw string compare); Rust uses {@code AccountEndpoint(Url)} (URL-based * equality). This implementation matches that contract. As a consequence, - * two clients to the same account that bootstrap from different regional - * endpoints (e.g. {@code my-acct-westus.documents.azure.com} vs - * {@code my-acct.documents.azure.com}) do not share a cache entry.

+ * two clients to the same account that bootstrap from different + * regional endpoints (e.g. {@code my-acct-westus.documents.azure.com} vs + * {@code my-acct.documents.azure.com}) do not share a cache entry — + * the same fragmentation behaviour the peer SDKs have.

* - *

Lifecycle. Callers obtain a shared cache via {@link #acquire(URI, Object)} - * during construction and return it via {@link #release(URI, AsyncCacheNonBlocking)} - * during {@code close()}. A per-entry refcount tracks live callers; when the - * count reaches zero the entry is evicted so an idle endpoint does not pin - * memory forever.

+ *

Lifecycle. Callers obtain a shared cache via + * {@link #acquire(URI, Object)} during construction and return it via + * {@link #release(URI, AsyncCacheNonBlocking, ReleaseHandle)} during + * {@code close()}. A per-entry refcount tracks live callers; when the count + * reaches zero the entry is evicted so an idle endpoint does not pin memory + * forever.

* *

Leaked-client safety net. A caller may forget to {@code close()} * its {@code CosmosAsyncClient}. Without protection the unclosed client would * keep a strong reference to the shared cache and pin it for the JVM's * lifetime. To handle that, every {@link #acquire(URI, Object)} also - * registers a {@link PhantomReference} for the owner. When the owner becomes - * unreachable the GC enqueues the phantom; a single daemon reaper thread - * drains the queue and calls {@link #release(URI, AsyncCacheNonBlocking)} - * to clean up. The reaper is not a substitute for - * {@link java.io.Closeable#close()} (no guaranteed promptness) but it - * prevents the cache from leaking forever. This mirrors the safety net - * Python provides via its {@code __del__} fallback and Rust gets for free - * via {@code Drop}; we cannot use {@code java.lang.ref.Cleaner} because - * this SDK still supports Java 8.

+ * registers a cleanup action with {@link ReferenceManager#INSTANCE} (the + * SDK-wide reference manager in {@code azure-core}). When the owner object + * becomes phantom-reachable, the reference manager runs the cleanup action + * which decrements the refcount and evicts the entry if it was the last + * reference. On Java 9+ {@code azure-core}'s {@code ReferenceManagerImpl} + * delegates to {@link java.lang.ref.Cleaner} reflectively; on Java 8 it uses + * an internal {@link java.lang.ref.PhantomReference}-based daemon thread. + * Cosmos reuses the supported, well-tested azure-core machinery rather than + * rolling its own.

* *

Opt-out. Setting the system property * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false} disables * sharing; each {@link #acquire(URI, Object)} returns a fresh, isolated cache - * (no registry entry, no phantom registration).

+ * (no registry entry, no cleanup registration).

* *

Concurrency. All state transitions go through * {@link ConcurrentHashMap#compute(Object, java.util.function.BiFunction)}, @@ -85,29 +84,7 @@ public final class SharedRoutingMapCacheRegistry { private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); - /** - * Queue the GC enqueues {@link OwnerPhantom} instances onto when their - * referent (the owning {@link RxPartitionKeyRangeCache}) becomes - * unreachable without {@code close()} having been called. - */ - private final ReferenceQueue reaperQueue = new ReferenceQueue<>(); - - /** - * Strong-references every live {@link OwnerPhantom} so the JVM does not - * collect the phantom before its referent. The GC only enqueues phantoms - * that are themselves still reachable; without this set the phantoms - * registered in {@link #acquire(URI, Object)} would be garbage-collected - * together with their owners and the reaper would never observe the leak. - * Entries are removed either by the reaper after processing or by - * {@link #release(URI, AsyncCacheNonBlocking, PhantomReference)} on prompt close. - */ - private final Set livePhantoms = - Collections.newSetFromMap(new ConcurrentHashMap<>()); - private SharedRoutingMapCacheRegistry() { - Thread reaper = new Thread(this::runReaper, "cosmos-shared-pkr-cache-reaper"); - reaper.setDaemon(true); - reaper.start(); } public static SharedRoutingMapCacheRegistry getInstance() { @@ -120,20 +97,20 @@ public static SharedRoutingMapCacheRegistry getInstance() { * *

If {@code endpoint} is {@code null} or sharing is disabled via * {@link Configs#isSharedPartitionKeyRangeCacheEnabled()}, returns a fresh - * isolated cache that the caller fully owns. - * {@link #release(URI, AsyncCacheNonBlocking)} is still safe to call on - * such a cache (it is a no-op).

+ * isolated cache that the caller fully owns.

* - *

When {@code owner} is non-null and sharing is enabled, a - * {@link PhantomReference} to {@code owner} is registered so that an + *

When {@code owner} is non-null and sharing is enabled, a cleanup + * action is registered with {@link ReferenceManager#INSTANCE} so that an * unreferenced (leaked) owner triggers a deferred release.

* * @param endpoint The Cosmos service endpoint URI, or {@code null} for an isolated cache. * @param owner The object whose unreachability should trigger a deferred release * (typically the {@link RxPartitionKeyRangeCache} caller). May be {@code null} - * to skip phantom registration (e.g. tests calling acquire directly). + * to skip cleanup registration (e.g. tests calling acquire directly). * @return A handle that exposes the shared cache instance plus a token used by - * {@link #release(URI, AsyncCacheNonBlocking)} for prompt cleanup. + * {@link RxPartitionKeyRangeCache#close()} to mark the cleanup action + * already-fulfilled so it becomes a no-op when {@link ReferenceManager} + * later runs it. */ public AcquireResult acquire(URI endpoint, Object owner) { if (endpoint == null || !Configs.isSharedPartitionKeyRangeCacheEnabled()) { @@ -152,58 +129,56 @@ public AcquireResult acquire(URI endpoint, Object owner) { return existing; }); - OwnerPhantom phantom = null; + ReleaseHandle handle = null; if (owner != null) { - phantom = new OwnerPhantom(owner, reaperQueue, endpoint, entry.cache); - // The phantom MUST be strongly reachable until it is either processed by - // the reaper or cleared on prompt close, otherwise the GC will collect it - // alongside the owner without ever enqueueing it. - livePhantoms.add(phantom); + // IMPORTANT: the cleanup action must NOT capture `owner`, or the + // owner will never become phantom-reachable. We capture only the + // endpoint URI and the cache reference — both independent of the owner. + final URI capturedEndpoint = endpoint; + final AsyncCacheNonBlocking capturedCache = entry.cache; + final ReleaseHandle h = new ReleaseHandle(); + ReferenceManager.INSTANCE.register(owner, () -> { + if (h.fulfill()) { + logger.warn( + "Leaked (unclosed) RxPartitionKeyRangeCache detected for endpoint [{}]" + + " — releasing shared cache reference via ReferenceManager. Always" + + " close CosmosClient / CosmosAsyncClient to avoid relying on this" + + " safety net.", + capturedEndpoint); + release(capturedEndpoint, capturedCache); + } + }); + handle = h; } - return new AcquireResult(entry.cache, phantom); + return new AcquireResult(entry.cache, handle); } /** - * Convenience overload used by tests that do not need phantom registration. + * Convenience overload used by tests that do not need cleanup registration. */ AsyncCacheNonBlocking acquire(URI endpoint) { return acquire(endpoint, null).cache; } /** - * Releases a reference to the shared cache previously obtained via - * {@link #acquire(URI, Object)}. When the last reference is released the - * registry entry is evicted. - * - *

Safe to call when sharing was bypassed (null endpoint or sharing - * disabled): the call is a no-op if the supplied cache is not the one - * currently registered for {@code endpoint}.

- * - *

If {@code phantom} is non-null it is cleared and dropped from the - * registry's live-phantom set, so the reaper does not later double-release - * the same reference.

- * - * @param endpoint The endpoint the cache was acquired for, or {@code null} - * if it was an isolated cache. - * @param cache The cache instance returned by {@link #acquire(URI, Object)}. - * @param phantom The phantom returned by {@link #acquire(URI, Object)}, or - * {@code null} if none was registered. + * Prompt-close path used by {@link RxPartitionKeyRangeCache#close()}. + * Marks the cleanup action as fulfilled (so the later + * {@link ReferenceManager}-triggered run becomes a no-op) and decrements + * the refcount. */ public void release(URI endpoint, AsyncCacheNonBlocking cache, - PhantomReference phantom) { - if (phantom instanceof OwnerPhantom) { - OwnerPhantom op = (OwnerPhantom) phantom; - livePhantoms.remove(op); - op.clear(); + ReleaseHandle handle) { + if (handle != null && !handle.fulfill()) { + // Already fulfilled by the ReferenceManager path; do not double-decrement. + return; } release(endpoint, cache); } /** - * Internal release used by the reaper (which already holds the phantom - * reference it processed) and by the two-arg overload above. Test code - * also calls this overload directly to simulate close paths. + * Internal release used by both the prompt-close path and the + * ReferenceManager-triggered cleanup action. */ public void release(URI endpoint, AsyncCacheNonBlocking cache) { if (endpoint == null || cache == null) { @@ -226,40 +201,6 @@ public void release(URI endpoint, AsyncCacheNonBlocking ref = reaperQueue.remove(); - if (ref instanceof OwnerPhantom) { - OwnerPhantom phantom = (OwnerPhantom) ref; - logger.warn( - "Leaked (unclosed) RxPartitionKeyRangeCache detected for endpoint [{}]" - + " — releasing shared cache reference via reaper. Always close CosmosClient" - + " / CosmosAsyncClient to avoid relying on this safety net.", - phantom.endpoint); - try { - release(phantom.endpoint, phantom.cache); - } catch (RuntimeException ex) { - logger.error("Reaper failed to release shared cache for endpoint [{}]", - phantom.endpoint, ex); - } finally { - livePhantoms.remove(phantom); - phantom.clear(); - } - } - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - return; - } - } - } - /** * Test-only: number of registered endpoints currently held by the registry. */ @@ -276,67 +217,39 @@ int referenceCount(URI endpoint) { return entry == null ? 0 : entry.refCount.get(); } - /** - * Test-only: number of live phantoms currently retained by the registry. - */ - int livePhantomCount() { - return livePhantoms.size(); - } - /** * Returned by {@link #acquire(URI, Object)}. Holds the shared cache and - * the {@link PhantomReference} (when one was registered) so the caller - * can clear it on prompt {@code close()} and avoid a redundant reaper - * release later. + * a handle the caller passes back to + * {@link #release(URI, AsyncCacheNonBlocking, ReleaseHandle)} on prompt + * close to prevent the deferred cleanup action from double-releasing. */ public static final class AcquireResult { public final AsyncCacheNonBlocking cache; - public final PhantomReference ownerPhantom; + public final ReleaseHandle releaseHandle; AcquireResult(AsyncCacheNonBlocking cache, - PhantomReference ownerPhantom) { + ReleaseHandle releaseHandle) { this.cache = cache; - this.ownerPhantom = ownerPhantom; + this.releaseHandle = releaseHandle; } } - private static final class Entry { - final AsyncCacheNonBlocking cache = new AsyncCacheNonBlocking<>(); - final AtomicInteger refCount = new AtomicInteger(0); - } - /** - * PhantomReference whose enqueueing on GC of its referent (the owning - * {@link RxPartitionKeyRangeCache}) signals the reaper to release the - * matching shared-cache entry. The captured endpoint + cache are strong - * fields on the phantom itself — that's fine: once the reaper clears the - * phantom, the phantom itself becomes unreachable and the captured cache - * loses one more strong holder. - * - *

Why is this safe? The phantom is registered on the - * {@code RxPartitionKeyRangeCache} (which is the field of the - * {@code RxDocumentClientImpl}). It does not reference the client - * or any field of it. So the phantom does not prevent the owning client - * from being GC'd; it is enqueued exactly when the owning cache - * becomes phantom-reachable.

- * - *

The phantom is kept reachable by being assigned to a field on the - * owning {@code RxPartitionKeyRangeCache}. If the phantom itself were - * unreachable before its referent became unreachable, the GC would never - * enqueue it. Holding it on the owner ties phantom liveness to owner - * liveness, which is precisely what we want.

+ * One-shot fulfilment flag shared between the prompt-close path and the + * deferred {@link ReferenceManager} cleanup. Whichever path runs first + * wins via {@link AtomicBoolean#compareAndSet}; the loser becomes a no-op + * so the refcount is decremented exactly once. */ - static final class OwnerPhantom extends PhantomReference { - final URI endpoint; - final AsyncCacheNonBlocking cache; + public static final class ReleaseHandle { + private final AtomicBoolean fulfilled = new AtomicBoolean(false); - OwnerPhantom(Object owner, - ReferenceQueue queue, - URI endpoint, - AsyncCacheNonBlocking cache) { - super(owner, queue); - this.endpoint = endpoint; - this.cache = cache; + boolean fulfill() { + return fulfilled.compareAndSet(false, true); } } + + private static final class Entry { + final AsyncCacheNonBlocking cache = new AsyncCacheNonBlocking<>(); + final AtomicInteger refCount = new AtomicInteger(0); + } } From 9b43616c9eee5fb6da30df0a75fee62dfc118fde Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 11:39:46 -0700 Subject: [PATCH 6/8] remove kafka test output --- .../Command line suite/Command line test.html | 129 ----- .../Command line suite/Command line test.xml | 14 - .../test-output/bullet_point.png | Bin 356 -> 0 bytes .../test-output/collapseall.gif | Bin 157 -> 0 bytes .../test-output/emailable-report.html | 27 - .../test-output/failed.png | Bin 977 -> 0 bytes .../test-output/index.html | 463 ------------------ .../test-output/jquery.min.js | 2 - ...ntation.sink.SinkRecordTransformerTest.xml | 24 - .../test-output/navigator-bullet.png | Bin 352 -> 0 bytes .../Command line test.properties | 1 - .../old/Command line suite/classes.html | 74 --- .../old/Command line suite/groups.html | 3 - .../old/Command line suite/index.html | 6 - .../old/Command line suite/main.html | 2 - .../methods-alphabetical.html | 24 - .../Command line suite/methods-not-run.html | 2 - .../old/Command line suite/methods.html | 24 - .../Command line suite/reporter-output.html | 1 - .../old/Command line suite/testng.xml.html | 1 - .../old/Command line suite/toc.html | 30 -- .../test-output/old/index.html | 9 - .../test-output/passed.png | Bin 1019 -> 0 bytes .../test-output/skipped.png | Bin 967 -> 0 bytes .../test-output/testng-reports.css | 326 ------------ .../test-output/testng-reports.js | 122 ----- .../test-output/testng-reports1.css | 344 ------------- .../test-output/testng-reports2.js | 76 --- .../test-output/testng-results.xml | 65 --- .../test-output/testng.css | 9 - 30 files changed, 1778 deletions(-) delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/collapseall.gif delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/junitreports/TEST-com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest.xml delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/Command line test.properties delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/classes.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/skipped.png delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml delete mode 100644 sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html deleted file mode 100644 index c53d83ca957a..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.html +++ /dev/null @@ -1,129 +0,0 @@ - - -TestNG: Command line test - - - - - - - - -

Command line test

- - - - - - - - - - - -
Tests passed/Failed/Skipped:10/0/0
Started on:Fri May 29 09:31:34 PDT 2026
Total time:0 seconds (490 ms)
Included groups:unit
Excluded groups:

-(Hover the method name to see the test class name)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PASSED TESTS
Test methodExceptionTime (seconds)Instance
allValidRecords_allInOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchToleranceNone_noReporter_exceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
reporterThrows_toleranceAll_recordSkippedProcessingContinues
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
toleranceNoneWithReporter_reportedToDlqAndExceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
reporterThrows_toleranceNone_originalExceptionRethrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
structConversionFailure_toleranceAll_reportedToDlqAndSkipped
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
structConversionFailure_toleranceNone_exceptionThrown
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130
mixedBatchToleranceAll_noReporter_badRecordSkipped
Test class: com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
0com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130

- - \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml deleted file mode 100644 index 205d4e6c554e..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/Command line suite/Command line test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/bullet_point.png deleted file mode 100644 index 176e6d5b3d64d032e76c493e5811a1cf839220b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hVR@^o z&n}1RKn7{UjfWZCs($|cfA#bKkH7!F`St(Z@BiQa{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtB(Pob+71*X+evXI>YLE;&}Fj8#mRE%&W?B30shyu13% zpT6C#3k-fJGjKF52@24V6I?%GvcZa|)%y<^9(-F=IB9W`k6g3(YLhfsMh0sDZC^x! diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html deleted file mode 100644 index 4d3118b9c4e5..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/emailable-report.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - -TestNG Report - - - - - - - -
Test# Passed# Skipped# Retried# FailedTime (ms)Included GroupsExcluded Groups
Command line suite
Command line test10000490unit
- -
ClassMethodStartTime (ms)
Command line suite
Command line test — passed
com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTestallBadRecordsWithReporterToleranceAll_allReportedEmptyOutput1780072294587448
allValidRecords_allInOutput17800722950361
mixedBatchToleranceAll_noReporter_badRecordSkipped17800722950370
mixedBatchToleranceNone_noReporter_exceptionThrown17800722950382
mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput17800722950401
reporterThrows_toleranceAll_recordSkippedProcessingContinues17800722950411
reporterThrows_toleranceNone_originalExceptionRethrown17800722950421
structConversionFailure_toleranceAll_reportedToDlqAndSkipped178007229504320
structConversionFailure_toleranceNone_exceptionThrown17800722950631
toleranceNoneWithReporter_reportedToDlqAndExceptionThrown17800722950641
-

Command line test

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#allValidRecords_allInOutput

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchToleranceAll_noReporter_badRecordSkipped

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchToleranceNone_noReporter_exceptionThrown

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#reporterThrows_toleranceAll_recordSkippedProcessingContinues

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#reporterThrows_toleranceNone_originalExceptionRethrown

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#structConversionFailure_toleranceAll_reportedToDlqAndSkipped

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#structConversionFailure_toleranceNone_exceptionThrown

back to summary

-

com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest#toleranceNoneWithReporter_reportedToDlqAndExceptionThrown

back to summary

- - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/failed.png deleted file mode 100644 index c117be59a9ecd1da15ebf48f6b7f53496302a7cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmV;?11|iDP)4Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0B%V{K~yLeW4y>F|DS;bz(j&tuu`}Ny`K+o>P41= zYq-R&z$-w|z14sZ}6S`uM8b)lMhS`K{GDtB9px6Kr!cSsofH?!*c`##8 zG{6+YB(Z6NYd}|wOA}U4!xUqq;Wl8C#3lv+hIuOk>aOmJ00000NkvXXu0mjfn+D0# diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html deleted file mode 100644 index f3e0de8a2bb2..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/index.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - TestNG reports - - - - - - - - - - - -
- Test results - -
- 1 suite -
- -
-
-
-
-
- - com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest -
-
-
-
- - - allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput -
-
-
-
- - - allValidRecords_allInOutput -
-
-
-
- - - mixedBatchToleranceAll_noReporter_badRecordSkipped -
-
-
-
- - - mixedBatchToleranceNone_noReporter_exceptionThrown -
-
-
-
- - - mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput -
-
-
-
- - - reporterThrows_toleranceAll_recordSkippedProcessingContinues -
-
-
-
- - - reporterThrows_toleranceNone_originalExceptionRethrown -
-
-
-
- - - structConversionFailure_toleranceAll_reportedToDlqAndSkipped -
-
-
-
- - - structConversionFailure_toleranceNone_exceptionThrown -
-
-
-
- - - toleranceNoneWithReporter_reportedToDlqAndExceptionThrown -
-
-
-
-
-
-
-
-
-
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
-<suite name="Command line suite">
-  <groups>
-    <run>
-      <include name="unit"/>
-    </run>
-  </groups>
-  <test thread-count="5" name="Command line test">
-    <classes>
-      <class name="com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest"/>
-    </classes>
-  </test> <!-- Command line test -->
-</suite> <!-- Command line suite -->
-            
-
-
-
-
- Tests for Command line suite -
-
-
    -
  • - Command line test (1 class) -
  • -
-
-
-
-
- Groups for Command line suite -
-
-
- unit -
-
- allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput -
-
-
- allValidRecords_allInOutput -
-
-
- mixedBatchToleranceAll_noReporter_badRecordSkipped -
-
-
- mixedBatchToleranceNone_noReporter_exceptionThrown -
-
-
- mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput -
-
-
- reporterThrows_toleranceAll_recordSkippedProcessingContinues -
-
-
- reporterThrows_toleranceNone_originalExceptionRethrown -
-
-
- structConversionFailure_toleranceAll_reportedToDlqAndSkipped -
-
-
- structConversionFailure_toleranceNone_exceptionThrown -
-
-
- toleranceNoneWithReporter_reportedToDlqAndExceptionThrown -
-
-
-
-
-
-
- Times for Command line suite -
-
-
- - Total running time: 476 ms -
-
-
-
-
-
-
- Reporter output for Command line suite -
-
-
-
-
-
- 0 ignored methods -
-
-
-
-
-
- Methods in chronological order -
-
-
-
com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest
-
- allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput - 0 ms -
-
- allValidRecords_allInOutput - 449 ms -
-
- mixedBatchToleranceAll_noReporter_badRecordSkipped - 450 ms -
-
- mixedBatchToleranceNone_noReporter_exceptionThrown - 451 ms -
-
- mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput - 453 ms -
-
- reporterThrows_toleranceAll_recordSkippedProcessingContinues - 454 ms -
-
- reporterThrows_toleranceNone_originalExceptionRethrown - 455 ms -
-
- structConversionFailure_toleranceAll_reportedToDlqAndSkipped - 456 ms -
-
- structConversionFailure_toleranceNone_exceptionThrown - 476 ms -
-
- toleranceNoneWithReporter_reportedToDlqAndExceptionThrown - 477 ms -
-
-
-
-
- - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js deleted file mode 100644 index b0614034ad3a..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/jquery.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/navigator-bullet.png deleted file mode 100644 index 36d90d395c51912e718b89dd88b4a3fb53aa1d85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 352 zcmV-m0iXVfP)G5@hw44>$jtc^drBsEhr7 z^X9?-KzfCWMC0vWtek#CBxB+XG+nX0$0e)!py)g%*!C9F3xb^$q9zV zJJ-RS;)J3Q3>X<0IJnsvq?E-OUUR%-Sh{}$*!>`a1>MbzjEoGd?5qriD%uRz5+)#_ z=~xvqF)}e2@@p|@3aYFDDdOf=+lQf0fP;_0P2842gi~-LkXsB?^cOvN)>U@o{(tlO y5-4a&(SrsYdr*b0AjKdWn<5ZqBsQ)A0t^5xc9&6bK}yU30000 - -Class name -Method name -Groups - -com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest -   - -@Test - - -  -allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput -unit - - -  -reporterThrows_toleranceNone_originalExceptionRethrown -unit - - -  -mixedBatchToleranceNone_noReporter_exceptionThrown -unit - - -  -structConversionFailure_toleranceNone_exceptionThrown -unit - - -  -structConversionFailure_toleranceAll_reportedToDlqAndSkipped -unit - - -  -reporterThrows_toleranceAll_recordSkippedProcessingContinues -unit - - -  -mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput -unit - - -  -allValidRecords_allInOutput -unit - - -  -toleranceNoneWithReporter_reportedToDlqAndExceptionThrown -unit - - -  -mixedBatchToleranceAll_noReporter_badRecordSkipped -unit - - -@BeforeClass - - -@BeforeMethod - - -@AfterMethod - - -@AfterClass - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html deleted file mode 100644 index 2cd12182e1b4..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/groups.html +++ /dev/null @@ -1,3 +0,0 @@ -

Groups used for this test run

- -
Group nameMethods
unitSinkRecordTransformerTest.mixedBatchToleranceAll_noReporter_badRecordSkipped()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.reporterThrows_toleranceNone_originalExceptionRethrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.structConversionFailure_toleranceAll_reportedToDlqAndSkipped()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.structConversionFailure_toleranceNone_exceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.mixedBatchToleranceNone_noReporter_exceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.allValidRecords_allInOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.allBadRecordsWithReporterToleranceAll_allReportedEmptyOutput()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.reporterThrows_toleranceAll_recordSkippedProcessingContinues()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
SinkRecordTransformerTest.toleranceNoneWithReporter_reportedToDlqAndExceptionThrown()[pri:0, instance:com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest@6eceb130]
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html deleted file mode 100644 index 3577bd28a9f6..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/index.html +++ /dev/null @@ -1,6 +0,0 @@ -Results for Command line suite - - - - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html deleted file mode 100644 index 0ee4b5b499ac..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/main.html +++ /dev/null @@ -1,2 +0,0 @@ -Results for Command line suite -Select a result on the left-hand pane. diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html deleted file mode 100644 index b337d595b0d7..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-alphabetical.html +++ /dev/null @@ -1,24 +0,0 @@ -

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hover the method name to see the test class name)

- - - - - - - - - - - - - - - - - - - - - - -
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
26/05/29 09:31:34 0      allBadRecordsWithReporterToleranceAll_allReportedEmptyOutputmain@1983025922
26/05/29 09:31:35 450      allValidRecords_allInOutputmain@1983025922
26/05/29 09:31:35 451      mixedBatchToleranceAll_noReporter_badRecordSkippedmain@1983025922
26/05/29 09:31:35 452      mixedBatchToleranceNone_noReporter_exceptionThrownmain@1983025922
26/05/29 09:31:35 454      mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutputmain@1983025922
26/05/29 09:31:35 455      reporterThrows_toleranceAll_recordSkippedProcessingContinuesmain@1983025922
26/05/29 09:31:35 456      reporterThrows_toleranceNone_originalExceptionRethrownmain@1983025922
26/05/29 09:31:35 457      structConversionFailure_toleranceAll_reportedToDlqAndSkippedmain@1983025922
26/05/29 09:31:35 477      structConversionFailure_toleranceNone_exceptionThrownmain@1983025922
26/05/29 09:31:35 478      toleranceNoneWithReporter_reportedToDlqAndExceptionThrownmain@1983025922
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html deleted file mode 100644 index 54b14cb854b6..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods-not-run.html +++ /dev/null @@ -1,2 +0,0 @@ -

Methods that were not run

-
\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html deleted file mode 100644 index 8445f609ae72..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/methods.html +++ /dev/null @@ -1,24 +0,0 @@ -

Methods run, sorted chronologically

>> means before, << means after


Command line suite

(Hover the method name to see the test class name)

- - - - - - - - - - - - - - - - - - - - - - -
TimeDelta (ms)Suite
configuration
Test
configuration
Class
configuration
Groups
configuration
Method
configuration
Test
method
ThreadInstances
26/05/29 09:31:35 0      allValidRecords_allInOutputmain@1983025922
26/05/29 09:31:35 2      mixedBatchToleranceNone_noReporter_exceptionThrownmain@1983025922
26/05/29 09:31:35 5      reporterThrows_toleranceAll_recordSkippedProcessingContinuesmain@1983025922
26/05/29 09:31:35 28      toleranceNoneWithReporter_reportedToDlqAndExceptionThrownmain@1983025922
26/05/29 09:31:34 -450      allBadRecordsWithReporterToleranceAll_allReportedEmptyOutputmain@1983025922
26/05/29 09:31:35 4      mixedBatchWithReporterToleranceAll_badRecordReportedValidRecordsInOutputmain@1983025922
26/05/29 09:31:35 6      reporterThrows_toleranceNone_originalExceptionRethrownmain@1983025922
26/05/29 09:31:35 7      structConversionFailure_toleranceAll_reportedToDlqAndSkippedmain@1983025922
26/05/29 09:31:35 27      structConversionFailure_toleranceNone_exceptionThrownmain@1983025922
26/05/29 09:31:35 1      mixedBatchToleranceAll_noReporter_badRecordSkippedmain@1983025922
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html deleted file mode 100644 index 063bc2e96fd0..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/reporter-output.html +++ /dev/null @@ -1 +0,0 @@ -

Reporter output

\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html deleted file mode 100644 index 0e5e7a37e49a..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/testng.xml.html +++ /dev/null @@ -1 +0,0 @@ -testng.xml for Command line suite<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Command line suite">
  <groups>
    <run>
      <include name="unit"/>
    </run>
  </groups>
  <test thread-count="5" name="Command line test">
    <classes>
      <class name="com.azure.cosmos.kafka.connect.implementation.sink.SinkRecordTransformerTest"/>
    </classes>
  </test> <!-- Command line test -->
</suite> <!-- Command line suite -->
\ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html deleted file mode 100644 index 77503f658dfb..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/Command line suite/toc.html +++ /dev/null @@ -1,30 +0,0 @@ - - -Results for Command line suite - - - - -

Results for
Command line suite

- - - - - - - - - - -
1 test1 class10 methods:
-  chronological
-  alphabetical
-  not run (0)
1 groupreporter outputtestng.xml
- -

-

-
Command line test (10/0/0) - Results -
-
- \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html deleted file mode 100644 index 436304d71d22..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/old/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -

Test results

- - - -
SuitePassedFailedSkippedtestng.xml
Total1000 
Command line suite1000Link
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/passed.png deleted file mode 100644 index 45e85bbfd0f5e85def14b896cfd4331675be2759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1019 zcmV4Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0GLTcK~yLeW0ahz`=5aXz(j&tuu_sWu%O#uE8~VD zl&lrR;HF{4AT>#kuni$fu3*LaYg^!kpg8GS-X(?~-@n6gsDV2}@4opAtDmldYd~=l z$fS+YQyErY*vatm`)9DCL(k8^6@wTk8o(y4Wnh>XTmx2AyLA%7m+#+DG@v*MBy;8c pT?UXs5IFYyJeWo%7zba(0RWt9G$oT4y{G^H002ovPDHLkV1nS74Tx0C)j~RNrgUP!#^!Wu36$i#lf!2|j3%Ze&w*L!7p2SGvtw>Nd9_NSmf@ zT$;ut?S8Na*^6&F#dq-sKKTa>*@JI;k`2ZbVfd_wB24xov!0tYO(#d#()tZ$I5%3%!zLYh@BH>w}XODA7?mkV}ap}jU$$3 zG&Mk)3Bm`(LOM&hKscCb;PVaG&Vdx+MpZJHTQ(R_;DA31$+jOGBoLXk_De?ey1m!ik&_4G zH9n^))_*|$z4!HUisgBd@awc5jn(v9k~&t~+vLrrBg4dZQ9lDnLV}JQWGLW~LJVP= zW5lZXOcog;N~F?hbX0k=IMzETla}oqM|jC!4!B+x^;@#I_Tc-T-6hwKycLDTx1-om z?X`jFy0R0R8-I0SrK4`)H@W4T8*Qr#2vPou<*`U!Wy(*2QP*`g=8#jD{B;Y@GL-Hm zb`n?&x~%YC_$q7)PlXr4m%r4=&fcvN%Ybn#KC7Nn&Bp8{(oE9pWVpYI^+LuN`H(R~ zTAjWmO`M83^4d@fCkA(d>*nHIFV_d2yUbnT`nd?LE^;G|!WZ>Ld?E0@Grm4ww{M7H zr`x{MWb30bTI;*hk-DO>dX$gbC-yy#suLNqvA(f>RtPJ!qGM`Gvvf}Y10`)vm-7Xa z?-7Ixe2A_siI1ydSCCID3U8SVUY86>uSnT0use_K1GZDvUFKY)t}F* z)!pahe+zh{{06Bb3f97*Uorpy0Axu-K~yLeV|;sz;XeZjfQbaPV5M*kLYBBKLY9MT zcz2wU0a*fOGe`_12Lo^oAOUnu=!!vVSU?0aK-Pq8GE5DM4KP7`G=>J4GmvdUHULEf pOfgIWHcfC1=!$V^Vx)OY0{~v*D#slo71{s*002ovPDHLkV1jLYy!8M8 diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css deleted file mode 100644 index d7b75c404782..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.css +++ /dev/null @@ -1,326 +0,0 @@ -body { - margin: 0 0 5px 5px; -} - -ul { - margin: 0; -} - -li { - list-style-type: none; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.navigator-selected { - background: #ffa500; -} - -.wrapper { - position: absolute; - top: 60px; - bottom: 0; - left: 400px; - right: 0; - overflow: auto; -} - -.navigator-root { - position: absolute; - top: 60px; - bottom: 0; - left: 0; - width: 400px; - overflow-y: auto; -} - -.suite { - margin: 0 10px 10px 0; - background-color: #fff8dc; -} - -.suite-name { - padding-left: 10px; - font-size: 25px; - font-family: Times, sans-serif; -} - -.main-panel-header { - padding: 5px; - background-color: #9FB4D9; /*afeeee*/; - font-family: monospace; - font-size: 18px; -} - -.main-panel-content { - padding: 5px; - margin-bottom: 10px; - background-color: #DEE8FC; /*d0ffff*/; -} - -.rounded-window { - border-radius: 10px; - border-style: solid; - border-width: 1px; -} - -.rounded-window-top { - border-top-right-radius: 10px 10px; - border-top-left-radius: 10px 10px; - border-style: solid; - border-width: 1px; - overflow: auto; -} - -.light-rounded-window-top { - border-top-right-radius: 10px 10px; - border-top-left-radius: 10px 10px; -} - -.rounded-window-bottom { - border-style: solid; - border-width: 0 1px 1px 1px; - border-bottom-right-radius: 10px 10px; - border-bottom-left-radius: 10px 10px; - overflow: auto; -} - -.method-name { - font-size: 12px; - font-family: monospace; -} - -.method-content { - border-style: solid; - border-width: 0 0 1px 0; - margin-bottom: 10px; - padding-bottom: 5px; - width: 80%; -} - -.parameters { - font-size: 14px; - font-family: monospace; -} - -.stack-trace { - white-space: pre; - font-family: monospace; - font-size: 12px; - font-weight: bold; - margin-top: 0; - margin-left: 20px; -} - -.testng-xml { - font-family: monospace; -} - -.method-list-content { - margin-left: 10px; -} - -.navigator-suite-content { - margin-left: 10px; - font: 12px 'Lucida Grande'; -} - -.suite-section-title { - margin-top: 10px; - width: 80%; - border-style: solid; - border-width: 1px 0 0 0; - font-family: Times, sans-serif; - font-size: 18px; - font-weight: bold; -} - -.suite-section-content { - list-style-image: url(bullet_point.png); -} - -.top-banner-root { - position: absolute; - top: 0; - height: 45px; - left: 0; - right: 0; - padding: 5px; - margin: 0 0 5px 0; - background-color: #0066ff; - font-family: Times, sans-serif; - color: #fff; - text-align: center; -} -.button{ - position: absolute; - margin-left:500px; - margin-top:8px; - background-color: white; - color:#0066ff; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight:bold; - border-color:#0066ff ; - border-radius:25px; - cursor: pointer; - height:30px; - width:150px; - outline:none; - -} - -.top-banner-title-font { - font-size: 25px; -} - -.test-name { - font-family: 'Lucida Grande', sans-serif; - font-size: 16px; -} - -.suite-icon { - padding: 5px; - float: right; - height: 20px; -} - -.test-group { - font: 20px 'Lucida Grande'; - margin: 5px 5px 10px 5px; - border-width: 0 0 1px 0; - border-style: solid; - padding: 5px; -} - -.test-group-name { - font-weight: bold; -} - -.method-in-group { - font-size: 16px; - margin-left: 80px; -} - -table.google-visualization-table-table { - width: 100%; -} - -.reporter-method-name { - font-size: 14px; - font-family: monospace; -} - -.reporter-method-output-div { - padding: 5px; - margin: 0 0 5px 20px; - font-size: 12px; - font-family: monospace; - border-width: 0 0 0 1px; - border-style: solid; -} - -.ignored-class-div { - font-size: 14px; - font-family: monospace; -} - -.ignored-methods-div { - padding: 5px; - margin: 0 0 5px 20px; - font-size: 12px; - font-family: monospace; - border-width: 0 0 0 1px; - border-style: solid; -} - -.border-failed { - border-top-left-radius: 10px 10px; - border-bottom-left-radius: 10px 10px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #f00; -} - -.border-skipped { - border-top-left-radius: 10px 10px; - border-bottom-left-radius: 10px 10px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #edc600; -} - -.border-passed { - border-top-left-radius: 10px 10px; - border-bottom-left-radius: 10px 10px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #19f52d; -} - -.times-div { - text-align: center; - padding: 5px; -} - -.suite-total-time { - font: 16px 'Lucida Grande'; -} - -.configuration-suite { - margin-left: 20px; -} - -.configuration-test { - margin-left: 40px; -} - -.configuration-class { - margin-left: 60px; -} - -.configuration-method { - margin-left: 80px; -} - -.test-method { - margin-left: 100px; -} - -.chronological-class { - background-color: skyblue; - border-style: solid; - border-width: 0 0 1px 1px; -} - -.method-start { - float: right; -} - -.chronological-class-name { - padding: 0 0 0 5px; - color: #008; -} - -.after, .before, .test-method { - font-family: monospace; - font-size: 14px; -} - -.navigator-suite-header { - font-size: 22px; - margin: 0 10px 5px 0; - background-color: #deb887; - text-align: center; -} - -.collapse-all-icon { - padding: 5px; - float: right; -} -/*retro Theme*/ diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js deleted file mode 100644 index c1a84a35d453..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports.js +++ /dev/null @@ -1,122 +0,0 @@ -$(document).ready(function() { - $('a.navigator-link').on("click", function() { - // Extract the panel for this link - var panel = getPanelName($(this)); - - // Mark this link as currently selected - $('.navigator-link').parent().removeClass('navigator-selected'); - $(this).parent().addClass('navigator-selected'); - - showPanel(panel); - }); - - installMethodHandlers('failed'); - installMethodHandlers('skipped'); - installMethodHandlers('passed', true); // hide passed methods by default - - $('a.method').on("click", function() { - showMethod($(this)); - return false; - }); - - // Hide all the panels and display the first one (do this last - // to make sure the click() will invoke the listeners) - $('.panel').hide(); - $('.navigator-link').first().trigger("click"); - - // Collapse/expand the suites - $('a.collapse-all-link').on("click", function() { - var contents = $('.navigator-suite-content'); - if (contents.css('display') == 'none') { - contents.show(); - } else { - contents.hide(); - } - }); -}); - -// The handlers that take care of showing/hiding the methods -function installMethodHandlers(name, hide) { - function getContent(t) { - return $('.method-list-content.' + name + "." + t.attr('panel-name')); - } - - function getHideLink(t, name) { - var s = 'a.hide-methods.' + name + "." + t.attr('panel-name'); - return $(s); - } - - function getShowLink(t, name) { - return $('a.show-methods.' + name + "." + t.attr('panel-name')); - } - - function getMethodPanelClassSel(element, name) { - var panelName = getPanelName(element); - var sel = '.' + panelName + "-class-" + name; - return $(sel); - } - - $('a.hide-methods.' + name).on("click", function() { - var w = getContent($(this)); - w.hide(); - getHideLink($(this), name).hide(); - getShowLink($(this), name).show(); - getMethodPanelClassSel($(this), name).hide(); - }); - - $('a.show-methods.' + name).on("click", function() { - var w = getContent($(this)); - w.show(); - getHideLink($(this), name).show(); - getShowLink($(this), name).hide(); - showPanel(getPanelName($(this))); - getMethodPanelClassSel($(this), name).show(); - }); - - if (hide) { - $('a.hide-methods.' + name).trigger("click"); - } else { - $('a.show-methods.' + name).trigger("click"); - } -} - -function getHashForMethod(element) { - return element.attr('hash-for-method'); -} - -function getPanelName(element) { - return element.attr('panel-name'); -} - -function showPanel(panelName) { - $('.panel').hide(); - var panel = $('.panel[panel-name="' + panelName + '"]'); - panel.show(); -} - -function showMethod(element) { - var hashTag = getHashForMethod(element); - var panelName = getPanelName(element); - showPanel(panelName); - var current = document.location.href; - var base = current.substring(0, current.indexOf('#')) - document.location.href = base + '#' + hashTag; - var newPosition = $(document).scrollTop() - 65; - $(document).scrollTop(newPosition); -} - -function drawTable() { - for (var i = 0; i < suiteTableInitFunctions.length; i++) { - window[suiteTableInitFunctions[i]](); - } - - for (var k in window.suiteTableData) { - var v = window.suiteTableData[k]; - var div = v.tableDiv; - var data = v.tableData - var table = new google.visualization.Table(document.getElementById(div)); - table.draw(data, { - showRowNumber : false - }); - } -} diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css deleted file mode 100644 index 570323ffb8fe..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports1.css +++ /dev/null @@ -1,344 +0,0 @@ -body { - background-color: whitesmoke; - margin: 0 0 5px 5px; -} -ul { - margin-top: 10px; - margin-left:-10px; -} - li { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - padding:5px 5px; - } - a { - text-decoration: none; - color: black; - font-size: 14px; - } - - a:hover { - color:black ; - text-decoration: underline; - } - - .navigator-selected { - /* #ffa500; Mouse hover color after click Orange.*/ - background:#027368 - } - - .wrapper { - position: absolute; - top: 60px; - bottom: 0; - left: 400px; - right: 0; - margin-right:9px; - overflow: auto;/*imortant*/ - } - - .navigator-root { - position: absolute; - top: 60px; - bottom: 0; - left: 0; - width: 400px; - overflow-y: auto;/*important*/ - } - - .suite { - margin: -5px 10px 10px 5px; - background-color: whitesmoke ;/*Colour of the left bside box*/ - } - - .suite-name { - font-size: 24px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;/*All TEST SUITE*/ - color: white; - } - - .main-panel-header { - padding: 5px; - background-color: #027368; /*afeeee*/; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color:white; - font-size: 18px; - } - - .main-panel-content { - padding: 5px; - margin-bottom: 10px; - background-color: #CCD0D1; /*d0ffff*/; /*Belongs to backGround of rightSide boxes*/ - } - - .rounded-window { - border-style: dotted; - border-width: 1px;/*Border of left Side box*/ - background-color: whitesmoke; - border-radius: 10px; - } - - .rounded-window-top { - border-top-right-radius: 10px 10px; - border-top-left-radius: 10px 10px; - border-style: solid; - border-width: 1px; - overflow: auto;/*Top of RightSide box*/ - } - - .light-rounded-window-top { - background-color: #027368; - padding-left:120px; - border-radius: 10px; - - } - - .rounded-window-bottom { - border-bottom-right-radius: 10px 10px; - border-bottom-left-radius: 10px 10px; - overflow: auto;/*Bottom of rightSide box*/ - } - - .method-name { - font-size: 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: bold; - } - - .method-content { - border-style: solid; - border-width: 0 0 1px 0; - margin-bottom: 10px; - padding-bottom: 5px; - width: 100%; - } - - .parameters { - font-size: 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - } - - .stack-trace { - white-space: pre; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; - font-weight: bold; - margin-top: 0; - margin-left: 20px; /*Error Stack Trace Message*/ - } - - .testng-xml { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - } - - .method-list-content { - margin-left: 10px; - } - - .navigator-suite-content { - margin-left: 10px; - font: 12px 'Lucida Grande'; - } - - .suite-section-title { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight:bold; - background-color: #8C8887; - margin-left: -10px; - margin-top:10px; - padding:6px; - } - - .suite-section-content { - list-style-image: url(bullet_point.png); - background-color: whitesmoke; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - overflow: hidden; - } - - .top-banner-root { - position: absolute; - top: 0; - height: 45px; - left: 0; - right: 0; - padding: 5px; - margin: 0 0 5px 0; - background-color: #027368; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 18px; - color: #fff; - text-align: center;/*Belongs to the Top of Report*//*Status: - Completed*/ - } - - .top-banner-title-font { - font-size: 25px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - padding: 3px; - float: right; - } - - .test-name { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - } - - .suite-icon { - padding: 5px; - float: right; - height: 20px; - } - - .test-group { - font: 20px 'Lucida Grande'; - margin: 5px 5px 10px 5px; - border-width: 0 0 1px 0; - border-style: solid; - padding: 5px; - } - - .test-group-name { - font-weight: bold; - } - - .method-in-group { - font-size: 16px; - margin-left: 80px; - } - - table.google-visualization-table-table { - width: 100%; - } - - .reporter-method-name { - font-size: 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - } - - .reporter-method-output-div { - padding: 5px; - margin: 0 0 5px 20px; - font-size: 12px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - border-width: 0 0 0 1px; - border-style: solid; - } - - .ignored-class-div { - font-size: 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - } - - .ignored-methods-div { - padding: 5px; - margin: 0 0 5px 20px; - font-size: 12px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - border-width: 0 0 0 1px; - border-style: solid; - } - - .border-failed { - border-radius:2px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #F20505; - } - - .border-skipped { - border-radius:2px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #F2BE22; - } - - .border-passed { - border-radius:2px; - border-style: solid; - border-width: 0 0 0 10px; - border-color: #038C73; - } - - .times-div { - text-align: center; - padding: 5px; - } - - .suite-total-time { - font: 16px 'Lucida Grande'; - } - - .configuration-suite { - margin-left: 20px; - } - - .configuration-test { - margin-left: 40px; - } - - .configuration-class { - margin-left: 60px; - } - - .configuration-method { - margin-left: 80px; - } - - .test-method { - margin-left: 100px; - } - - .chronological-class { - background-color: #CCD0D1; - border-width: 0 0 1px 1px;/*Chronological*/ - } - - .method-start { - float: right; - } - - .chronological-class-name { - padding: 0 0 0 5px; - margin-top:5px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #008; - } - - .after, .before, .test-method { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - margin-top:5px; - } - - .navigator-suite-header { - font-size: 18px; - margin: 0px 10px 10px 5px; - padding: 5px; - border-radius: 10px; - background-color: #027368; - color: white; - font-weight:bold; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - text-align: center; /*All Suites on top of left box*//*Status: -Completed*/ - } - - .collapse-all-icon { - padding: 3px; - float: right; - } - .button{ - position: absolute; - margin-left:500px; - margin-top:8px; - background-color: white; - color:#027368; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight:bold; - border-color:#027368; - border-radius:25px; - cursor: pointer; - height:30px; - width:150px; - outline: none; -} -/*Author: - Akhil Gullapalli*/ \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js deleted file mode 100644 index 5342859fa4df..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-reports2.js +++ /dev/null @@ -1,76 +0,0 @@ -window.onload = function () { - let cookies = document.cookie; - let cookieValue = cookies.split('='); - if (cookieValue[1] === 'null' || localStorage.getItem('Theme') === 'null') { - document.getElementById('retro').setAttribute('disabled', 'false'); - } else if (cookieValue[1] === 'Switch Ultra Theme' || - localStorage.getItem('Theme') === 'Switch Ultra Theme') { - document.getElementById('button').innerText = "Switch Retro Theme"; - document.getElementById('retro').setAttribute('disabled', 'false'); - - } else if (cookieValue[1] === 'Switch Retro Theme' || - localStorage.getItem('Theme') === 'Switch Retro Theme') { - if (cookieValue[1] === 'Switch Ultra Theme' || - localStorage.getItem('Theme') === 'Switch Ultra Theme') { - document.getElementById('button').innerText = "Switch Retro Theme"; - document.getElementById('retro').setAttribute('disabled', 'false'); - - document.getElementById('button').innerText = "Switch Ultra Theme"; - document.getElementById('retro').removeAttribute('disabled'); - document.getElementById('ultra').setAttribute('disabled', 'false'); - localStorage.setItem('Theme', select); - - } else if (select === 'Switch Ultra Theme') { - document.getElementById('button').innerText = "Switch Retro Theme"; - document.getElementById('ultra').removeAttribute('disabled'); - document.getElementById('retro').setAttribute('disabled', 'false'); - localStorage.setItem('Theme', select); - } - } else if (cookieValue[1] === 'Switch Retro Theme' || - localStorage.getItem('Theme') === 'Switch Retro Theme') { - document.getElementById('button').innerText = "Switch Ultra Theme"; - document.getElementById('ultra').setAttribute('disabled', 'false'); - } -} -document.getElementById('button').onclick = function () { - let select = document.getElementById('button').innerText; - if (select === 'Switch Retro Theme') { - let d = new Date(); - days = 365; - d.setTime(+d + (days * 86400000)); //24 * 60 * 60 * 1000 - document.cookie = "Theme =" + select + "; expires=" + d.toGMTString() + ";"; - document.getElementById('button').innerText = "Switch Ultra Theme"; - document.getElementById('retro').removeAttribute('disabled'); - document.getElementById('ultra').setAttribute('disabled', 'false'); - localStorage.setItem('Theme', select); - - } else if (select === 'Switch Ultra Theme') { - let d = new Date(); - days = 365; - d.setTime(+d + (days * 86400000)); //24 * 60 * 60 * 1000 - document.cookie = "Theme =" + select + "; expires=" + d.toGMTString() + ";"; - document.getElementById('button').innerText = "Switch Retro Theme"; - document.getElementById('ultra').removeAttribute('disabled'); - document.getElementById('retro').setAttribute('disabled', 'false'); - localStorage.setItem('Theme', select); - } -} -//Function to mouse hovering affect. -document.getElementById('button').onmouseover = function () { - document.getElementById('button').style.borderRadius = "25px"; - document.getElementById('button').style.width = "180px"; - document.getElementById('button').style.height = "45px"; - document.getElementById('button').style.marginTop = "1px"; - -} -//Function to mouse out affect -document.getElementById('button').onmouseout = function () { - document.getElementById('button').style.borderRadius = "25px"; - document.getElementById('button').style.width = "150px"; - document.getElementById('button').style.height = "30px"; - document.getElementById('button').style.marginTop = "8px"; - -} - -//This is the file where we handle the switching of the Themes. -/*Author:- Akhil Gullapalli*/ diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml deleted file mode 100644 index efe99aa4b70a..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng-results.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css b/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css deleted file mode 100644 index 5124ba863b37..000000000000 --- a/sdk/cosmos/azure-cosmos-kafka-connect/test-output/testng.css +++ /dev/null @@ -1,9 +0,0 @@ -.invocation-failed, .test-failed { background-color: #DD0000; } -.invocation-percent, .test-percent { background-color: #006600; } -.invocation-passed, .test-passed { background-color: #00AA00; } -.invocation-skipped, .test-skipped { background-color: #CCCC00; } - -.main-page { - font-size: x-large; -} - From 892a7d79c3990f68bed9954c30eedf00d10051f8 Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 14:56:29 -0700 Subject: [PATCH 7/8] Cosmos Java: trim comments to core logic; drop cross-SDK references Per PR feedback, comments in the shared-cache implementation were too verbose and contained cross-SDK comparisons that don't add value to maintainers reading the Java code. Trimmed everywhere: - SharedRoutingMapCacheRegistry: removed Python/Rust comparison paragraphs, the "Cross-SDK consistency" and "Leaked-client safety net" walls of text, and condensed JavaDoc on individual methods. Kept only the critical "lambda must not capture owner" comment because it's a correctness invariant that's easy to break in a refactor. - RxPartitionKeyRangeCache: removed the long ownerPhantom-style field comments; consolidated the class JavaDoc into two sentences. - Configs: condensed the system-property comment to two lines. - RxDocumentClientImpl: shortened the close-path log message. - CHANGELOG entry: condensed to a single sentence describing the change and the opt-out flag. - Tests: stripped the "First client / Second client" narration, the "must hit the shared cache" explanations, and the multi-paragraph preambles on the leak tests. Kept enough to explain the GC-related test setup since that's not obvious from the code. Behavior unchanged; 36 cache unit tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../caches/RxPartitionKeyRangeCacheTest.java | 14 +- .../SharedRoutingMapCacheRegistryTest.java | 54 ++----- sdk/cosmos/azure-cosmos/CHANGELOG.md | 2 +- .../azure/cosmos/implementation/Configs.java | 6 +- .../implementation/RxDocumentClientImpl.java | 2 +- .../caches/RxPartitionKeyRangeCache.java | 27 +--- .../caches/SharedRoutingMapCacheRegistry.java | 147 ++++-------------- 7 files changed, 50 insertions(+), 202 deletions(-) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java index b2ab667200cd..972adad416dd 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java @@ -251,9 +251,6 @@ public void tryLookupAsync_RetriesOnceAndConvertsToNotFoundException() { @Test(groups = "unit") public void twoCachesForSameEndpointShareRoutingMapStorage() throws Exception { - // Two RxPartitionKeyRangeCache instances pointing at the same endpoint should - // share their underlying AsyncCacheNonBlocking, so a routing map populated by - // one is immediately visible to the other without a second /pkranges call. URI endpoint = new URI("https://test-shared-pkr-1.documents.azure.com:443/"); RxDocumentClientImpl clientA = Mockito.mock(RxDocumentClientImpl.class); @@ -298,22 +295,16 @@ public void twoCachesForSameEndpointShareRoutingMapStorage() throws Exception { RxPartitionKeyRangeCache cacheB = new RxPartitionKeyRangeCache(clientB, collB, endpoint); try { - // First client populates the cache. StepVerifier.create(cacheA.tryLookupAsync(null, collectionRid, null, new HashMap<>())) .expectNextMatches(v -> v != null && v.v != null) .verifyComplete(); - // Second client must hit the shared cache without issuing its own /pkranges read. StepVerifier.create(cacheB.tryLookupAsync(null, collectionRid, null, new HashMap<>())) .expectNextMatches(v -> v != null && v.v != null) .verifyComplete(); - assertThat(clientACalls.get()) - .as("client A populates the shared routing map") - .isEqualTo(1); - assertThat(clientBCalls.get()) - .as("client B must hit the shared cache without issuing its own /pkranges call") - .isZero(); + assertThat(clientACalls.get()).isEqualTo(1); + assertThat(clientBCalls.get()).isZero(); } finally { cacheA.close(); cacheB.close(); @@ -378,7 +369,6 @@ public void cachesForDifferentEndpointsDoNotShareStorage() throws Exception { .expectNextMatches(v -> v != null && v.v != null) .verifyComplete(); - // Two different endpoints → no sharing, each client issues its own read. assertThat(clientACalls.get()).isEqualTo(1); assertThat(clientBCalls.get()).isEqualTo(1); } finally { diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java index 0f5982a5417d..05771589a3ff 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java @@ -19,13 +19,8 @@ import static org.assertj.core.api.Assertions.assertThat; -/** - * Unit tests for {@link SharedRoutingMapCacheRegistry}. - * - *

The registry is a process-wide singleton, so these tests must leave it - * in a clean state for whatever endpoints they touch. Each test uses a - * uniquely-named endpoint and releases every reference it acquires.

- */ +/** Unit tests for {@link SharedRoutingMapCacheRegistry}. Each test uses a unique endpoint + * and releases every reference, since the registry is a process-wide singleton. */ public class SharedRoutingMapCacheRegistryTest { private static final String ENABLE_FLAG = "COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED"; @@ -84,15 +79,12 @@ public void acquireReturnsDifferentInstanceForDifferentEndpoints() { @Test(groups = "unit") public void acquireTreatsHostCaseInsensitivelyMatchingUriEquals() { - // URI.equals is case-insensitive on host (RFC 3986). Two clients built with - // mixed-case host names must collapse to a single shared cache entry — this - // is the reason we key on URI rather than URI.toString(). + // URI.equals is case-insensitive on host (RFC 3986); confirm clients with + // mixed-case host names collapse into one shared entry. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI lower = URI.create("https://test-acct-share-case.documents.azure.com:443/"); URI mixed = URI.create("https://Test-Acct-Share-Case.documents.azure.com:443/"); - // Sanity: URI.equals must consider these equal; otherwise the test below - // would silently regress without anyone noticing. assertThat(lower).isEqualTo(mixed); AsyncCacheNonBlocking a = registry.acquire(lower); @@ -126,7 +118,7 @@ public void releaseEvictsAtZeroRefcount() { registry.release(endpoint, b); assertThat(registry.referenceCount(endpoint)).isZero(); - // After eviction, a fresh acquire produces a brand-new cache (not the previous one). + // After eviction, fresh acquire produces a new cache instance. AsyncCacheNonBlocking c = registry.acquire(endpoint); try { assertThat(c).isNotSameAs(a); @@ -138,8 +130,6 @@ public void releaseEvictsAtZeroRefcount() { @Test(groups = "unit") public void releaseIsIdempotentWhenSuppliedSameCacheRepeatedly() { - // The registry's contract is that calling release with a cache instance - // that is not currently registered (e.g. already-evicted) is a no-op. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-4.documents.azure.com:443/"); @@ -147,15 +137,13 @@ public void releaseIsIdempotentWhenSuppliedSameCacheRepeatedly() { registry.release(endpoint, a); assertThat(registry.referenceCount(endpoint)).isZero(); - // Releasing again with the now-stale cache reference must not crash or go negative. + // Releasing a stale cache reference must not crash or drive refcount negative. registry.release(endpoint, a); assertThat(registry.referenceCount(endpoint)).isZero(); } @Test(groups = "unit") public void releaseIsNoOpWhenCacheIsNotTheRegisteredInstance() { - // After eviction and re-acquire, the registry holds a different instance. - // Releasing the old (stale) reference must not affect the new registered entry. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-5.documents.azure.com:443/"); @@ -182,7 +170,6 @@ public void nullEndpointReturnsIsolatedCacheAndDoesNotEnterRegistry() { assertThat(a).isNotSameAs(b); assertThat(registry.registeredEndpointCount()).isEqualTo(before); - // Release with null endpoint is a safe no-op. registry.release(null, a); registry.release(null, b); } @@ -200,12 +187,10 @@ public void disabledFlagReturnsIsolatedCachesAndPreservesRegistryEmpty() { AsyncCacheNonBlocking b = registry.acquire(endpoint); try { - // With sharing disabled, each acquire returns a fresh, isolated cache. assertThat(a).isNotSameAs(b); assertThat(registry.registeredEndpointCount()).isEqualTo(before); assertThat(registry.referenceCount(endpoint)).isZero(); } finally { - // Release should be safe (no-op) since these caches were never registered. registry.release(endpoint, a); registry.release(endpoint, b); } @@ -252,26 +237,19 @@ public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Excep pool.shutdownNow(); } - // All acquires matched by releases → refcount must be zero and entry evicted. assertThat(registry.referenceCount(endpoint)).isZero(); } @Test(groups = "unit") public void referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Exception { - // Simulates a customer that forgot to call CosmosClient.close(). The owning - // object (here: an opaque Object stand-in for RxPartitionKeyRangeCache) is - // discarded; azure-core's ReferenceManager observes that the owner is - // phantom-reachable and runs the registered cleanup which decrements the refcount. + // Owner is allocated in a separate stack frame so this frame can't keep it alive; + // ReferenceManager runs the cleanup once GC observes the owner as phantom-reachable. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-1.documents.azure.com:443/"); - // Acquire and immediately discard the owner in a separate stack frame so the - // test frame cannot keep a hidden strong reference alive past the call. acquireAndLeakOwner(registry, endpoint); assertThat(registry.referenceCount(endpoint)).isEqualTo(1); - // Wait for the ReferenceManager to observe the GC and run the cleanup action. - // Poll up to 15 s; on most JVMs this completes within a few GC cycles. boolean released = false; long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(15); while (System.nanoTime() < deadlineNanos) { @@ -290,31 +268,21 @@ public void referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected() thr .isTrue(); } - /** - * Helper that allocates an owner, registers it with the registry, and returns — - * letting the owner immediately become eligible for GC. Living in its own stack - * frame guarantees the caller's frame cannot keep the owner alive. - */ private static void acquireAndLeakOwner(SharedRoutingMapCacheRegistry registry, URI endpoint) { Object owner = new Object(); registry.acquire(endpoint, owner); - // owner falls out of scope on return; nothing else references it. + // owner falls out of scope on return. } @Test(groups = "unit") public void promptCloseFulfillsHandleSoReferenceManagerCleanupIsANoop() throws Exception { - // After a prompt close() the registry's refcount is already zero and the - // entry already evicted. When the JVM later runs the ReferenceManager cleanup - // action after the owner is GC'd, the ReleaseHandle is already fulfilled so - // the action is a no-op — exercised by referenceCount staying at zero across - // a forced GC cycle. SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-2.documents.azure.com:443/"); acquireAndPromptlyClose(registry, endpoint); assertThat(registry.referenceCount(endpoint)).isZero(); - // Force GC; refcount must remain zero, no exception. + // GC + cleanup-action firing must not drive the refcount negative. for (int i = 0; i < 5; i++) { System.gc(); System.runFinalization(); @@ -326,8 +294,6 @@ public void promptCloseFulfillsHandleSoReferenceManagerCleanupIsANoop() throws E private static void acquireAndPromptlyClose(SharedRoutingMapCacheRegistry registry, URI endpoint) { Object owner = new Object(); SharedRoutingMapCacheRegistry.AcquireResult result = registry.acquire(endpoint, owner); - // Simulate the prompt RxPartitionKeyRangeCache.close() path. This fulfils - // the handle so the ReferenceManager-registered cleanup later becomes a no-op. registry.release(endpoint, result.cache, result.releaseHandle); } } diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md index f8a6e307d9f7..0174f4374d83 100644 --- a/sdk/cosmos/azure-cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md @@ -10,7 +10,7 @@ #### Other Changes * Reduced memory footprint of deserialized `PartitionKeyRange` instances by stripping unused fields in the `PartitionKeyRange(ObjectNode)` constructor - See PR [49513](https://github.com/Azure/azure-sdk-for-java/pull/49513). -* Reduced memory footprint and redundant `/pkranges` reads when multiple `CosmosClient` / `CosmosAsyncClient` instances in the same JVM target the same Cosmos account. The partition-key-range routing-map cache (`RxPartitionKeyRangeCache`) is now process-scoped and shared across clients via a refcounted registry keyed by service endpoint; the shared entry is evicted when the last client closes. Sharing also strengthens the single-flight invariant: only one in-flight `/pkranges` fetch per `(account, container)` at any time, even across clients. Disable with system property `COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false` if needed. +* Reduced memory footprint and redundant `/pkranges` reads when multiple `CosmosClient` / `CosmosAsyncClient` instances in the same JVM target the same Cosmos account. The partition-key-range routing-map cache (`RxPartitionKeyRangeCache`) is now process-scoped and shared across clients via a refcounted registry keyed by service endpoint URI; the shared entry is evicted when the last client closes. Disable with system property `COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false` if needed. ### 4.81.0 (2026-06-08) 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 383e7ff77856..a68bf363fa85 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 @@ -218,10 +218,8 @@ public class Configs { private static final String USE_LEGACY_TRACING = "COSMOS.USE_LEGACY_TRACING"; private static final boolean DEFAULT_USE_LEGACY_TRACING = false; - // Whether multiple CosmosClient instances in the same JVM that target the same - // service endpoint share a single partition-key-range routing-map cache. - // Enabled by default. Setting this to false restores the pre-sharing behaviour - // where every client owns a private cache (useful as a safety valve only). + // Whether multiple CosmosClient instances targeting the same endpoint share + // a single partition-key-range cache. Enabled by default. private static final String SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED = "COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED"; private static final boolean DEFAULT_SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED = true; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java index a561db304388..ad1840225e5a 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java @@ -7466,7 +7466,7 @@ public void close() { } if (this.partitionKeyRangeCache != null) { - logger.info("Releasing shared PartitionKeyRangeCache reference ..."); + logger.info("Closing PartitionKeyRangeCache ..."); LifeCycleUtils.closeQuietly(this.partitionKeyRangeCache); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index 61440a88a949..0b497b57450c 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -47,13 +47,10 @@ * While this class is public, but it is not part of our published public APIs. * This is meant to be internally used only by our sdk. * - *

The underlying routing-map storage ({@link AsyncCacheNonBlocking}) is - * obtained from {@link SharedRoutingMapCacheRegistry} keyed by the service - * endpoint URI. Multiple {@code CosmosClient} instances targeting the same - * service endpoint share a single routing-map cache. {@link #close()} - * releases this instance's reference; the shared cache is evicted only - * when the last reference is released. The fetching logic (network call, - * collection resolution, diagnostics) remains per-client.

+ *

The routing-map storage is obtained from {@link SharedRoutingMapCacheRegistry} + * keyed by the service endpoint URI; multiple clients targeting the same endpoint + * share it. {@link #close()} releases this instance's reference. The fetching + * logic (network call, collection resolution, diagnostics) remains per-client.

**/ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closeable { private final Logger logger = LoggerFactory.getLogger(RxPartitionKeyRangeCache.class); @@ -64,12 +61,6 @@ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closea private final DiagnosticsClientContext clientContext; private final URI sharedCacheEndpointKey; private final AtomicBoolean closed = new AtomicBoolean(false); - /** - * Handle returned by {@link SharedRoutingMapCacheRegistry#acquire(URI, Object)} - * that lets {@link #close()} mark the deferred cleanup action (registered with - * {@link com.azure.core.util.ReferenceManager}) as already-fulfilled. May be - * {@code null} for isolated caches (sharing disabled / null endpoint). - */ private final SharedRoutingMapCacheRegistry.ReleaseHandle releaseHandle; public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { @@ -91,15 +82,7 @@ public RxPartitionKeyRangeCache( this.clientContext = client; } - /** - * Releases this instance's reference to the shared routing-map cache. - * Safe to call multiple times; only the first call has an effect. - * After {@code close()} the instance must not be used further. - * - *

Marks the {@link com.azure.core.util.ReferenceManager}-registered - * cleanup action as fulfilled so it becomes a no-op when the JVM later - * runs it after this instance is GC'd.

- */ + /** Idempotent release of this instance's shared-cache reference. */ @Override public void close() { if (closed.compareAndSet(false, true)) { diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java index c08216b554e2..ead4560b45ab 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java @@ -14,68 +14,17 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Process-wide registry of {@link AsyncCacheNonBlocking} instances that hold - * the cached partition-key-range routing maps, keyed by the Cosmos service - * endpoint URI. + * Process-wide registry of {@link AsyncCacheNonBlocking} instances holding + * partition-key-range routing maps, keyed by service endpoint URI. Multiple + * {@code CosmosAsyncClient} instances targeting the same account share a single + * cache; the entry is refcounted and evicted when the last caller releases. * - *

Historically, every {@link RxPartitionKeyRangeCache} owned its own - * private {@link AsyncCacheNonBlocking}. When many {@code CosmosAsyncClient} - * instances in the same JVM target the same account (a common multi-tenant / - * multi-credential pattern), each client paid an independent memory cost for - * the routing-map cache and independently issued {@code /pkranges} reads for - * the same containers. + *

An unreleased caller is cleaned up by registering a one-shot action with + * {@link ReferenceManager#INSTANCE}; when the owner becomes phantom-reachable + * the action decrements the refcount.

* - *

This registry lets clients targeting the same account share a single - * cache instance. Sharing also strengthens the single-flight invariant - * already provided by {@link AsyncCacheNonBlocking}: only one in-flight - * {@code /pkranges} fetch per (account, container) at any time, even across - * clients.

- * - *

Key identity. The {@link URI} is used directly so - * {@link URI#equals(Object)}'s case-insensitive host comparison applies — two - * clients built with {@code https://Acct.documents.azure.com/} and - * {@code https://acct.documents.azure.com/} share a single cache entry as - * intended.

- * - *

Cross-SDK consistency. The peer Cosmos DB SDKs key sharing on the - * user-supplied account endpoint URL: Python uses {@code client.url_connection} - * (raw string compare); Rust uses {@code AccountEndpoint(Url)} (URL-based - * equality). This implementation matches that contract. As a consequence, - * two clients to the same account that bootstrap from different - * regional endpoints (e.g. {@code my-acct-westus.documents.azure.com} vs - * {@code my-acct.documents.azure.com}) do not share a cache entry — - * the same fragmentation behaviour the peer SDKs have.

- * - *

Lifecycle. Callers obtain a shared cache via - * {@link #acquire(URI, Object)} during construction and return it via - * {@link #release(URI, AsyncCacheNonBlocking, ReleaseHandle)} during - * {@code close()}. A per-entry refcount tracks live callers; when the count - * reaches zero the entry is evicted so an idle endpoint does not pin memory - * forever.

- * - *

Leaked-client safety net. A caller may forget to {@code close()} - * its {@code CosmosAsyncClient}. Without protection the unclosed client would - * keep a strong reference to the shared cache and pin it for the JVM's - * lifetime. To handle that, every {@link #acquire(URI, Object)} also - * registers a cleanup action with {@link ReferenceManager#INSTANCE} (the - * SDK-wide reference manager in {@code azure-core}). When the owner object - * becomes phantom-reachable, the reference manager runs the cleanup action - * which decrements the refcount and evicts the entry if it was the last - * reference. On Java 9+ {@code azure-core}'s {@code ReferenceManagerImpl} - * delegates to {@link java.lang.ref.Cleaner} reflectively; on Java 8 it uses - * an internal {@link java.lang.ref.PhantomReference}-based daemon thread. - * Cosmos reuses the supported, well-tested azure-core machinery rather than - * rolling its own.

- * - *

Opt-out. Setting the system property - * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false} disables - * sharing; each {@link #acquire(URI, Object)} returns a fresh, isolated cache - * (no registry entry, no cleanup registration).

- * - *

Concurrency. All state transitions go through - * {@link ConcurrentHashMap#compute(Object, java.util.function.BiFunction)}, - * which gives atomic check-and-update under a per-key lock. No global lock - * is required.

+ *

Sharing can be disabled via system property + * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false}.

*/ public final class SharedRoutingMapCacheRegistry { private static final Logger logger = LoggerFactory.getLogger(SharedRoutingMapCacheRegistry.class); @@ -92,29 +41,16 @@ public static SharedRoutingMapCacheRegistry getInstance() { } /** - * Returns the shared {@link AsyncCacheNonBlocking} for the given endpoint, - * creating it if necessary, and increments the refcount. - * - *

If {@code endpoint} is {@code null} or sharing is disabled via - * {@link Configs#isSharedPartitionKeyRangeCacheEnabled()}, returns a fresh - * isolated cache that the caller fully owns.

+ * Returns the shared cache for {@code endpoint} (creating it if needed) and + * bumps the refcount. Returns an isolated cache when {@code endpoint} is + * {@code null} or sharing is disabled. * - *

When {@code owner} is non-null and sharing is enabled, a cleanup - * action is registered with {@link ReferenceManager#INSTANCE} so that an - * unreferenced (leaked) owner triggers a deferred release.

- * - * @param endpoint The Cosmos service endpoint URI, or {@code null} for an isolated cache. - * @param owner The object whose unreachability should trigger a deferred release - * (typically the {@link RxPartitionKeyRangeCache} caller). May be {@code null} - * to skip cleanup registration (e.g. tests calling acquire directly). - * @return A handle that exposes the shared cache instance plus a token used by - * {@link RxPartitionKeyRangeCache#close()} to mark the cleanup action - * already-fulfilled so it becomes a no-op when {@link ReferenceManager} - * later runs it. + *

If {@code owner} is non-null, a deferred cleanup action is registered + * so the refcount is decremented automatically if {@code owner} is GC'd + * without calling {@link #release(URI, AsyncCacheNonBlocking, ReleaseHandle)}.

*/ public AcquireResult acquire(URI endpoint, Object owner) { if (endpoint == null || !Configs.isSharedPartitionKeyRangeCacheEnabled()) { - // Caller-owned cache; never enters the shared map. return new AcquireResult(new AsyncCacheNonBlocking<>(), null); } @@ -131,19 +67,16 @@ public AcquireResult acquire(URI endpoint, Object owner) { ReleaseHandle handle = null; if (owner != null) { - // IMPORTANT: the cleanup action must NOT capture `owner`, or the - // owner will never become phantom-reachable. We capture only the - // endpoint URI and the cache reference — both independent of the owner. + // The cleanup lambda MUST NOT capture `owner`, otherwise the owner can + // never become phantom-reachable and the cleanup will never run. final URI capturedEndpoint = endpoint; final AsyncCacheNonBlocking capturedCache = entry.cache; final ReleaseHandle h = new ReleaseHandle(); ReferenceManager.INSTANCE.register(owner, () -> { if (h.fulfill()) { logger.warn( - "Leaked (unclosed) RxPartitionKeyRangeCache detected for endpoint [{}]" - + " — releasing shared cache reference via ReferenceManager. Always" - + " close CosmosClient / CosmosAsyncClient to avoid relying on this" - + " safety net.", + "Leaked RxPartitionKeyRangeCache for endpoint [{}] released by" + + " ReferenceManager; always close the CosmosClient to avoid this.", capturedEndpoint); release(capturedEndpoint, capturedCache); } @@ -153,33 +86,26 @@ public AcquireResult acquire(URI endpoint, Object owner) { return new AcquireResult(entry.cache, handle); } - /** - * Convenience overload used by tests that do not need cleanup registration. - */ + /** Test-only overload without cleanup registration. */ AsyncCacheNonBlocking acquire(URI endpoint) { return acquire(endpoint, null).cache; } /** - * Prompt-close path used by {@link RxPartitionKeyRangeCache#close()}. - * Marks the cleanup action as fulfilled (so the later - * {@link ReferenceManager}-triggered run becomes a no-op) and decrements - * the refcount. + * Prompt release path. Fulfils {@code handle} (so the deferred cleanup + * becomes a no-op) and decrements the refcount. If the handle was already + * fulfilled by the deferred cleanup, this call is a no-op. */ public void release(URI endpoint, AsyncCacheNonBlocking cache, ReleaseHandle handle) { if (handle != null && !handle.fulfill()) { - // Already fulfilled by the ReferenceManager path; do not double-decrement. return; } release(endpoint, cache); } - /** - * Internal release used by both the prompt-close path and the - * ReferenceManager-triggered cleanup action. - */ + /** Refcount decrement; evicts the entry at zero. */ public void release(URI endpoint, AsyncCacheNonBlocking cache) { if (endpoint == null || cache == null) { return; @@ -187,9 +113,6 @@ public void release(URI endpoint, AsyncCacheNonBlocking { if (existing == null || existing.cache != cache) { - // Either sharing was disabled when this cache was acquired - // (isolated cache, never registered) or another release() - // already evicted the entry. Nothing to do. return existing; } int remaining = existing.refCount.decrementAndGet(); @@ -201,28 +124,18 @@ public void release(URI endpoint, AsyncCacheNonBlocking cache; public final ReleaseHandle releaseHandle; @@ -235,10 +148,8 @@ public static final class AcquireResult { } /** - * One-shot fulfilment flag shared between the prompt-close path and the - * deferred {@link ReferenceManager} cleanup. Whichever path runs first - * wins via {@link AtomicBoolean#compareAndSet}; the loser becomes a no-op - * so the refcount is decremented exactly once. + * One-shot CAS flag shared between prompt-close and deferred cleanup; + * guarantees exactly one refcount decrement. */ public static final class ReleaseHandle { private final AtomicBoolean fulfilled = new AtomicBoolean(false); From 1a5e92d3d8a43c8e0408b24488249240df1c6e9a Mon Sep 17 00:00:00 2001 From: Annie Liang Date: Fri, 19 Jun 2026 15:02:53 -0700 Subject: [PATCH 8/8] Cosmos Java: address PR review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed SharedRoutingMapCacheRegistry → SharedPartitionKeyRangeCacheRegistry for consistency with the class it serves (RxPartitionKeyRangeCache). - Removed the test-only acquire(URI) overload that bypassed ReferenceManager registration; tests now use acquire(URI, owner) so the cleanup-action path is exercised end-to-end. - Added clientWithServiceEndpointAcquiresAndReleasesRegistryRefcount: regression test guarding the RxDocumentClientImpl.close() → partitionKeyRangeCache.close() → refcount-- wiring. Constructs the cache via the 2-arg ctor (matching production) and asserts the refcount delta on construct and close. - Added forceRefreshOnSharedCacheIsVisibleToSiblingClient: cross-client invalidation propagation. Client A populates → A force-refreshes after a simulated split → B's lookup sees A's refreshed value (same routing-map instance) without issuing its own /pkranges call. Asserts object identity on the shared CollectionRoutingMap. 38 cache unit tests pass (was 36). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../caches/RxPartitionKeyRangeCacheTest.java | 124 ++++++++++++++++- ...edPartitionKeyRangeCacheRegistryTest.java} | 131 +++++++++--------- .../caches/RxPartitionKeyRangeCache.java | 10 +- ...SharedPartitionKeyRangeCacheRegistry.java} | 15 +- 4 files changed, 198 insertions(+), 82 deletions(-) rename sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/{SharedRoutingMapCacheRegistryTest.java => SharedPartitionKeyRangeCacheRegistryTest.java} (62%) rename sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/{SharedRoutingMapCacheRegistry.java => SharedPartitionKeyRangeCacheRegistry.java} (92%) diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java index 972adad416dd..872d2391059e 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCacheTest.java @@ -310,7 +310,7 @@ public void twoCachesForSameEndpointShareRoutingMapStorage() throws Exception { cacheB.close(); } - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) + assertThat(SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint)) .as("close() releases the shared cache reference") .isZero(); } @@ -384,15 +384,133 @@ public void closeIsIdempotent() throws Exception { RxCollectionCache mockColl = Mockito.mock(RxCollectionCache.class); RxPartitionKeyRangeCache c = new RxPartitionKeyRangeCache(mockClient, mockColl, endpoint); - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) + assertThat(SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint)) .isEqualTo(1); c.close(); c.close(); // second call must be a no-op c.close(); - assertThat(SharedRoutingMapCacheRegistry.getInstance().referenceCount(endpoint)) + assertThat(SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint)) .as("repeated close() must not drive refcount negative") .isZero(); } + + @Test(groups = "unit") + public void clientWithServiceEndpointAcquiresAndReleasesRegistryRefcount() throws Exception { + // Regression-guard for the RxDocumentClientImpl.close() -> partitionKeyRangeCache.close() + // wiring: constructing the cache must bump the registry refcount; close() must drop it. + URI endpoint = new URI("https://test-pkr-lifecycle.documents.azure.com:443/"); + RxDocumentClientImpl mockClient = Mockito.mock(RxDocumentClientImpl.class); + when(mockClient.getServiceEndpoint()).thenReturn(endpoint); + RxCollectionCache mockColl = Mockito.mock(RxCollectionCache.class); + + int before = SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint); + + // 2-arg ctor mirrors what RxDocumentClientImpl actually uses. + RxPartitionKeyRangeCache c = new RxPartitionKeyRangeCache(mockClient, mockColl); + try { + assertThat(SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint)) + .isEqualTo(before + 1); + } finally { + c.close(); + } + assertThat(SharedPartitionKeyRangeCacheRegistry.getInstance().referenceCount(endpoint)) + .isEqualTo(before); + } + + @Test(groups = "unit") + public void forceRefreshOnSharedCacheIsVisibleToSiblingClient() throws Exception { + // Cross-client invalidation propagation: client A force-refreshes a routing map, + // the new value must be visible to client B's next lookup. + URI endpoint = new URI("https://test-shared-pkr-refresh.documents.azure.com:443/"); + + RxDocumentClientImpl clientA = Mockito.mock(RxDocumentClientImpl.class); + RxDocumentClientImpl clientB = Mockito.mock(RxDocumentClientImpl.class); + RxCollectionCache collA = Mockito.mock(RxCollectionCache.class); + RxCollectionCache collB = Mockito.mock(RxCollectionCache.class); + + String collectionRid = "refresh-coll-1"; + DocumentCollection collection = new DocumentCollection(); + collection.setResourceId(collectionRid); + collection.setSelfLink("dbs/db1/colls/coll1"); + + PartitionKeyRange rangeBefore = new PartitionKeyRange(); + rangeBefore.setId("0"); + rangeBefore.setMinInclusive(PartitionKeyRange.MINIMUM_INCLUSIVE_EFFECTIVE_PARTITION_KEY); + rangeBefore.setMaxExclusive(PartitionKeyRange.MAXIMUM_EXCLUSIVE_EFFECTIVE_PARTITION_KEY); + + // After refresh: a split scenario produces two child ranges with the original as parent. + PartitionKeyRange rangeAfter1 = new PartitionKeyRange(); + rangeAfter1.setId("1"); + rangeAfter1.setMinInclusive(PartitionKeyRange.MINIMUM_INCLUSIVE_EFFECTIVE_PARTITION_KEY); + rangeAfter1.setMaxExclusive("80"); + rangeAfter1.setParents(Arrays.asList("0")); + PartitionKeyRange rangeAfter2 = new PartitionKeyRange(); + rangeAfter2.setId("2"); + rangeAfter2.setMinInclusive("80"); + rangeAfter2.setMaxExclusive(PartitionKeyRange.MAXIMUM_EXCLUSIVE_EFFECTIVE_PARTITION_KEY); + rangeAfter2.setParents(Arrays.asList("0")); + + FeedResponse responseBefore = Mockito.mock(FeedResponse.class); + when(responseBefore.getResults()).thenReturn(Arrays.asList(rangeBefore)); + when(responseBefore.getContinuationToken()).thenReturn("etag-before"); + + FeedResponse responseAfter = Mockito.mock(FeedResponse.class); + when(responseAfter.getResults()).thenReturn(Arrays.asList(rangeAfter1, rangeAfter2)); + when(responseAfter.getContinuationToken()).thenReturn("etag-after"); + + when(collA.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + when(collB.resolveCollectionAsync(any(), any())) + .thenReturn(Mono.just(new Utils.ValueHolder<>(collection))); + + // Client A first returns the pre-split layout, then the post-split layout on refresh. + AtomicInteger clientACalls = new AtomicInteger(); + when(clientA.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + int n = clientACalls.incrementAndGet(); + return n == 1 ? Flux.just(responseBefore) : Flux.just(responseAfter); + }); + AtomicInteger clientBCalls = new AtomicInteger(); + when(clientB.readPartitionKeyRanges(eq(collection.getSelfLink()), any(CosmosQueryRequestOptions.class))) + .thenAnswer(invocation -> { + clientBCalls.incrementAndGet(); + return Flux.just(responseAfter); + }); + + RxPartitionKeyRangeCache cacheA = new RxPartitionKeyRangeCache(clientA, collA, endpoint); + RxPartitionKeyRangeCache cacheB = new RxPartitionKeyRangeCache(clientB, collB, endpoint); + + try { + // Step 1: A populates the shared cache with the pre-split routing map. + CollectionRoutingMap[] beforeMapHolder = new CollectionRoutingMap[1]; + StepVerifier.create(cacheA.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .consumeNextWith(v -> beforeMapHolder[0] = v.v) + .verifyComplete(); + assertThat(beforeMapHolder[0]).isNotNull(); + + // Step 2: A force-refreshes (passing previousValue == current cached map). + CollectionRoutingMap[] afterMapHolder = new CollectionRoutingMap[1]; + StepVerifier.create(cacheA.tryLookupAsync(null, collectionRid, beforeMapHolder[0], new HashMap<>())) + .consumeNextWith(v -> afterMapHolder[0] = v.v) + .verifyComplete(); + assertThat(afterMapHolder[0]).isNotSameAs(beforeMapHolder[0]); + + // Step 3: B's lookup must see A's refreshed value (no fresh fetch from B). + StepVerifier.create(cacheB.tryLookupAsync(null, collectionRid, null, new HashMap<>())) + .consumeNextWith(v -> assertThat(v.v).isSameAs(afterMapHolder[0])) + .verifyComplete(); + + assertThat(clientACalls.get()) + .as("A populated then refreshed -> 2 calls") + .isEqualTo(2); + assertThat(clientBCalls.get()) + .as("B must observe A's refresh without issuing its own fetch") + .isZero(); + } finally { + cacheA.close(); + cacheB.close(); + } + } } \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistryTest.java similarity index 62% rename from sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java rename to sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistryTest.java index 05771589a3ff..968ac878e5b5 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistryTest.java @@ -4,6 +4,7 @@ package com.azure.cosmos.implementation.caches; import com.azure.cosmos.implementation.Configs; +import com.azure.cosmos.implementation.caches.SharedPartitionKeyRangeCacheRegistry.AcquireResult; import com.azure.cosmos.implementation.routing.CollectionRoutingMap; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -19,9 +20,9 @@ import static org.assertj.core.api.Assertions.assertThat; -/** Unit tests for {@link SharedRoutingMapCacheRegistry}. Each test uses a unique endpoint +/** Unit tests for {@link SharedPartitionKeyRangeCacheRegistry}. Each test uses a unique endpoint * and releases every reference, since the registry is a process-wide singleton. */ -public class SharedRoutingMapCacheRegistryTest { +public class SharedPartitionKeyRangeCacheRegistryTest { private static final String ENABLE_FLAG = "COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED"; @@ -44,36 +45,36 @@ public void after() { @Test(groups = "unit") public void acquireReturnsSameInstanceForSameEndpoint() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-1.documents.azure.com:443/"); - AsyncCacheNonBlocking a = registry.acquire(endpoint); - AsyncCacheNonBlocking b = registry.acquire(endpoint); + AcquireResult ra = registry.acquire(endpoint, new Object()); + AcquireResult rb = registry.acquire(endpoint, new Object()); try { - assertThat(a).isSameAs(b); + assertThat(ra.cache).isSameAs(rb.cache); assertThat(registry.referenceCount(endpoint)).isEqualTo(2); } finally { - registry.release(endpoint, a); - registry.release(endpoint, b); + registry.release(endpoint, ra.cache, ra.releaseHandle); + registry.release(endpoint, rb.cache, rb.releaseHandle); } assertThat(registry.referenceCount(endpoint)).isZero(); } @Test(groups = "unit") public void acquireReturnsDifferentInstanceForDifferentEndpoints() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI e1 = URI.create("https://test-acct-share-2a.documents.azure.com:443/"); URI e2 = URI.create("https://test-acct-share-2b.documents.azure.com:443/"); - AsyncCacheNonBlocking a = registry.acquire(e1); - AsyncCacheNonBlocking b = registry.acquire(e2); + AcquireResult ra = registry.acquire(e1, new Object()); + AcquireResult rb = registry.acquire(e2, new Object()); try { - assertThat(a).isNotSameAs(b); + assertThat(ra.cache).isNotSameAs(rb.cache); } finally { - registry.release(e1, a); - registry.release(e2, b); + registry.release(e1, ra.cache, ra.releaseHandle); + registry.release(e2, rb.cache, rb.releaseHandle); } } @@ -81,124 +82,127 @@ public void acquireReturnsDifferentInstanceForDifferentEndpoints() { public void acquireTreatsHostCaseInsensitivelyMatchingUriEquals() { // URI.equals is case-insensitive on host (RFC 3986); confirm clients with // mixed-case host names collapse into one shared entry. - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI lower = URI.create("https://test-acct-share-case.documents.azure.com:443/"); URI mixed = URI.create("https://Test-Acct-Share-Case.documents.azure.com:443/"); assertThat(lower).isEqualTo(mixed); - AsyncCacheNonBlocking a = registry.acquire(lower); - AsyncCacheNonBlocking b = registry.acquire(mixed); + AcquireResult ra = registry.acquire(lower, new Object()); + AcquireResult rb = registry.acquire(mixed, new Object()); try { - assertThat(a) + assertThat(ra.cache) .as("lower-case and mixed-case host must share the same registry entry") - .isSameAs(b); + .isSameAs(rb.cache); assertThat(registry.referenceCount(lower)).isEqualTo(2); assertThat(registry.referenceCount(mixed)).isEqualTo(2); } finally { - registry.release(lower, a); - registry.release(mixed, b); + registry.release(lower, ra.cache, ra.releaseHandle); + registry.release(mixed, rb.cache, rb.releaseHandle); } assertThat(registry.referenceCount(lower)).isZero(); } @Test(groups = "unit") public void releaseEvictsAtZeroRefcount() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-3.documents.azure.com:443/"); - AsyncCacheNonBlocking a = registry.acquire(endpoint); - AsyncCacheNonBlocking b = registry.acquire(endpoint); + AcquireResult ra = registry.acquire(endpoint, new Object()); + AcquireResult rb = registry.acquire(endpoint, new Object()); assertThat(registry.referenceCount(endpoint)).isEqualTo(2); - registry.release(endpoint, a); + registry.release(endpoint, ra.cache, ra.releaseHandle); assertThat(registry.referenceCount(endpoint)).isEqualTo(1); - registry.release(endpoint, b); + registry.release(endpoint, rb.cache, rb.releaseHandle); assertThat(registry.referenceCount(endpoint)).isZero(); // After eviction, fresh acquire produces a new cache instance. - AsyncCacheNonBlocking c = registry.acquire(endpoint); + AcquireResult rc = registry.acquire(endpoint, new Object()); try { - assertThat(c).isNotSameAs(a); + assertThat(rc.cache).isNotSameAs(ra.cache); assertThat(registry.referenceCount(endpoint)).isEqualTo(1); } finally { - registry.release(endpoint, c); + registry.release(endpoint, rc.cache, rc.releaseHandle); } } @Test(groups = "unit") public void releaseIsIdempotentWhenSuppliedSameCacheRepeatedly() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-4.documents.azure.com:443/"); - AsyncCacheNonBlocking a = registry.acquire(endpoint); - registry.release(endpoint, a); + AcquireResult ra = registry.acquire(endpoint, new Object()); + registry.release(endpoint, ra.cache, ra.releaseHandle); assertThat(registry.referenceCount(endpoint)).isZero(); // Releasing a stale cache reference must not crash or drive refcount negative. - registry.release(endpoint, a); + registry.release(endpoint, ra.cache, ra.releaseHandle); assertThat(registry.referenceCount(endpoint)).isZero(); } @Test(groups = "unit") public void releaseIsNoOpWhenCacheIsNotTheRegisteredInstance() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-5.documents.azure.com:443/"); - AsyncCacheNonBlocking stale = registry.acquire(endpoint); - registry.release(endpoint, stale); + AcquireResult stale = registry.acquire(endpoint, new Object()); + registry.release(endpoint, stale.cache, stale.releaseHandle); - AsyncCacheNonBlocking current = registry.acquire(endpoint); + AcquireResult current = registry.acquire(endpoint, new Object()); try { - registry.release(endpoint, stale); // stale != current → no-op + registry.release(endpoint, stale.cache, null); // stale != current → no-op assertThat(registry.referenceCount(endpoint)).isEqualTo(1); } finally { - registry.release(endpoint, current); + registry.release(endpoint, current.cache, current.releaseHandle); } } @Test(groups = "unit") public void nullEndpointReturnsIsolatedCacheAndDoesNotEnterRegistry() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); int before = registry.registeredEndpointCount(); - AsyncCacheNonBlocking a = registry.acquire(null); - AsyncCacheNonBlocking b = registry.acquire(null); + AcquireResult ra = registry.acquire(null, new Object()); + AcquireResult rb = registry.acquire(null, new Object()); - assertThat(a).isNotSameAs(b); + assertThat(ra.cache).isNotSameAs(rb.cache); + assertThat(ra.releaseHandle).isNull(); + assertThat(rb.releaseHandle).isNull(); assertThat(registry.registeredEndpointCount()).isEqualTo(before); - registry.release(null, a); - registry.release(null, b); + registry.release(null, ra.cache, ra.releaseHandle); + registry.release(null, rb.cache, rb.releaseHandle); } @Test(groups = "unit") public void disabledFlagReturnsIsolatedCachesAndPreservesRegistryEmpty() { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-6.documents.azure.com:443/"); int before = registry.registeredEndpointCount(); System.setProperty(ENABLE_FLAG, "false"); assertThat(Configs.isSharedPartitionKeyRangeCacheEnabled()).isFalse(); - AsyncCacheNonBlocking a = registry.acquire(endpoint); - AsyncCacheNonBlocking b = registry.acquire(endpoint); + AcquireResult ra = registry.acquire(endpoint, new Object()); + AcquireResult rb = registry.acquire(endpoint, new Object()); try { - assertThat(a).isNotSameAs(b); + assertThat(ra.cache).isNotSameAs(rb.cache); + assertThat(ra.releaseHandle).isNull(); assertThat(registry.registeredEndpointCount()).isEqualTo(before); assertThat(registry.referenceCount(endpoint)).isZero(); } finally { - registry.release(endpoint, a); - registry.release(endpoint, b); + registry.release(endpoint, ra.cache, ra.releaseHandle); + registry.release(endpoint, rb.cache, rb.releaseHandle); } } @Test(groups = "unit") public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Exception { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-share-7.documents.azure.com:443/"); int threads = 32; @@ -212,17 +216,16 @@ public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Excep pool.submit(() -> { try { start.await(); - List> held = new ArrayList<>(); + List held = new ArrayList<>(); for (int i = 0; i < opsPerThread; i++) { - held.add(registry.acquire(endpoint)); + held.add(registry.acquire(endpoint, new Object())); if (i % 3 == 0 && !held.isEmpty()) { - AsyncCacheNonBlocking c = - held.remove(held.size() - 1); - registry.release(endpoint, c); + AcquireResult r = held.remove(held.size() - 1); + registry.release(endpoint, r.cache, r.releaseHandle); } } - for (AsyncCacheNonBlocking c : held) { - registry.release(endpoint, c); + for (AcquireResult r : held) { + registry.release(endpoint, r.cache, r.releaseHandle); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -244,7 +247,7 @@ public void concurrentAcquireAndReleaseProducesConsistentRefcount() throws Excep public void referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected() throws Exception { // Owner is allocated in a separate stack frame so this frame can't keep it alive; // ReferenceManager runs the cleanup once GC observes the owner as phantom-reachable. - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-1.documents.azure.com:443/"); acquireAndLeakOwner(registry, endpoint); @@ -268,7 +271,7 @@ public void referenceManagerReleasesSharedCacheWhenOwnerIsGarbageCollected() thr .isTrue(); } - private static void acquireAndLeakOwner(SharedRoutingMapCacheRegistry registry, URI endpoint) { + private static void acquireAndLeakOwner(SharedPartitionKeyRangeCacheRegistry registry, URI endpoint) { Object owner = new Object(); registry.acquire(endpoint, owner); // owner falls out of scope on return. @@ -276,7 +279,7 @@ private static void acquireAndLeakOwner(SharedRoutingMapCacheRegistry registry, @Test(groups = "unit") public void promptCloseFulfillsHandleSoReferenceManagerCleanupIsANoop() throws Exception { - SharedRoutingMapCacheRegistry registry = SharedRoutingMapCacheRegistry.getInstance(); + SharedPartitionKeyRangeCacheRegistry registry = SharedPartitionKeyRangeCacheRegistry.getInstance(); URI endpoint = URI.create("https://test-acct-leak-2.documents.azure.com:443/"); acquireAndPromptlyClose(registry, endpoint); @@ -291,9 +294,9 @@ public void promptCloseFulfillsHandleSoReferenceManagerCleanupIsANoop() throws E assertThat(registry.referenceCount(endpoint)).isZero(); } - private static void acquireAndPromptlyClose(SharedRoutingMapCacheRegistry registry, URI endpoint) { + private static void acquireAndPromptlyClose(SharedPartitionKeyRangeCacheRegistry registry, URI endpoint) { Object owner = new Object(); - SharedRoutingMapCacheRegistry.AcquireResult result = registry.acquire(endpoint, owner); + AcquireResult result = registry.acquire(endpoint, owner); registry.release(endpoint, result.cache, result.releaseHandle); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java index 0b497b57450c..038c4a27e068 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/RxPartitionKeyRangeCache.java @@ -47,7 +47,7 @@ * While this class is public, but it is not part of our published public APIs. * This is meant to be internally used only by our sdk. * - *

The routing-map storage is obtained from {@link SharedRoutingMapCacheRegistry} + *

The routing-map storage is obtained from {@link SharedPartitionKeyRangeCacheRegistry} * keyed by the service endpoint URI; multiple clients targeting the same endpoint * share it. {@link #close()} releases this instance's reference. The fetching * logic (network call, collection resolution, diagnostics) remains per-client.

@@ -61,7 +61,7 @@ public class RxPartitionKeyRangeCache implements IPartitionKeyRangeCache, Closea private final DiagnosticsClientContext clientContext; private final URI sharedCacheEndpointKey; private final AtomicBoolean closed = new AtomicBoolean(false); - private final SharedRoutingMapCacheRegistry.ReleaseHandle releaseHandle; + private final SharedPartitionKeyRangeCacheRegistry.ReleaseHandle releaseHandle; public RxPartitionKeyRangeCache(RxDocumentClientImpl client, RxCollectionCache collectionCache) { this(client, collectionCache, client == null ? null : client.getServiceEndpoint()); @@ -73,8 +73,8 @@ public RxPartitionKeyRangeCache( URI serviceEndpoint) { this.sharedCacheEndpointKey = serviceEndpoint; - SharedRoutingMapCacheRegistry.AcquireResult acquired = - SharedRoutingMapCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey, this); + SharedPartitionKeyRangeCacheRegistry.AcquireResult acquired = + SharedPartitionKeyRangeCacheRegistry.getInstance().acquire(this.sharedCacheEndpointKey, this); this.routingMapCache = acquired.cache; this.releaseHandle = acquired.releaseHandle; this.client = client; @@ -86,7 +86,7 @@ public RxPartitionKeyRangeCache( @Override public void close() { if (closed.compareAndSet(false, true)) { - SharedRoutingMapCacheRegistry.getInstance().release( + SharedPartitionKeyRangeCacheRegistry.getInstance().release( this.sharedCacheEndpointKey, this.routingMapCache, this.releaseHandle); } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistry.java similarity index 92% rename from sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java rename to sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistry.java index ead4560b45ab..9ac70d4831e2 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedRoutingMapCacheRegistry.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/caches/SharedPartitionKeyRangeCacheRegistry.java @@ -26,17 +26,17 @@ *

Sharing can be disabled via system property * {@code COSMOS.SHARED_PARTITION_KEY_RANGE_CACHE_ENABLED=false}.

*/ -public final class SharedRoutingMapCacheRegistry { - private static final Logger logger = LoggerFactory.getLogger(SharedRoutingMapCacheRegistry.class); +public final class SharedPartitionKeyRangeCacheRegistry { + private static final Logger logger = LoggerFactory.getLogger(SharedPartitionKeyRangeCacheRegistry.class); - private static final SharedRoutingMapCacheRegistry INSTANCE = new SharedRoutingMapCacheRegistry(); + private static final SharedPartitionKeyRangeCacheRegistry INSTANCE = new SharedPartitionKeyRangeCacheRegistry(); private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); - private SharedRoutingMapCacheRegistry() { + private SharedPartitionKeyRangeCacheRegistry() { } - public static SharedRoutingMapCacheRegistry getInstance() { + public static SharedPartitionKeyRangeCacheRegistry getInstance() { return INSTANCE; } @@ -86,11 +86,6 @@ public AcquireResult acquire(URI endpoint, Object owner) { return new AcquireResult(entry.cache, handle); } - /** Test-only overload without cleanup registration. */ - AsyncCacheNonBlocking acquire(URI endpoint) { - return acquire(endpoint, null).cache; - } - /** * Prompt release path. Fulfils {@code handle} (so the deferred cleanup * becomes a no-op) and decrements the refcount. If the handle was already