memlimit

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 7 Imported by: 0

README

memlimit

Auto-set Go's GOMEMLIMIT from cgroups — tiny, dependency-free, OOM-safe.

Go Reference Go Version License Dependencies


Sets the soft memory limit (GOMEMLIMIT) from a container's cgroup memory limit, so the garbage collector pushes back before the process hits the ceiling and gets OOM-killed.

📖 Why

By default the Go runtime does not know its container's memory limit: the GC paces itself against heap growth, not the cgroup cap. Under memory pressure a containerized service can overshoot its limit and get OOM-killed and restarted. Setting GOMEMLIMIT to a fraction of the cgroup limit makes the GC work harder as it nears the ceiling — trading a little CPU for far fewer OOM kills.

Doesn't Go already do this? As of Go 1.25 the runtime sets GOMAXPROCS from the cgroup CPU limit automatically — but there is still no built-in equivalent for GOMEMLIMIT. This library fills that gap.

✨ Features

  • Zero dependencies: standard library only.
  • cgroup v2 & v1: reads memory.max (v2) with memory.limit_in_bytes (v1) fallback, plus hybrid.
  • Namespace-aware: resolves the process's own cgroup via /proc/self/cgroup, then the mount root.
  • Safe no-op: no limit, non-Linux, or a too-small limit leaves GOMEMLIMIT untouched — the happy path never errors.
  • Death-spiral guard: refuses to set a pathologically small limit that would trap the GC.
  • Bring your own logger: silent by default, or pass an *slog.Logger.

🚀 Quick Start

go get github.com/gocronx-team/memlimit

Call it once, early in main:

import (
    "log/slog"

    "github.com/gocronx-team/memlimit"
)

func main() {
    // Set GOMEMLIMIT to 90% of the cgroup memory limit, logging the outcome.
    memlimit.SetFromCgroup(memlimit.WithLogger(slog.Default()))

    // ... start your app ...
}

Silent (default):

memlimit.SetFromCgroup()

Inspect without changing anything:

bytes, found := memlimit.DetectCgroupLimit()

⚙️ API

Symbol Purpose
SetFromCgroup(opts ...Option) (int64, error) Read the cgroup limit and set GOMEMLIMIT. Returns bytes set (0 if unchanged). Errors only on invalid options.
DetectCgroupLimit() (int64, bool) Read the raw cgroup limit with no side effects.
WithRatio(r float64) Fraction of the cgroup limit to use. Default 0.9; must be in (0, 1].
WithMinBytes(n int64) Skip setting a computed limit below this. Default 16 MiB.
WithLogger(l *slog.Logger) Log the outcome. Silent by default.

🔍 How It Works

  • cgroup v2: memory.max at the process's cgroup path and the mount root (max = no limit).
  • cgroup v1: falls back to memory.limit_in_bytes (huge sentinel = no limit).
  • hybrid: if v2 reports max but the v1 memory controller has a real limit, uses the v1 value.
  • no-op: no limit / non-Linux / below the minimum → GOMEMLIMIT left unchanged.

📊 Comparison

Benchmarked head-to-head against the reference library it was modeled on (KimMachineGun/automemlimit), inside a Linux container with a real 256 MiB limit — both read the identical limit, while this implementation is dependency-free and lighter:

this library automemlimit v0.7.5
Third-party dependencies 0 1
Detection time / op ~13 µs ~40 µs
Detection memory / op 1.7 KB 21.6 KB
Detection allocs / op 14 119

The reference is more feature-rich (dynamic refresh, system-memory fallback, exotic mount layouts) and more battle-tested; this library is the better fit for a lightweight, set-once-at-startup helper. Full methodology and an honest trade-off breakdown: COMPARISON.md.

⚠️ Scope

Targets the common single-container case (mount root + /proc/self/cgroup nesting). It does not parse mountinfo for unusual cgroup mount layouts — that covers the vast majority of Docker/Kubernetes deployments and can be extended if needed.

🙏 Acknowledgements

Thanks to KimMachineGun/automemlimit — the idea of driving GOMEMLIMIT from the cgroup limit, and the behavior this library models, were learned from it. This is an independent implementation, but it stands on the approach that project pioneered. Much appreciated. 🙌

📄 License

MIT

Documentation

Overview

Package memlimit sets Go's soft memory limit (GOMEMLIMIT) from the container's cgroup memory limit, so the garbage collector applies back-pressure before the process hits the cgroup ceiling and gets OOM-killed.

By default Go does not know its container's memory limit: the GC paces itself against heap growth, not against the cgroup cap. Under memory pressure that leads to OOM kills and restarts. Calling SetFromCgroup once at startup teaches the runtime the limit (defaulting to 90% of it, leaving headroom for non-heap allocations the GC cannot reclaim).

It is a safe no-op when there is no cgroup memory limit, on non-Linux platforms, or when the limit is too small to be sane.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DetectCgroupLimit

func DetectCgroupLimit() (bytes int64, found bool)

DetectCgroupLimit returns the raw cgroup memory limit in bytes (before any ratio is applied) and whether a finite limit was found. It performs no side effects, so it is useful for inspecting or comparing what SetFromCgroup would act on. Returns (0, false) on non-Linux or when there is no limit.

func SetFromCgroup

func SetFromCgroup(opts ...Option) (int64, error)

SetFromCgroup reads the current cgroup memory limit and, if found, sets GOMEMLIMIT to that limit multiplied by the configured ratio.

It returns the number of bytes GOMEMLIMIT was set to, or 0 if it was left unchanged (no cgroup limit, non-Linux, unlimited, or below the minimum). A non-nil error is returned only for invalid options.

Types

type Option

type Option func(*config)

Option customizes SetFromCgroup.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger enables logging of the outcome via the given slog.Logger. By default nothing is logged.

func WithMinBytes

func WithMinBytes(n int64) Option

WithMinBytes sets the smallest computed limit worth applying. If the computed value is below this, GOMEMLIMIT is left unchanged. The default is 16 MiB.

func WithRatio

func WithRatio(r float64) Option

WithRatio sets the fraction of the cgroup limit used for GOMEMLIMIT. r must be in (0, 1]; the default is 0.9.

Directories

Path Synopsis
Command example demonstrates and verifies the end-to-end effect of memlimit: it applies GOMEMLIMIT from the cgroup, then reads the runtime's current value back via debug.SetMemoryLimit(-1) to confirm it was actually applied.
Command example demonstrates and verifies the end-to-end effect of memlimit: it applies GOMEMLIMIT from the cgroup, then reads the runtime's current value back via debug.SetMemoryLimit(-1) to confirm it was actually applied.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL