laxmanmamididd commented on code in PR #7363:
URL: https://github.com/apache/datafusion-comet/pull/7363#discussion_r4503365849


##########
spark/src/test/java/org/apache/comet/cloud/s3/CometS3CredentialDispatcherTest.java:
##########
@@ -267,4 +282,145 @@ public void closeAllSwallowsProviderExceptions() {
     // Both close() invocations ran even though one (the first to be visited) 
threw.
     assertEquals(2, TestCometS3CredentialProvider.closeCount.get());
   }
+
+  @Test
+  public void releasingTheLastReferenceClosesTheProvider() throws Exception {
+    long h1 = init();
+    long h2 = init();
+    assertEquals(h1, h2);
+
+    CometS3CredentialDispatcher.release(h1);
+    assertEquals(0, TestCometS3CredentialProvider.closeCount.get());
+    CometS3CredentialDispatcher.getCredentialsForPath(h2, "b", "k", READ);
+
+    CometS3CredentialDispatcher.release(h2);
+    assertEquals(1, TestCometS3CredentialProvider.closeCount.get());
+    assertEquals(0, CometS3CredentialDispatcher.registeredCount());
+    Exception thrown =
+        assertThrows(
+            IllegalStateException.class,
+            () -> CometS3CredentialDispatcher.getCredentialsForPath(h2, "b", 
"k", READ));
+    assertTrue(thrown.getMessage().contains("not initialized"));
+  }
+
+  @Test
+  public void aRetiredKeyGetsANewProvider() {
+    long retired = init();
+    CometS3CredentialDispatcher.release(retired);
+
+    long fresh = init();
+
+    assertNotEquals(retired, fresh);
+    assertEquals(2, TestCometS3CredentialProvider.initCount.get());
+    assertEquals(1, CometS3CredentialDispatcher.registeredCount());
+  }
+
+  /** An extra release, or one for a handle closed at shutdown, changes 
nothing. */
+  @Test
+  public void unknownOrRepeatedReleasesAreIgnored() {
+    CometS3CredentialDispatcher.release(Long.MAX_VALUE);
+    long handle = init();
+    CometS3CredentialDispatcher.release(handle);
+    CometS3CredentialDispatcher.release(handle);
+
+    assertEquals(1, TestCometS3CredentialProvider.closeCount.get());
+    long other = init();
+    CometS3CredentialDispatcher.release(handle);
+    assertEquals(1, CometS3CredentialDispatcher.registeredCount());
+    CometS3CredentialDispatcher.release(other);
+    assertEquals(2, TestCometS3CredentialProvider.closeCount.get());
+  }
+
+  @Test
+  public void concurrentAcquireAndReleaseNeverUseAClosedProvider() throws 
Exception {
+    int threads = 8;
+    int rounds = 2000;
+    ExecutorService pool = Executors.newFixedThreadPool(threads);
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      for (int t = 0; t < threads; t++) {
+        futures.add(
+            pool.submit(
+                () -> {
+                  for (int i = 0; i < rounds; i++) {
+                    long handle = init();
+                    CometS3CredentialDispatcher.getCredentialsForPath(handle, 
"b", "k", READ);
+                    CometS3CredentialDispatcher.release(handle);
+                  }
+                  return null;
+                }));
+      }
+      for (Future<?> f : futures) {
+        f.get();
+      }
+    } finally {
+      pool.shutdownNow();
+    }
+    assertEquals(0, CometS3CredentialDispatcher.registeredCount());
+    assertEquals(
+        TestCometS3CredentialProvider.initCount.get(),
+        TestCometS3CredentialProvider.closeCount.get());
+  }
+
+  /** The releasing thread may be a native reader's, so the close runs on the 
dispatcher's own. */
+  @Test
+  public void releaseClosesOnTheDispatcherThread() throws Exception {
+    CometS3CredentialDispatcher.setCloserForTesting(null);
+    TestCometS3CredentialProvider.closeEntered = new CountDownLatch(1);
+    long handle = init();
+
+    CometS3CredentialDispatcher.release(handle);
+
+    assertTrue(TestCometS3CredentialProvider.closeEntered.await(10, 
TimeUnit.SECONDS));
+    assertEquals("comet-s3-credential-close", 
TestCometS3CredentialProvider.lastCloseThread);
+    assertEquals(0, CometS3CredentialDispatcher.registeredCount());
+  }
+
+  /** A close that blocks holds up neither the releasing thread nor a new 
reader for the key. */
+  @Test
+  public void aBlockingCloseDoesNotHoldUpRelease() throws Exception {
+    CometS3CredentialDispatcher.setCloserForTesting(null);
+    CountDownLatch block = new CountDownLatch(1);
+    TestCometS3CredentialProvider.blockClose = block;
+    TestCometS3CredentialProvider.closeEntered = new CountDownLatch(1);
+    try {
+      long retired = init();
+      CometS3CredentialDispatcher.release(retired);
+      assertTrue(TestCometS3CredentialProvider.closeEntered.await(10, 
TimeUnit.SECONDS));
+
+      long fresh = init();
+      CometS3CredentialDispatcher.getCredentialsForPath(fresh, "b", "k", READ);
+      assertNotEquals(retired, fresh);
+      assertEquals(0, TestCometS3CredentialProvider.closeCount.get());
+    } finally {
+      block.countDown();

Review Comment:
   Thanks for the repro. Fixed in fc3a4f2a1 on #7464, now under this branch 
too. The dispatcher has a test-only `awaitQueuedClosesForTesting()`, which 
queues a no-op on its single close thread and waits for it, so every close 
queued before the call has finished. `@Before` calls it before `closeAll()` and 
the counter reset, and `@After` calls it after restoring the default closer. So 
no close can land in the next test's counts, whatever ordering the tests run 
in. `aBlockingCloseDoesNotHoldUpRelease` still checks that release doesn't wait 
for a blocked close, since the drain only runs in cleanup.
   
   Running the whole class 1,500 times in a row through JUnit gave no failed 
runs.
   



##########
docs/source/contributor-guide/s3-credential-provider-design.md:
##########
@@ -158,20 +158,29 @@ The Parquet path forwards the full `fs.s3a.*` config 
subset as `catalog_properti
 
 ## Built-in adapters
 
-Comet ships two reference SPI implementations under 
`org.apache.comet.cloud.s3`, so standard provider classes that the native Rust 
list does not match work with a config change instead of bespoke code:
+Comet ships three SPI implementations under `org.apache.comet.cloud.s3`, so 
standard provider classes that the native Rust list does not match work with a 
config change instead of bespoke code:
 
 - `HadoopS3ACredentialProviderAdapter` delegates to Hadoop S3A's own provider 
construction (`S3AUtils.createAWSCredentialProviderSet` on Hadoop 3.3.x, 
`CredentialProviderListFactory.createAWSCredentialProviderList` on 3.4+).
 - `AwsSdkCredentialProviderAdapter` wraps a raw AWS SDK provider named in 
`fs.s3a.comet.credential.adapter.class`.
+- `IcebergVendedS3CredentialProvider` resolves REST-vended Iceberg credentials 
through Iceberg's `AwsClientProperties`, as `S3FileIO` does, and refreshes them 
through `client.refresh-credentials-endpoint`. It exists only in the spark-4.x 
source set. `CometIcebergNativeScan.withVendedCredentialProvider` selects it 
for the native Iceberg scan and writer when the catalog returns a refresh 
endpoint and no provider is configured.

Review Comment:
   Updated in 9122cca8a. The paragraph already said scan-only after 286d711c8. 
The "Each adapter" paragraph now says that only the Hadoop and AWS SDK adapters 
have spark-3.x and spark-4.x bodies, and that 
`IcebergVendedS3CredentialProvider` has only the spark-4.x body, so Spark 3.x 
builds neither ship nor select it.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to