forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 49
WTF: add uv_get_cgroup_current_memory() #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
robobun
wants to merge
2
commits into
main
Choose a base branch
from
farm/8c72807a/cgroup-current-memory
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| #pragma once | ||
|
|
||
| uint64_t uv_get_constrained_memory(); | ||
| uint64_t uv_get_cgroup_current_memory(); | ||
| int uv_get_constrained_cpu(); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Minor asymmetry with this fork's limit reader:
uv__get_cgroup2_memory_limits()walks leaf→root and returns the tightest ancestormemory.max/memory.high, but this readsmemory.currentonly at the leaf. When the effective limit comes from an ancestor (e.g. a K8s pod-level limit with sidecar containers),ancestor.max - leaf.currentwill overestimateprocess.availableMemory()by whatever siblings have charged against the ancestor. Fine to land as-is since it matches upstream libuv and is still strictly better than today, but a follow-up that computesmin(limit_i - current_i)across the hierarchy (or readsmemory.currentat the same level whose limit was selected) would make the two consistent.Extended reasoning...
What the bug is
This fork's
uv__get_cgroup2_memory_limits()(Source/WTF/wtf/uv_get_constrained_memory.cpp:186-224) diverges from upstream libuv: it walks from the leaf cgroup up to/sys/fs/cgroupand returns the tightestmemory.max/memory.highfound at any ancestor. The comment at line 203 is explicit: "cgroup v2 limits are hierarchical: walk from the leaf to the root, taking the tightest limit observed at any level."The new
uv__get_cgroup2_current_memory()copies upstream libuv verbatim and readsmemory.currentonly at the leaf. It does not walk the hierarchy.The PR description states these two values will be subtracted to implement
process.availableMemory()(Node'suv_get_available_memory()semantics:cgroup limit - cgroup current usage). Pairing an ancestor-level limit with a leaf-level usage is not apples-to-apples, because in cgroup v2 an ancestor'smemory.currentaggregates the usage of all its descendants — including the leaf's siblings — and it is that aggregate that is enforced against the ancestor'smemory.max.Step-by-step example
Kubernetes pod with a pod-level memory limit and two containers (app + sidecar):
uv_get_constrained_memory()walks the hierarchy: leafcontainer-Ahasmemory.max = max, so it climbs topod-Xand returns 1Gi. ✅ Correct.uv_get_cgroup_current_memory()reads only leafcontainer-A/memory.currentand returns 450Mi.availableMemory = 1Gi - 450Mi ≈ 574Mi.memory.currentis already 900Mi, so only ~100Mi is allocatable before the pod is OOM-killed. The result overestimates headroom by ~5.7×.Why existing code doesn't prevent it
Upstream libuv is internally consistent because it reads both the limit and the current usage at the leaf only — so even if it misses an ancestor limit, the two numbers are at least from the same accounting scope. This fork enhanced the limit reader to be hierarchy-aware but the new usage reader is leaf-only, creating the asymmetry. Nothing on the Bun side can reconcile the two scalars after the fact, because the caller doesn't know which ancestor supplied the limit.
Impact
process.availableMemory()is advisory/best-effort, so this won't crash anything, and the single-container-per-pod case (very common) is unaffected because the leaf and the constraining ancestor coincide. But multi-container pods with a pod-level (rather than per-container) memory limit are a normal K8s deployment pattern (sidecars, service meshes), and there the reported headroom will be inflated by the siblings' usage — which somewhat undermines the PR's stated goal.How to fix
The cleanest fix is a single hierarchy walk that computes
min(memory.max_i - memory.current_i)across levels and exposes that directly as "available", since the two independent scalars can't be correctly combined by the caller. A lighter fix is to haveuv__get_cgroup2_current_memory()walk the same hierarchy and returnmemory.currentfrom the level whosememory.maxwas tightest (though that requires the limit reader to communicate which level that was). Either is reasonable as a follow-up; this PR is still a strict improvement over the status quo.