// Copyright 2026 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . //go:build linux package memlimit import ( "os" "path" "strconv" "strings" ) // cgroupV1UnlimitedThreshold detects the v1 "no limit" sentinel, which // is LONG_MAX rounded down to the kernel page size. Anything above 1<<62 // is treated as unlimited regardless of page size. const cgroupV1UnlimitedThreshold = uint64(1) << 62 // fileReader abstracts os.ReadFile for testing. type fileReader func(path string) ([]byte, error) // platformLimit returns the cgroup limit (v2 memory.max or v1 // memory.limit_in_bytes) of the current process. The cgroup limit is // the authoritative budget in a container, where /proc/meminfo // reports the host's RAM. func platformLimit() (uint64, Source, bool) { if v, ok := cgroupV2Limit(os.ReadFile); ok { return v, SourceCgroupV2, true } if v, ok := cgroupV1Limit(os.ReadFile); ok { return v, SourceCgroupV1, true } return 0, SourceUnknown, false } // cgroupV2Limit reads the cgroup v2 memory.max for the current process. // It probes /sys/fs/cgroup directly first (the effective root inside a // cgroup-namespaced container), then the path from /proc/self/cgroup // for the bare-metal case where the limit sits on a systemd slice. func cgroupV2Limit(read fileReader) (uint64, bool) { if v, ok := readCgroupV2At("/sys/fs/cgroup", "/", read); ok { return v, true } procPath, ok := readProcSelfCgroupV2(read) if !ok || procPath == "/" { return 0, false } return readCgroupV2At("/sys/fs/cgroup", procPath, read) } // readCgroupV2At reads memory.max under root+rel, walking up parents // until a numeric value is found or the path bottoms out. func readCgroupV2At(root, rel string, read fileReader) (uint64, bool) { // cgroup.controllers exists only on v2; if absent, v2 is not mounted here. if _, err := read(path.Join(root, "cgroup.controllers")); err != nil { return 0, false } for { raw, err := read(path.Join(root, rel, "memory.max")) if err == nil { s := strings.TrimSpace(string(raw)) if s != "max" { // Zero is legal to write but degenerate; treat it like // "max" and keep walking up. if n, err := strconv.ParseUint(s, 10, 64); err == nil && n != 0 { return n, true } } } if rel == "/" || rel == "" { return 0, false } rel = path.Dir(rel) } } // readProcSelfCgroupV2 returns the cgroup path from the v2 line // ("0::") of /proc/self/cgroup. func readProcSelfCgroupV2(read fileReader) (string, bool) { raw, err := read("/proc/self/cgroup") if err != nil { return "", false } for line := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") { // v2 unified line: "0::" if strings.HasPrefix(line, "0::") { return strings.TrimPrefix(line, "0::"), true } } return "", false } // cgroupV1Limit reads memory.limit_in_bytes from the v1 memory // controller, walking up parents when a node reports the unlimited // sentinel. func cgroupV1Limit(read fileReader) (uint64, bool) { rel, ok := readProcSelfCgroupV1Memory(read) if !ok { return 0, false } root := "/sys/fs/cgroup/memory" if _, err := read(path.Join(root, "memory.limit_in_bytes")); err != nil { return 0, false } for { raw, err := read(path.Join(root, rel, "memory.limit_in_bytes")) if err == nil { if n, err := strconv.ParseUint(strings.TrimSpace(string(raw)), 10, 64); err == nil { if n != 0 && n < cgroupV1UnlimitedThreshold { return n, true } } } if rel == "/" || rel == "" { return 0, false } rel = path.Dir(rel) } } // readProcSelfCgroupV1Memory parses /proc/self/cgroup for the v1 memory // controller line (":memory:" or ":...,memory,...:"). func readProcSelfCgroupV1Memory(read fileReader) (string, bool) { raw, err := read("/proc/self/cgroup") if err != nil { return "", false } for line := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") { // Format: "::" parts := strings.SplitN(line, ":", 3) if len(parts) != 3 { continue } for ctrl := range strings.SplitSeq(parts[1], ",") { if ctrl == "memory" { return parts[2], true } } } return "", false }