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
commit 74322db8ebd2fa7a9e15e7369db6add65498f83b Author: Yang Luo <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
