mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 00:17:27 +00:00
fix(gateway): re-apply tool rate limiter after system_configs overlay (#1111)
setupToolRegistry creates the rate limiter from cfg.Tools.RateLimitPerHour during early bootstrap (gateway.go line 137). System_configs DB overlay runs ~50 lines later via cfg.ApplySystemConfigs (line 191), so any DB override of tools.rate_limit_per_hour was silently lost - the limiter object was already initialised from the JSON5 default. Symptom in production: editing tools.rate_limit_per_hour via system_configs table or the config HTTP API had no effect on running gateways. Operators had to inject a config.json file to change the value, defeating the DB-as-source-of-truth pattern that other tunables rely on. Re-apply the limiter after ApplySystemConfigs runs. Safe ordering: server has not started yet, no in-flight tool calls, and SetRateLimiter is a plain field assignment with no shutdown cost on the discarded limiter. nil case (rate_limit_per_hour <= 0) also handled so DB writes can disable the limiter without restart. Tests: new TestRegistry_SetRateLimiter_ReplacesPriorLimiter covers both the replace-with-higher-limit path and the nil-disables path. All existing rate limiter tests still pass. Note: the same ordering pattern likely affects other config consumed inside setupToolRegistry (tools.scrub_credentials, MCP server wiring). Out of scope for this hotfix - they need a deeper restructure.
This commit is contained in:
1 parent
b5d5fce6e4
commit
db98e477d4
2 files changed
+48
No files matched your search
@@ -226,6 +226,18 @@ func runGateway() {
|
||||
slog.Info("system_configs applied to in-memory config", "keys", len(sysConfigs))
|
||||
}
|
||||
}
|
||||
|
||||
// Re-apply tool rate limiter using DB-overlaid config. setupToolRegistry
|
||||
// initialised the limiter from the JSON5 default before ApplySystemConfigs
|
||||
// ran, so DB-driven changes to tools.rate_limit_per_hour were lost. Replace
|
||||
// the limiter object now that cfg reflects the DB value. Safe: server has
|
||||
// not started, no in-flight tool calls.
|
||||
if cfg.Tools.RateLimitPerHour > 0 {
|
||||
toolsReg.SetRateLimiter(tools.NewToolRateLimiter(cfg.Tools.RateLimitPerHour))
|
||||
slog.Info("tool rate limiting reapplied from system_configs", "per_hour", cfg.Tools.RateLimitPerHour)
|
||||
} else {
|
||||
toolsReg.SetRateLimiter(nil)
|
||||
}
|
||||
setupMemoryEmbeddings(pgStores, providerRegistry)
|
||||
usageCapSvc := usagecaps.NewService(pgStores.UsageCaps, pgStores.Providers)
|
||||
|
||||
|
||||
@@ -168,6 +168,42 @@ func TestRegistry_ExecuteWithContext_RateLimiting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetRateLimiter must be re-callable so that startup code can swap the limiter
|
||||
// after DB-overlaid config is applied (cmd/gateway.go re-applies once
|
||||
// system_configs has overlaid the JSON5 default).
|
||||
func TestRegistry_SetRateLimiter_ReplacesPriorLimiter(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
reg.SetRateLimiter(NewToolRateLimiter(1)) // simulate JSON5 default
|
||||
reg.Register(&mockTool{name: "tool"})
|
||||
|
||||
// Replace with higher limit (simulates DB overlay = 5)
|
||||
reg.SetRateLimiter(NewToolRateLimiter(5))
|
||||
|
||||
for i := range 5 {
|
||||
result := reg.ExecuteWithContext(context.Background(), "tool", nil,
|
||||
"", "", "", "session-replace", nil)
|
||||
if result.IsError {
|
||||
t.Errorf("call %d should succeed under new 5/h limit: %s", i, result.ForLLM)
|
||||
}
|
||||
}
|
||||
result := reg.ExecuteWithContext(context.Background(), "tool", nil,
|
||||
"", "", "", "session-replace", nil)
|
||||
if !result.IsError {
|
||||
t.Error("6th call should hit the 5/h limit")
|
||||
}
|
||||
|
||||
// Disable rate limiting via nil — verifies the gateway path that disables
|
||||
// the limiter when cfg.Tools.RateLimitPerHour <= 0.
|
||||
reg.SetRateLimiter(nil)
|
||||
for i := range 10 {
|
||||
result := reg.ExecuteWithContext(context.Background(), "tool", nil,
|
||||
"", "", "", "session-replace", nil)
|
||||
if result.IsError {
|
||||
t.Errorf("call %d after nil limiter should be unbounded", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_ExecuteWithContext_NoRateLimitWithoutSessionKey(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
reg.SetRateLimiter(NewToolRateLimiter(1))
|
||||
|
||||
Reference in new issue
Block a user