You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/04/24 13:53:11 UTC

(casbin) branch master updated: fix: add gFunctionCache boolean option to enforcer

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


The following commit(s) were added to refs/heads/master by this push:
     new 26288f9  fix: add gFunctionCache boolean option to enforcer
26288f9 is described below

commit 26288f98cc0a1b9418f55ca455776a2d63a1e499
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Fri Apr 24 21:52:57 2026 +0800

    fix: add gFunctionCache boolean option to enforcer
---
 enforcer.go               | 12 +++++++++-
 management_api.go         |  2 +-
 util/builtin_operators.go | 61 ++++++++++++++++++++++++++++++-----------------
 3 files changed, 51 insertions(+), 24 deletions(-)

diff --git a/enforcer.go b/enforcer.go
index a6bf174..c78350c 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -56,6 +56,7 @@ type Enforcer struct {
 	autoNotifyWatcher    bool
 	autoNotifyDispatcher bool
 	acceptJsonRequest    bool
+	gFunctionCache       bool
 
 	aiConfig AIConfig
 }
@@ -193,6 +194,7 @@ func (e *Enforcer) initialize() {
 	e.autoBuildRoleLinks = true
 	e.autoNotifyWatcher = true
 	e.autoNotifyDispatcher = true
+	e.gFunctionCache = true
 	e.initRmMap()
 
 	// Initialize detectors with default detector if not already set
@@ -637,6 +639,14 @@ func (e *Enforcer) EnableAcceptJsonRequest(acceptJsonRequest bool) {
 	e.acceptJsonRequest = acceptJsonRequest
 }
 
+// EnableGFunctionCache controls whether to cache g() function results.
+// Disable this when inputs are high-cardinality (e.g. UUIDs or dynamic paths)
+// to prevent unbounded memory growth.
+func (e *Enforcer) EnableGFunctionCache(enabled bool) {
+	e.gFunctionCache = enabled
+	e.invalidateMatcherMap()
+}
+
 // BuildRoleLinks manually rebuild the role inheritance relations.
 func (e *Enforcer) BuildRoleLinks() error {
 	e.invalidateMatcherMap()
@@ -704,7 +714,7 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 			//   or a conditional role definition (ast.CondRM != nil)
 			// ast.RM and ast.CondRM shouldn't be nil at the same time
 			if ast.RM != nil {
-				functions[key] = util.GenerateGFunction(ast.RM)
+				functions[key] = util.GenerateGFunction(ast.RM, e.gFunctionCache)
 			}
 			if ast.CondRM != nil {
 				functions[key] = util.GenerateConditionalGFunction(ast.CondRM)
diff --git a/management_api.go b/management_api.go
index 7a8f768..d18536c 100644
--- a/management_api.go
+++ b/management_api.go
@@ -145,7 +145,7 @@ func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher strin
 			//   or a conditional role definition (ast.CondRM != nil)
 			// ast.RM and ast.CondRM shouldn't be nil at the same time
 			if ast.RM != nil {
-				functions[key] = util.GenerateGFunction(ast.RM)
+				functions[key] = util.GenerateGFunction(ast.RM, e.gFunctionCache)
 			}
 			if ast.CondRM != nil {
 				functions[key] = util.GenerateConditionalGFunction(ast.CondRM)
diff --git a/util/builtin_operators.go b/util/builtin_operators.go
index 37d9cb5..066a0bd 100644
--- a/util/builtin_operators.go
+++ b/util/builtin_operators.go
@@ -402,34 +402,53 @@ func GlobMatchFunc(args ...interface{}) (interface{}, error) {
 }
 
 // GenerateGFunction is the factory method of the g(_, _[, _]) function.
-func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
+// If useCache is true, results are memoized per unique argument combination for performance.
+// Set useCache to false when inputs are high-cardinality (e.g. UUIDs) to avoid unbounded memory growth.
+func GenerateGFunction(rm rbac.RoleManager, useCache bool) govaluate.ExpressionFunction {
 	memorized := sync.Map{}
 	return func(args ...interface{}) (interface{}, error) {
 		// Like all our other govaluate functions, all args are strings.
 
-		// Allocate and generate a cache key from the arguments...
-		total := len(args)
-		for _, a := range args {
-			aStr := a.(string)
-			total += len(aStr)
-		}
-		builder := strings.Builder{}
-		builder.Grow(total)
-		for _, arg := range args {
-			builder.WriteByte(0)
-			builder.WriteString(arg.(string))
-		}
-		key := builder.String()
-
-		// ...and see if we've already calculated this.
-		v, found := memorized.Load(key)
-		if found {
+		if useCache {
+			// Allocate and generate a cache key from the arguments...
+			total := len(args)
+			for _, a := range args {
+				aStr := a.(string)
+				total += len(aStr)
+			}
+			builder := strings.Builder{}
+			builder.Grow(total)
+			for _, arg := range args {
+				builder.WriteByte(0)
+				builder.WriteString(arg.(string))
+			}
+			key := builder.String()
+
+			// ...and see if we've already calculated this.
+			if v, found := memorized.Load(key); found {
+				return v, nil
+			}
+
+			// If not, do the calculation.
+			// There are guaranteed to be exactly 2 or 3 arguments.
+			name1, name2 := args[0].(string), args[1].(string)
+			var v interface{}
+			if rm == nil {
+				v = name1 == name2
+			} else if len(args) == 2 {
+				v, _ = rm.HasLink(name1, name2)
+			} else {
+				domain := args[2].(string)
+				v, _ = rm.HasLink(name1, name2, domain)
+			}
+
+			memorized.Store(key, v)
 			return v, nil
 		}
 
-		// If not, do the calculation.
-		// There are guaranteed to be exactly 2 or 3 arguments.
+		// No caching path.
 		name1, name2 := args[0].(string), args[1].(string)
+		var v interface{}
 		if rm == nil {
 			v = name1 == name2
 		} else if len(args) == 2 {
@@ -438,8 +457,6 @@ func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
 			domain := args[2].(string)
 			v, _ = rm.HasLink(name1, name2, domain)
 		}
-
-		memorized.Store(key, v)
 		return v, nil
 	}
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org