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

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-jcasbin.git


The following commit(s) were added to refs/heads/master by this push:
     new 1eca2fb  fix: scope g() result cache to each enforcer
1eca2fb is described below

commit 1eca2fb60310843e49adb2e16301b6b0a56b8d21
Author: Yang Luo <[email protected]>
AuthorDate: Tue Oct 20 22:28:42 2026 +0800

    fix: scope g() result cache to each enforcer
---
 .../java/org/casbin/jcasbin/main/CoreEnforcer.java |  7 ++-
 .../org/casbin/jcasbin/util/BuiltInFunctions.java  | 30 ++++++++--
 .../jcasbin/main/SyncedEnforcerUnitTest.java       | 70 ++++++++++++++++++++++
 3 files changed, 99 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java 
b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
index 577ee99..d6079f3 100644
--- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.googlecode.aviator.AviatorEvaluator;
 import com.googlecode.aviator.AviatorEvaluatorInstance;
 import com.googlecode.aviator.Expression;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
 import com.googlecode.aviator.runtime.type.AviatorFunction;
 import org.casbin.jcasbin.effect.DefaultEffector;
 import org.casbin.jcasbin.effect.Effect;
@@ -42,6 +43,7 @@ import org.casbin.jcasbin.util.EnforceContext;
 import org.casbin.jcasbin.util.Util;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 
@@ -68,6 +70,7 @@ public class CoreEnforcer {
     boolean acceptJsonRequest = false;
 
     private AviatorEvaluatorInstance aviatorEval;
+    private final Map<String, Map<String, AviatorBoolean>> gFunctionCache = 
new ConcurrentHashMap<>();
 
     void initialize() {
         rmMap = new HashMap<>();
@@ -541,13 +544,13 @@ public class CoreEnforcer {
 
                 RoleManager rm = ast.rm;
                 if (rm != null){
-                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm);
+                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm, 
gFunctionCache);
                     gFunctions.put(key, aviatorFunction);
                 }
 
                 ConditionalRoleManager condRM = ast.condRM;
                 if (condRM != null){
-                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
 condRM);
+                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
 condRM, gFunctionCache);
                     gFunctions.put(key, aviatorFunction);
                 }
 
diff --git a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java 
b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
index 5c03755..7cb39b0 100644
--- a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
+++ b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
@@ -400,9 +400,6 @@ public class BuiltInFunctions {
 
 
     public static class GenerateGFunctionClass {
-        // key:name such as g,g2  value:user-role mapping
-        private static Map<String, Map<String, AviatorBoolean>> memorizedMap = 
new ConcurrentHashMap<>();
-
         /**
          * generateGFunction is the factory method of the g(_, _) function.
          *
@@ -411,6 +408,18 @@ public class BuiltInFunctions {
          * @return the function.
          */
         public static AviatorFunction generateGFunction(String name, 
RoleManager rm) {
+            return generateGFunction(name, rm, new ConcurrentHashMap<>());
+        }
+
+        /**
+         * generateGFunction is the factory method of the g(_, _) function.
+         *
+         * @param name         the name of the g(_, _) function, can be "g", 
"g2", ..
+         * @param rm           the role manager used by the function.
+         * @param memorizedMap the cache of g(_, _) results owned by the 
caller, key: name such as g, g2.
+         * @return the function.
+         */
+        public static AviatorFunction generateGFunction(String name, 
RoleManager rm, Map<String, Map<String, AviatorBoolean>> memorizedMap) {
             memorizedMap.put(name, new ConcurrentHashMap<>());
 
             return new AbstractVariadicFunction() {
@@ -458,9 +467,6 @@ public class BuiltInFunctions {
     }
 
     public static class GenerateConditionalGFunctionClass {
-        // key:name such as g,g2  value:user-role mapping
-        private static Map<String, Map<String, AviatorBoolean>> memorizedMap = 
new ConcurrentHashMap<>();
-
         /**
          * GenerateConditionalGFunction is the factory method of the g(_, _[, 
_]) function with conditions.
          *
@@ -469,6 +475,18 @@ public class BuiltInFunctions {
          * @return the function.
          */
         public static AviatorFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm) {
+            return generateConditionalGFunction(name, condRm, new 
ConcurrentHashMap<>());
+        }
+
+        /**
+         * GenerateConditionalGFunction is the factory method of the g(_, _[, 
_]) function with conditions.
+         *
+         * @param name         the name of the g(_, _) function, can be "g", 
"g2", ..
+         * @param condRm       the conditional role manager used by the 
function.
+         * @param memorizedMap the cache of g(_, _) results owned by the 
caller, key: name such as g, g2.
+         * @return the function.
+         */
+        public static AviatorFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm, Map<String, Map<String, AviatorBoolean>> 
memorizedMap) {
             memorizedMap.put(name, new ConcurrentHashMap<>());
 
             return new AbstractVariadicFunction() {
diff --git a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
index b42bb17..05dc00c 100644
--- a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
@@ -17,20 +17,27 @@
 
 package org.casbin.jcasbin.main;
 
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
 import org.casbin.jcasbin.model.Model;
 import org.casbin.jcasbin.persist.Adapter;
 import org.casbin.jcasbin.persist.file_adapter.FileAdapter;
+import org.casbin.jcasbin.util.function.CustomFunction;
 import org.testng.annotations.Test;
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static java.util.Arrays.asList;
 import static org.casbin.jcasbin.main.CoreEnforcer.newModel;
 import static org.casbin.jcasbin.main.TestUtil.*;
 import static org.casbin.jcasbin.main.TestUtil.testEnforceEx;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
 
 public class SyncedEnforcerUnitTest {
 
@@ -38,6 +45,69 @@ public class SyncedEnforcerUnitTest {
         assertEquals(res, e.enforce(sub, obj, act));
     }
 
+    public static void testEnforceSync(SyncedEnforcer e, String sub, Object 
obj, boolean res) {
+        assertEquals(res, e.enforce(sub, obj));
+    }
+
+    private static class GateFunction extends CustomFunction {
+        final CountDownLatch entered = new CountDownLatch(1);
+        final CountDownLatch release = new CountDownLatch(1);
+        volatile boolean block;
+
+        @Override
+        public AviatorObject call(Map<String, Object> env, AviatorObject arg) {
+            if (block) {
+                entered.countDown();
+                try {
+                    release.await(10, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+            return AviatorBoolean.TRUE;
+        }
+
+        @Override
+        public String getName() {
+            return "gate";
+        }
+    }
+
+    private static SyncedEnforcer newGatedEnforcer(GateFunction gate, boolean 
hasRole) {
+        Model m = newModel();
+        m.addDef("r", "r", "sub, obj");
+        m.addDef("p", "p", "sub, obj");
+        m.addDef("g", "g", "_, _");
+        m.addDef("e", "e", "some(where (p.eft == allow))");
+        m.addDef("m", "m", "gate(r.sub) && g(r.sub, p.sub) && r.obj == p.obj");
+        SyncedEnforcer e = new SyncedEnforcer(m);
+        e.addFunction(gate.getName(), gate);
+        e.addPolicy("admin", "data");
+        if (hasRole) {
+            e.addGroupingPolicy("alice", "admin");
+        }
+        return e;
+    }
+
+    @Test
+    public void testRoleCacheIsolatedBetweenEnforcers() throws 
InterruptedException {
+        GateFunction gate = new GateFunction();
+        SyncedEnforcer isolated = newGatedEnforcer(gate, false);
+        SyncedEnforcer privileged = newGatedEnforcer(new GateFunction(), true);
+        testEnforceSync(isolated, "alice", "data", false);
+
+        gate.block = true;
+        AtomicReference<Boolean> result = new AtomicReference<>();
+        Thread t = new Thread(() -> result.set(isolated.enforce("alice", 
"data")));
+        t.start();
+        assertTrue(gate.entered.await(10, TimeUnit.SECONDS));
+        testEnforceSync(privileged, "alice", "data", true);
+        gate.release.countDown();
+        t.join(10000);
+
+        assertEquals(result.get(), Boolean.FALSE);
+    }
+
     @Test
     public void testSync(){
         SyncedEnforcer e = new SyncedEnforcer("examples/basic_model.conf", 
"examples/basic_policy.csv");


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

Reply via email to