This is an automated email from the ASF dual-hosted git repository.

yuqi4733 pushed a commit to branch fix-10545-batch-load-securable-objects
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to 
refs/heads/fix-10545-batch-load-securable-objects by this push:
     new 81098981cb [#10545] Address Copilot review comments on batch 
securable-object loading
81098981cb is described below

commit 81098981cb19f6c61f134fcf56e8e2df39729a8a
Author: yuqi <[email protected]>
AuthorDate: Fri Apr 10 15:00:39 2026 +0800

    [#10545] Address Copilot review comments on batch securable-object loading
    
    - Add try/catch around batchListSecurableObjectsForRoles with actionable 
context (metalake, userId, roleIds)
    - Remove unused executor field and thread pool from JcasbinAuthorizer (no 
longer used after N+1 fix)
    - Fix fully-qualified ImmutableMap.Builder reference in 
TestJcasbinAuthorizer → use import
    - Add testBatchListSecurableObjectsForRoles to TestRoleMetaService covering 
multiple roles and missing role ID
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 .../relational/service/TestRoleMetaService.java    | 61 ++++++++++++++++++++++
 .../authorization/jcasbin/JcasbinAuthorizer.java   | 39 +++++---------
 .../jcasbin/TestJcasbinAuthorizer.java             | 23 +-------
 3 files changed, 77 insertions(+), 46 deletions(-)

diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
index fbd17a31d7..d9ee5de0ab 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
@@ -286,6 +286,67 @@ class TestRoleMetaService extends TestJDBCBackend {
     }
   }
 
+  @TestTemplate
+  void testBatchListSecurableObjectsForRoles() throws IOException {
+    createAndInsertMakeLake(METALAKE_NAME);
+    String catalogName1 = "catalog1";
+    String catalogName2 = "catalog2";
+    createAndInsertCatalog(METALAKE_NAME, catalogName1);
+    createAndInsertCatalog(METALAKE_NAME, catalogName2);
+
+    RoleMetaService roleMetaService = RoleMetaService.getInstance();
+
+    // Create role1 with a securable object on catalog1
+    RoleEntity role1 =
+        createRoleEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofRoleNamespace(METALAKE_NAME),
+            "role1",
+            AUDIT_INFO,
+            SecurableObjects.ofCatalog(
+                catalogName1, 
Lists.newArrayList(Privileges.UseCatalog.allow())),
+            ImmutableMap.of());
+    roleMetaService.insertRole(role1, false);
+
+    // Create role2 with a securable object on catalog2
+    RoleEntity role2 =
+        createRoleEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofRoleNamespace(METALAKE_NAME),
+            "role2",
+            AUDIT_INFO,
+            SecurableObjects.ofCatalog(
+                catalogName2, 
Lists.newArrayList(Privileges.UseCatalog.allow())),
+            ImmutableMap.of());
+    roleMetaService.insertRole(role2, false);
+
+    // Batch-load securable objects for both roles
+    Map<Long, List<SecurableObject>> result =
+        RoleMetaService.batchListSecurableObjectsForRoles(
+            Lists.newArrayList(role1.id(), role2.id()));
+
+    assertEquals(2, result.size());
+
+    List<SecurableObject> role1SecObjs = result.get(role1.id());
+    Assertions.assertNotNull(role1SecObjs);
+    assertEquals(1, role1SecObjs.size());
+    assertEquals(catalogName1, role1SecObjs.get(0).name());
+
+    List<SecurableObject> role2SecObjs = result.get(role2.id());
+    Assertions.assertNotNull(role2SecObjs);
+    assertEquals(1, role2SecObjs.size());
+    assertEquals(catalogName2, role2SecObjs.get(0).name());
+
+    // A role ID that does not exist should return an empty list
+    long nonExistentRoleId = -9999L;
+    Map<Long, List<SecurableObject>> resultWithMissing =
+        RoleMetaService.batchListSecurableObjectsForRoles(
+            Lists.newArrayList(role1.id(), nonExistentRoleId));
+    assertEquals(2, resultWithMissing.size());
+    assertEquals(1, resultWithMissing.get(role1.id()).size());
+    assertEquals(0, resultWithMissing.get(nonExistentRoleId).size());
+  }
+
   @TestTemplate
   void testInsertRole() throws IOException {
     createAndInsertMakeLake(METALAKE_NAME);
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
index 62220b1205..31eb41e383 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
@@ -32,9 +32,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
@@ -92,8 +89,6 @@ public class JcasbinAuthorizer implements GravitinoAuthorizer 
{
 
   private Cache<Long, Optional<Long>> ownerRel;
 
-  private Executor executor = null;
-
   @Override
   public void initialize() {
     long cacheExpirationSecs =
@@ -129,16 +124,6 @@ public class JcasbinAuthorizer implements 
GravitinoAuthorizer {
             .expireAfterAccess(cacheExpirationSecs, TimeUnit.SECONDS)
             .maximumSize(ownerCacheSize)
             .build();
-    executor =
-        Executors.newFixedThreadPool(
-            GravitinoEnv.getInstance()
-                .config()
-                .get(Configs.GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE),
-            runnable -> {
-              Thread thread = new Thread(runnable);
-              thread.setName("GravitinoAuthorizer-ThreadPool-" + 
thread.getId());
-              return thread;
-            });
   }
 
   private Model getModel(String modelFilePath) {
@@ -416,14 +401,7 @@ public class JcasbinAuthorizer implements 
GravitinoAuthorizer {
   }
 
   @Override
-  public void close() throws IOException {
-    if (executor != null) {
-      if (executor instanceof ThreadPoolExecutor) {
-        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
-        threadPoolExecutor.shutdown();
-      }
-    }
-  }
+  public void close() throws IOException {}
 
   private class InternalAuthorizer {
 
@@ -525,8 +503,19 @@ public class JcasbinAuthorizer implements 
GravitinoAuthorizer {
           // eliminating the N+1 pattern of per-role entityStore.get() calls.
           List<Long> unloadedRoleIds =
               
unloadedRoleStubs.stream().map(RoleEntity::id).collect(Collectors.toList());
-          Map<Long, List<SecurableObject>> secObjsByRoleId =
-              
RoleMetaService.batchListSecurableObjectsForRoles(unloadedRoleIds);
+          Map<Long, List<SecurableObject>> secObjsByRoleId;
+          try {
+            secObjsByRoleId = 
RoleMetaService.batchListSecurableObjectsForRoles(unloadedRoleIds);
+          } catch (RuntimeException e) {
+            throw new RuntimeException(
+                "Failed to batch-load securable objects for roles "
+                    + unloadedRoleIds
+                    + " of metalake "
+                    + metalake
+                    + ", userId "
+                    + userId,
+                e);
+          }
 
           for (RoleEntity stub : unloadedRoleStubs) {
             List<SecurableObject> securableObjects =
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index dbca29ad0c..9a46a0a7c4 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -34,6 +34,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.security.Principal;
@@ -42,9 +43,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
-import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.GravitinoEnv;
@@ -134,8 +133,7 @@ public class TestJcasbinAuthorizer {
         .thenAnswer(
             inv -> {
               List<Long> ids = inv.getArgument(0);
-              com.google.common.collect.ImmutableMap.Builder<Long, 
List<SecurableObject>> result =
-                  com.google.common.collect.ImmutableMap.builder();
+              ImmutableMap.Builder<Long, List<SecurableObject>> result = 
ImmutableMap.builder();
               for (Long id : ids) {
                 result.put(id, roleSecurableObjectsMap.getOrDefault(id, 
ImmutableList.of()));
               }
@@ -201,7 +199,6 @@ public class TestJcasbinAuthorizer {
 
   @Test
   public void testAuthorize() throws Exception {
-    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
     Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
     assertFalse(doAuthorize(currentPrincipal));
     RoleEntity allowRole =
@@ -380,21 +377,8 @@ public class TestJcasbinAuthorizer {
         "testCatalog2", getDenySecurableObjectPO(), 
MetadataObject.Type.CATALOG);
   }
 
-  private static void makeCompletableFutureUseCurrentThread(JcasbinAuthorizer 
jcasbinAuthorizer) {
-    try {
-      Executor currentThread = Runnable::run;
-      Class<JcasbinAuthorizer> jcasbinAuthorizerClass = 
JcasbinAuthorizer.class;
-      Field field = jcasbinAuthorizerClass.getDeclaredField("executor");
-      field.setAccessible(true);
-      FieldUtils.writeField(field, jcasbinAuthorizer, currentThread);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   @Test
   public void testRoleCacheInvalidation() throws Exception {
-    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
 
     // Get the loadedRoles cache via reflection
     Cache<Long, Boolean> loadedRoles = getLoadedRolesCache(jcasbinAuthorizer);
@@ -437,7 +421,6 @@ public class TestJcasbinAuthorizer {
 
   @Test
   public void testRoleCacheSynchronousRemovalListenerDeletesPolicy() throws 
Exception {
-    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
 
     // Get the enforcers via reflection
     Enforcer allowEnforcer = getAllowEnforcer(jcasbinAuthorizer);
@@ -462,7 +445,6 @@ public class TestJcasbinAuthorizer {
     assertTrue(denyEnforcer.hasPolicy(roleIdStr, "CATALOG", "999", 
"USE_CATALOG", "allow"));
 
     // Invalidate the cache entry - this triggers the synchronous removal 
listener
-    // (using executor(Runnable::run) to ensure synchronous execution)
     loadedRoles.invalidate(testRoleId);
 
     // Verify the role's policies have been deleted from enforcers 
(synchronous, no need to wait)
@@ -483,7 +465,6 @@ public class TestJcasbinAuthorizer {
   /** Tests {@link JcasbinAuthorizer#hasMetadataPrivilegePermission} hierarchy 
walk */
   @Test
   public void testHasMetadataPrivilegePermission() throws Exception {
-    makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
     NameIdentifier userNameIdentifier = NameIdentifierUtil.ofUser(METALAKE, 
USERNAME);
 
     // --- Case 1: no MANAGE_GRANTS anywhere → false ---

Reply via email to