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


##########
server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java:
##########
@@ -482,36 +528,37 @@ private void loadRolePrivilege(
                         SupportsRelationOperations.Type.ROLE_USER_REL,
                         userNameIdentifier,
                         Entity.EntityType.USER);
+            Set<Long> roleIds = new HashSet<>(entities.size());
             List<CompletableFuture<Void>> loadRoleFutures = new ArrayList<>();
             for (RoleEntity role : entities) {
               Long roleId = role.id();
+              roleIds.add(roleId);
               allowEnforcer.addRoleForUser(String.valueOf(userId), 
String.valueOf(roleId));
               denyEnforcer.addRoleForUser(String.valueOf(userId), 
String.valueOf(roleId));
-              if (loadedRoles.getIfPresent(roleId) != null) {
-                continue;
-              }
               CompletableFuture<Void> loadRoleFuture =
-                  CompletableFuture.supplyAsync(
-                          () -> {
-                            try {
-                              return entityStore.get(
-                                  NameIdentifierUtil.ofRole(metalake, 
role.name()),
-                                  Entity.EntityType.ROLE,
-                                  RoleEntity.class);
-                            } catch (Exception e) {
-                              throw new RuntimeException("Failed to load role: 
" + role.name(), e);
-                            }
-                          },
-                          executor)
-                      .thenAcceptAsync(
-                          roleEntity -> {
-                            loadPolicyByRoleEntity(roleEntity);
-                            loadedRoles.put(roleId, true);
-                          },
-                          executor);
+                  CompletableFuture.runAsync(
+                      () -> {
+                        loadedRoles.get(
+                            roleId,
+                            unused -> {
+                              try {
+                                RoleEntity roleEntity =
+                                    entityStore.get(
+                                        NameIdentifierUtil.ofRole(metalake, 
role.name()),
+                                        Entity.EntityType.ROLE,
+                                        RoleEntity.class);
+                                return loadPolicyByRoleEntity(roleEntity);
+                              } catch (Exception e) {
+                                throw new RuntimeException(
+                                    "Failed to load role: " + role.name(), e);
+                              }
+                            });
+                      },
+                      executor);
               loadRoleFutures.add(loadRoleFuture);
             }

Review Comment:
   `loadRolePrivilege` now schedules a `CompletableFuture.runAsync` task for 
every role on every request, even when the role is already present in 
`loadedRoles`. This adds thread-pool scheduling/coordination overhead 
proportional to roles-per-user and can partially negate the intended hot-path 
optimization. Consider checking `loadedRoles.getIfPresent(roleId)` first (which 
still counts as an access for `expireAfterAccess`) and only creating async load 
futures for cache misses; alternatively batch-load misses and avoid creating 
futures for cached roles.



##########
core/src/main/java/org/apache/gravitino/authorization/AuthorizationRequestContext.java:
##########
@@ -103,6 +113,14 @@ public void setOriginalAuthorizationExpression(String 
originalAuthorizationExpre
     this.originalAuthorizationExpression = originalAuthorizationExpression;
   }
 
+  public Set<Long> getUserRoleIds() {
+    return userRoleIds;
+  }
+

Review Comment:
   The new `getUserRoleIds` / `setUserRoleIds` methods are public but lack 
JavaDoc, while the rest of this class’s public API is documented. Please add 
JavaDoc describing the expected lifecycle (request-scoped), 
thread-safety/visibility expectations, and whether the returned set is safe to 
iterate and/or modify.
   ```suggestion
   
     /**
      * Returns the user role IDs associated with the current authorization 
request.
      *
      * <p>This context is request-scoped and is expected to be used only for 
the lifetime of a
      * single authorization request.
      *
      * <p>The returned set is the live backing set stored in this context, not 
a defensive copy or
      * unmodifiable view. Iteration is safe only when the set is not being 
modified concurrently.
      * Changes made through the returned set are reflected in this context.
      *
      * <p>This class is not intended for concurrent mutation across threads. 
Callers should not rely
      * on additional thread-safety or cross-thread visibility guarantees 
beyond normal request-scoped
      * usage.
      *
      * @return the live set of user role IDs for the current request, or an 
empty set if none have
      *     been set
      */
     public Set<Long> getUserRoleIds() {
       return userRoleIds;
     }
   
     /**
      * Sets the user role IDs associated with the current authorization 
request.
      *
      * <p>This context is request-scoped and is expected to be used only for 
the lifetime of a
      * single authorization request.
      *
      * <p>The provided set reference is stored directly. The caller must 
ensure the set is not
      * modified concurrently while being read or iterated through this 
context. Subsequent
      * modifications to the provided set are visible through {@link 
#getUserRoleIds()}.
      *
      * <p>This class is not intended for concurrent mutation across threads. 
Callers should not rely
      * on additional thread-safety or cross-thread visibility guarantees 
beyond normal request-scoped
      * usage.
      *
      * @param userRoleIds the user role IDs for the current request; if {@code 
null}, an empty set is
      *     stored
      */
   ```



##########
core/src/main/java/org/apache/gravitino/authorization/AuthorizationRequestContext.java:
##########
@@ -103,6 +113,14 @@ public void setOriginalAuthorizationExpression(String 
originalAuthorizationExpre
     this.originalAuthorizationExpression = originalAuthorizationExpression;
   }
 
+  public Set<Long> getUserRoleIds() {
+    return userRoleIds;
+  }
+
+  public void setUserRoleIds(@Nullable Set<Long> userRoleIds) {
+    this.userRoleIds = userRoleIds == null ? Collections.emptySet() : 
userRoleIds;
+  }

Review Comment:
   `setUserRoleIds` stores the provided `Set` reference directly. Because this 
is request context state that may be read concurrently during authorization, 
storing an externally mutable set can lead to hard-to-debug races if the caller 
mutates it after setting. Consider defensively copying (e.g., to an 
immutable/unmodifiable set) before assigning, and optionally returning an 
unmodifiable view from `getUserRoleIds` to prevent accidental mutation by 
downstream code.



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