mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-11 22:54:44 +00:00
d05e468598
## Summary Implements **part 1** of #836 — `GOMEMLIMIT` support so the Go runtime self-throttles GC under cgroup memory pressure instead of getting SIGKILLed. (Parts 2 & 3 — bounded cold-load batching + README ops docs — land in follow-up PRs.) ## Behavior On startup `cmd/server/main.go` now calls `applyMemoryLimit(maxMemoryMB, envSet)`: | Condition | Action | Log | |---|---|---| | `GOMEMLIMIT` env set | Honor the runtime's parse, do nothing | `[memlimit] using GOMEMLIMIT from environment (...)` | | env unset, `packetStore.maxMemoryMB > 0` | `debug.SetMemoryLimit(maxMB * 1.5 MiB)` | `[memlimit] derived from packetStore.maxMemoryMB=512 → 768 MiB (1.5x headroom)` | | env unset, `maxMemoryMB == 0` | No-op | `[memlimit] no soft memory limit set ... recommend setting one to avoid container OOM-kill` | The 1.5x headroom covers Go's NextGC trigger at ~2× live heap (per #836 heap profile: 680 MB live → 1.38 GB NextGC). ## Tests (TDD red→green visible in commit history) - `TestApplyMemoryLimit_FromEnv` — env wins, function does not override - `TestApplyMemoryLimit_DerivedFromMaxMemoryMB` — verifies bytes computation + `debug.SetMemoryLimit` actually applied at runtime - `TestApplyMemoryLimit_None` — no env, no config → reports `"none"`, no side effect Red commit: `7de3c62` (assertion failures, builds clean) Green commit: `454516d` ## Config docs `config.example.json` `packetStore._comment_gomemlimit` documents env/derived/override behavior. ## Out of scope - Cold-load transient bounding (item 2 in #836) - README container-size table (item 3) - QA §1.1 rewrite Closes part 1 of #836. --------- Co-authored-by: corescope-bot <bot@corescope>
55 lines
1.4 KiB
Go
55 lines
1.4 KiB
Go
package main
|
|
|
|
import (
|
|
"runtime/debug"
|
|
"testing"
|
|
)
|
|
|
|
func TestApplyMemoryLimit_FromEnv(t *testing.T) {
|
|
t.Setenv("GOMEMLIMIT", "850MiB")
|
|
// reset to a known state after test
|
|
defer debug.SetMemoryLimit(-1)
|
|
|
|
limit, source := applyMemoryLimit(512, true /* envSet */)
|
|
if source != "env" {
|
|
t.Fatalf("expected source=env, got %q", source)
|
|
}
|
|
// When env is set, our function must NOT override it; reported limit is 0.
|
|
if limit != 0 {
|
|
t.Fatalf("expected limit=0 (not set by us), got %d", limit)
|
|
}
|
|
}
|
|
|
|
func TestApplyMemoryLimit_DerivedFromMaxMemoryMB(t *testing.T) {
|
|
defer debug.SetMemoryLimit(-1)
|
|
|
|
// maxMemoryMB=512 → 512 * 1.5 = 768 MiB = 768 * 1024 * 1024 bytes
|
|
limit, source := applyMemoryLimit(512, false /* envSet */)
|
|
if source != "derived" {
|
|
t.Fatalf("expected source=derived, got %q", source)
|
|
}
|
|
want := int64(768) * 1024 * 1024
|
|
if limit != want {
|
|
t.Fatalf("expected limit=%d, got %d", want, limit)
|
|
}
|
|
// Verify it was actually set on the runtime
|
|
cur := debug.SetMemoryLimit(-1)
|
|
if cur != want {
|
|
t.Fatalf("runtime memory limit not set: want=%d got=%d", want, cur)
|
|
}
|
|
}
|
|
|
|
func TestApplyMemoryLimit_None(t *testing.T) {
|
|
defer debug.SetMemoryLimit(-1)
|
|
// Reset to "no limit" (math.MaxInt64) before test
|
|
debug.SetMemoryLimit(int64(1<<63 - 1))
|
|
|
|
limit, source := applyMemoryLimit(0, false)
|
|
if source != "none" {
|
|
t.Fatalf("expected source=none, got %q", source)
|
|
}
|
|
if limit != 0 {
|
|
t.Fatalf("expected limit=0, got %d", limit)
|
|
}
|
|
}
|