| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- package sys
- import (
- "os"
- "runtime/debug"
- "strconv"
- "strings"
- "github.com/shirou/gopsutil/v4/mem"
- )
- // memLimitHeadroomPercent is the share of detected memory used for the soft
- // limit, leaving room for non-heap (stacks, mmap, the xray child) before the OS
- // OOM-kills the process.
- const memLimitHeadroomPercent = 90
- // ApplyMemoryLimit sets a Go soft memory limit (the runtime's GOMEMLIMIT) when
- // one is not already configured, so a long-running panel in a memory-capped
- // container or VPS triggers GC as it approaches the cap instead of growing RSS
- // until the OS OOM-kills it. Precedence: an explicit GOMEMLIMIT env is left to
- // the runtime; otherwise XUI_MEMORY_LIMIT (in MiB) wins; otherwise the limit is
- // derived from the cgroup memory limit, falling back to total system RAM.
- // Returns the limit applied in bytes (0 when none) and a short source label.
- func ApplyMemoryLimit() (int64, string) {
- if strings.TrimSpace(os.Getenv("GOMEMLIMIT")) != "" {
- return 0, "GOMEMLIMIT env (handled by the Go runtime)"
- }
- if v := strings.TrimSpace(os.Getenv("XUI_MEMORY_LIMIT")); v != "" {
- if mb, err := strconv.ParseInt(v, 10, 64); err == nil && mb > 0 {
- limit := mb << 20
- debug.SetMemoryLimit(limit)
- return limit, "XUI_MEMORY_LIMIT=" + v + "MiB"
- }
- }
- total, source := detectAvailableMemory()
- if total <= 0 {
- return 0, "undetectable; left at Go default"
- }
- limit := total / 100 * memLimitHeadroomPercent
- debug.SetMemoryLimit(limit)
- return limit, source
- }
- func detectAvailableMemory() (int64, string) {
- if v, ok := cgroupMemoryLimit(); ok {
- return v, "cgroup limit"
- }
- if vm, err := mem.VirtualMemory(); err == nil && vm.Total > 0 {
- return int64(vm.Total), "system RAM"
- }
- return 0, ""
- }
- // cgroupMemoryLimit reads the container memory limit from cgroup v2 then v1.
- // A "max" value or the v1 unlimited sentinel (~8 EiB) means no limit at this
- // level, so it reports not-found and the caller falls back to system RAM. The
- // files are absent off Linux, which also yields not-found.
- func cgroupMemoryLimit() (int64, bool) {
- const unlimited = int64(1) << 62
- if b, err := os.ReadFile("/sys/fs/cgroup/memory.max"); err == nil {
- if s := strings.TrimSpace(string(b)); s != "" && s != "max" {
- if v, err := strconv.ParseInt(s, 10, 64); err == nil && v > 0 && v < unlimited {
- return v, true
- }
- }
- }
- if b, err := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes"); err == nil {
- if v, err := strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64); err == nil && v > 0 && v < unlimited {
- return v, true
- }
- }
- return 0, false
- }
|