Copilot commented on code in PR #11846:
URL: https://github.com/apache/gravitino/pull/11846#discussion_r3568531392


##########
catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogCapability.java:
##########
@@ -62,18 +62,15 @@ public CapabilityResult columnDefaultValue() {
 
   @Override
   public CapabilityResult caseSensitiveOnName(Scope scope) {
-    switch (scope) {
-      case SCHEMA:
-      case TABLE:
-        // Glue folds database/table names to lowercase on storage.
-        // See 
https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-databases.html
-        // See 
https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html
-        return CapabilityResult.unsupported(
-            "AWS Glue Data Catalog is case-insensitive for "
-                + scope.name().toLowerCase(Locale.ROOT)
-                + " names.");
-      default:
-        return CapabilityResult.SUPPORTED;
+    if (scope == Scope.SCHEMA || scope == Scope.TABLE) {
+      // Glue folds database/table names to lowercase on storage.
+      // See 
https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-databases.html
+      // See 
https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html

Review Comment:
   `caseSensitiveOnName` now treats `null` scope as supported (returns 
SUPPORTED) whereas the previous `switch` would throw at runtime. This can mask 
programming errors and changes behavior; it should fail fast with a clear 
exception (consistent with Hive’s new null guard).



##########
core/src/test/java/org/apache/gravitino/catalog/TestCatalogWrapperConcurrency.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.utils.IsolatedClassLoader;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/**
+ * Verifies that {@link CatalogManager.CatalogWrapper} correctly serializes 
{@code close()} against
+ * in-flight {@code doWithCapabilityOps()} calls via {@code synchronized}, 
preventing the {@link
+ * IsolatedClassLoader} from being torn down while a capability operation is 
still executing.
+ */
+public class TestCatalogWrapperConcurrency {
+
+  /**
+   * close() must block until a concurrent doWithCapabilityOps() call finishes 
(both methods are
+   * synchronized on the same monitor), and subsequent doWithCapabilityOps() 
calls after close()
+   * must throw IllegalStateException.
+   */
+  @Test
+  public void testCloseBlocksUntilInFlightCapabilityOpsComplete() throws 
Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.when(mockCatalog.capability()).thenReturn(Capability.DEFAULT);
+
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+    Mockito.when(mockCl.withClassLoader(Mockito.any()))
+        .thenAnswer(
+            inv ->
+                ((org.apache.gravitino.utils.ThrowableFunction<ClassLoader, 
?>) inv.getArgument(0))
+                    .apply(Thread.currentThread().getContextClassLoader()));
+
+    CatalogManager.CatalogWrapper wrapper = new 
CatalogManager.CatalogWrapper(mockCatalog, mockCl);
+
+    CountDownLatch opStarted = new CountDownLatch(1);
+    CountDownLatch permitClose = new CountDownLatch(1);
+    AtomicInteger opCompleted = new AtomicInteger(0);
+
+    ExecutorService exec = Executors.newFixedThreadPool(2);
+    try {
+      // One in-flight capability op that holds the synchronized lock while 
waiting.
+      Callable<Void> op =
+          () -> {
+            wrapper.doWithCapabilityOps(
+                cap -> {
+                  opStarted.countDown();
+                  permitClose.await(5, TimeUnit.SECONDS);
+                  opCompleted.incrementAndGet();
+                  return null;
+                });
+            return null;
+          };
+
+      Future<Void> f1 = exec.submit(op);
+
+      // Wait for the op to acquire the synchronized lock.
+      Assertions.assertTrue(opStarted.await(5, TimeUnit.SECONDS));
+
+      // Start close() on a separate thread; it must block waiting for the op 
to release the lock.
+      Future<Void> closeFuture =
+          exec.submit(
+              () -> {
+                wrapper.close();
+                return null;
+              });
+
+      // Give close() a moment to attempt acquiring the lock, then verify it 
is still blocked.
+      Thread.sleep(100);
+      Assertions.assertFalse(
+          closeFuture.isDone(), "close() must not complete while op holds the 
lock");

Review Comment:
   This test uses `Thread.sleep(100)` to infer that `close()` is blocked, which 
can be flaky on slow/contended CI hosts (sleep may be too short or 
unnecessarily long). Prefer a bounded wait loop that doesn’t assume scheduling 
latency.



##########
core/src/test/java/org/apache/gravitino/catalog/TestCatalogWrapperConcurrency.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.utils.IsolatedClassLoader;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/**
+ * Verifies that {@link CatalogManager.CatalogWrapper} correctly serializes 
{@code close()} against
+ * in-flight {@code doWithCapabilityOps()} calls via {@code synchronized}, 
preventing the {@link
+ * IsolatedClassLoader} from being torn down while a capability operation is 
still executing.
+ */
+public class TestCatalogWrapperConcurrency {
+
+  /**
+   * close() must block until a concurrent doWithCapabilityOps() call finishes 
(both methods are
+   * synchronized on the same monitor), and subsequent doWithCapabilityOps() 
calls after close()
+   * must throw IllegalStateException.
+   */
+  @Test
+  public void testCloseBlocksUntilInFlightCapabilityOpsComplete() throws 
Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.when(mockCatalog.capability()).thenReturn(Capability.DEFAULT);
+
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+    Mockito.when(mockCl.withClassLoader(Mockito.any()))
+        .thenAnswer(
+            inv ->
+                ((org.apache.gravitino.utils.ThrowableFunction<ClassLoader, 
?>) inv.getArgument(0))
+                    .apply(Thread.currentThread().getContextClassLoader()));
+
+    CatalogManager.CatalogWrapper wrapper = new 
CatalogManager.CatalogWrapper(mockCatalog, mockCl);
+
+    CountDownLatch opStarted = new CountDownLatch(1);
+    CountDownLatch permitClose = new CountDownLatch(1);
+    AtomicInteger opCompleted = new AtomicInteger(0);
+
+    ExecutorService exec = Executors.newFixedThreadPool(2);
+    try {
+      // One in-flight capability op that holds the synchronized lock while 
waiting.
+      Callable<Void> op =
+          () -> {
+            wrapper.doWithCapabilityOps(
+                cap -> {
+                  opStarted.countDown();
+                  permitClose.await(5, TimeUnit.SECONDS);
+                  opCompleted.incrementAndGet();
+                  return null;
+                });
+            return null;
+          };
+
+      Future<Void> f1 = exec.submit(op);
+
+      // Wait for the op to acquire the synchronized lock.
+      Assertions.assertTrue(opStarted.await(5, TimeUnit.SECONDS));
+
+      // Start close() on a separate thread; it must block waiting for the op 
to release the lock.
+      Future<Void> closeFuture =
+          exec.submit(
+              () -> {
+                wrapper.close();
+                return null;
+              });
+
+      // Give close() a moment to attempt acquiring the lock, then verify it 
is still blocked.
+      Thread.sleep(100);
+      Assertions.assertFalse(
+          closeFuture.isDone(), "close() must not complete while op holds the 
lock");
+
+      // Release the in-flight op.
+      permitClose.countDown();
+      f1.get(5, TimeUnit.SECONDS);
+
+      // Now close() should complete.
+      closeFuture.get(5, TimeUnit.SECONDS);
+      Assertions.assertTrue(closeFuture.isDone());
+
+      // The op finished before close() completed.
+      Assertions.assertEquals(1, opCompleted.get());
+
+      // Subsequent capability ops must be rejected.
+      Assertions.assertThrows(
+          IllegalStateException.class, () -> wrapper.doWithCapabilityOps(cap 
-> null));
+    } finally {
+      exec.shutdownNow();
+    }
+  }
+
+  /**
+   * close() is idempotent: calling it a second time must not throw and must 
not call
+   * classLoader.close() again.
+   */
+  @Test
+  public void testCloseIsIdempotent() throws Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.when(mockCatalog.capability()).thenReturn(Capability.DEFAULT);
+
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+    Mockito.when(mockCl.withClassLoader(Mockito.any()))
+        .thenAnswer(
+            inv ->
+                ((org.apache.gravitino.utils.ThrowableFunction<ClassLoader, 
?>) inv.getArgument(0))
+                    .apply(Thread.currentThread().getContextClassLoader()));
+
+    CatalogManager.CatalogWrapper wrapper = new 
CatalogManager.CatalogWrapper(mockCatalog, mockCl);
+
+    wrapper.close();
+    wrapper.close(); // must not throw
+
+    // classLoader.close() should be called exactly once.
+    Mockito.verify(mockCl, Mockito.times(1)).close();
+  }
+
+  /**
+   * doWithCapabilityOps() after close() throws IllegalStateException, not a 
cryptic
+   * NullPointerException or NoClassDefFoundError.
+   */
+  @Test
+  public void testCapabilityOpsAfterCloseThrowIllegalStateException() throws 
Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+
+    CatalogManager.CatalogWrapper wrapper = new 
CatalogManager.CatalogWrapper(mockCatalog, mockCl);
+    Mockito.when(mockCl.withClassLoader(Mockito.any())).thenReturn(null);
+    wrapper.close();
+
+    Assertions.assertThrows(
+        IllegalStateException.class, () -> wrapper.doWithCapabilityOps(cap -> 
null));
+  }
+
+  /**
+   * Multiple concurrent close() calls must not result in multiple 
classLoader.close() invocations
+   * (synchronized idempotency under concurrency).
+   */
+  @Test
+  public void testConcurrentCloseCallsAreIdempotent() throws Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.when(mockCatalog.capability()).thenReturn(Capability.DEFAULT);
+
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+    Mockito.when(mockCl.withClassLoader(Mockito.any()))
+        .thenAnswer(
+            inv ->
+                ((org.apache.gravitino.utils.ThrowableFunction<ClassLoader, 
?>) inv.getArgument(0))
+                    .apply(Thread.currentThread().getContextClassLoader()));
+
+    CatalogManager.CatalogWrapper wrapper = new 
CatalogManager.CatalogWrapper(mockCatalog, mockCl);
+
+    int threads = 8;
+    ExecutorService exec = Executors.newFixedThreadPool(threads);
+    CountDownLatch ready = new CountDownLatch(threads);
+    CountDownLatch go = new CountDownLatch(1);
+
+    List<Future<Void>> futures = new ArrayList<>();
+    for (int i = 0; i < threads; i++) {
+      futures.add(
+          exec.submit(
+              () -> {
+                ready.countDown();
+                go.await();
+                wrapper.close();
+                return null;
+              }));
+    }
+
+    ready.await(5, TimeUnit.SECONDS);
+    go.countDown();
+
+    for (Future<Void> f : futures) {
+      f.get(5, TimeUnit.SECONDS);
+    }
+
+    exec.shutdownNow();

Review Comment:
   `ready.await(5, TimeUnit.SECONDS)` ignores the return value, so the test can 
proceed even if not all threads are ready, making failures harder to diagnose. 
Also, shutting down the executor should be in a `finally` so it’s cleaned up if 
any `Future#get` fails.



##########
core/src/test/java/org/apache/gravitino/catalog/TestCatalogWrapperConcurrency.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.utils.IsolatedClassLoader;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/**
+ * Verifies that {@link CatalogManager.CatalogWrapper} correctly serializes 
{@code close()} against
+ * in-flight {@code doWithCapabilityOps()} calls via {@code synchronized}, 
preventing the {@link
+ * IsolatedClassLoader} from being torn down while a capability operation is 
still executing.
+ */
+public class TestCatalogWrapperConcurrency {
+
+  /**
+   * close() must block until a concurrent doWithCapabilityOps() call finishes 
(both methods are
+   * synchronized on the same monitor), and subsequent doWithCapabilityOps() 
calls after close()
+   * must throw IllegalStateException.
+   */
+  @Test
+  public void testCloseBlocksUntilInFlightCapabilityOpsComplete() throws 
Exception {
+    BaseCatalog mockCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.when(mockCatalog.capability()).thenReturn(Capability.DEFAULT);
+
+    IsolatedClassLoader mockCl = Mockito.mock(IsolatedClassLoader.class);
+    Mockito.when(mockCl.withClassLoader(Mockito.any()))
+        .thenAnswer(
+            inv ->
+                ((org.apache.gravitino.utils.ThrowableFunction<ClassLoader, 
?>) inv.getArgument(0))
+                    .apply(Thread.currentThread().getContextClassLoader()));

Review Comment:
   This test uses a fully-qualified 
`org.apache.gravitino.utils.ThrowableFunction` in code. The repo’s Java style 
guidance prefers normal imports over FQNs unless there’s a genuine name 
collision; consider importing `ThrowableFunction` and using the simple name 
here for consistency.



-- 
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]

Reply via email to