Skip to content

Commit ba626aa

Browse files
committed
Use dynamic GOMEMLIMIT: 25% of system RAM, clamped to [2GB, 8GB]
Replace static 2GB default with platform-aware auto-detection: - Linux: syscall.Sysinfo - macOS: sysctl hw.memsize - Windows: GlobalMemoryStatusEx Falls back to 4GB if detection fails. User config still overrides. Prevents both OOM kills (upper clamp) and excessive GC pressure (lower clamp).
1 parent fac0c57 commit ba626aa

File tree

5 files changed

+103
-7
lines changed

5 files changed

+103
-7
lines changed

cmd/codebase-memory-mcp/main.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,17 @@ func main() {
6767
log.Fatalf("config err=%v", err)
6868
}
6969

70-
// Apply GOMEMLIMIT from config (e.g. "2G", "512M"), default 2GB.
71-
// Prevents OOM kills on systems without user-configured limits.
72-
memLimitStr := cfg.Get(store.ConfigMemLimit, "2G")
73-
if limit, parseErr := parseByteSize(memLimitStr); parseErr != nil {
74-
log.Printf("config: invalid mem_limit %q: %v", memLimitStr, parseErr)
75-
debug.SetMemoryLimit(2 << 30) // fallback: 2GB
70+
// Apply GOMEMLIMIT from config (e.g. "4G", "512M").
71+
// If not configured, auto-detect: 25% of system memory, clamped to [2GB, 8GB].
72+
// Prevents OOM kills while avoiding unnecessary GC pressure.
73+
if memLimitStr := cfg.Get(store.ConfigMemLimit, ""); memLimitStr != "" {
74+
if limit, parseErr := parseByteSize(memLimitStr); parseErr != nil {
75+
log.Printf("config: invalid mem_limit %q: %v", memLimitStr, parseErr)
76+
} else {
77+
debug.SetMemoryLimit(limit)
78+
}
7679
} else {
77-
debug.SetMemoryLimit(limit)
80+
debug.SetMemoryLimit(autoMemLimit())
7881
}
7982

8083
srv := tools.NewServer(router, tools.WithConfig(cfg))
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import "log"
4+
5+
const (
6+
minMemLimit int64 = 2 << 30 // 2 GB
7+
maxMemLimit int64 = 8 << 30 // 8 GB
8+
memFraction int64 = 4 // use 1/4 of system memory
9+
)
10+
11+
// autoMemLimit returns a GOMEMLIMIT value based on available system memory.
12+
// Uses 25% of total physical RAM, clamped to [2GB, 8GB].
13+
// Falls back to 4GB if detection fails.
14+
func autoMemLimit() int64 {
15+
total := totalSystemMemory()
16+
if total <= 0 {
17+
log.Printf("mem_limit: auto-detect unavailable, default=4GB")
18+
return 4 << 30
19+
}
20+
21+
limit := total / memFraction
22+
if limit < minMemLimit {
23+
limit = minMemLimit
24+
}
25+
if limit > maxMemLimit {
26+
limit = maxMemLimit
27+
}
28+
log.Printf("mem_limit: system=%dGB limit=%dGB", total>>30, limit>>30)
29+
return limit
30+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build darwin
2+
3+
package main
4+
5+
import "golang.org/x/sys/unix"
6+
7+
// totalSystemMemory returns total physical memory in bytes via sysctl hw.memsize.
8+
func totalSystemMemory() int64 {
9+
mem, err := unix.SysctlUint64("hw.memsize")
10+
if err != nil {
11+
return 0
12+
}
13+
return int64(mem)
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build linux
2+
3+
package main
4+
5+
import "syscall"
6+
7+
// totalSystemMemory returns total physical memory in bytes via Sysinfo.
8+
func totalSystemMemory() int64 {
9+
var si syscall.Sysinfo_t
10+
if err := syscall.Sysinfo(&si); err != nil {
11+
return 0
12+
}
13+
return int64(si.Totalram) * int64(si.Unit)
14+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//go:build windows
2+
3+
package main
4+
5+
import (
6+
"syscall"
7+
"unsafe"
8+
)
9+
10+
// memoryStatusEx mirrors the Windows MEMORYSTATUSEX struct.
11+
type memoryStatusEx struct {
12+
length uint32
13+
memoryLoad uint32
14+
totalPhys uint64
15+
availPhys uint64
16+
totalPageFile uint64
17+
availPageFile uint64
18+
totalVirtual uint64
19+
availVirtual uint64
20+
availExtendedVirtual uint64
21+
}
22+
23+
// totalSystemMemory returns total physical memory via GlobalMemoryStatusEx.
24+
func totalSystemMemory() int64 {
25+
kernel32 := syscall.NewLazyDLL("kernel32.dll")
26+
proc := kernel32.NewProc("GlobalMemoryStatusEx")
27+
28+
var ms memoryStatusEx
29+
ms.length = uint32(unsafe.Sizeof(ms)) //nolint:gosec // standard Windows API pattern
30+
r, _, _ := proc.Call(uintptr(unsafe.Pointer(&ms)))
31+
if r == 0 {
32+
return 0
33+
}
34+
return int64(ms.totalPhys)
35+
}

0 commit comments

Comments
 (0)