R-1125: the install page shows the check's own memory reading
gates / gates (push) Successful in 46s

The deploy page read /proc's host-wide figures (system.GetMemoryMB) while the install check reads the guest cap and
the installed apps' committed requests; on 9202 the page said 12,637 MB needed where the check counted 2,648. Both
now read stacks.InstallMemory. TestR1125_InstallMemoryIsTheChecksReading, red-proved.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-17 13:37:55 +02:00
parent 67a4b452e2
commit a4c0d546e5
3 changed files with 68 additions and 6 deletions
+16 -3
View File
@@ -1517,15 +1517,20 @@ func randomAlphanumeric(length int) (string, error) {
// household reads it in its own language). One value where there used to be a sentence plus a
// wrapper at each call site. An unreadable memory reading admits with a WARN, exactly as the deploy
// always has.
func (m *Manager) memoryVerdict(newReqMB, newLimitMB, releasedReqMB, releasedLimitMB int) (refusal error, warningKey string) {
reservedMB := m.cfg.System.ReservedMemoryMB
// InstallMemory is the ONE reading of the memory the install check judges: the guest's total, the reserve, and the
// installed apps' committed requests („used"). The install page (web deploy handler) shows exactly these, so the page
// and the check can never disagree (R-1125, 2026-10-17: the page used /proc's host-wide figures and blocked an app the
// check would accept, or the other way round). ok=false when the memory cannot be read. Pinned by
// TestR1125_InstallMemoryIsTheChecksReading.
func (m *Manager) InstallMemory() (totalMB, reservedMB, usedMB int, ok bool) {
reservedMB = m.cfg.System.ReservedMemoryMB
totalMB, usedMB, memErr := system.GetMemoryMB()
// F1: the controller container cannot read the guest's RAM cap from /proc (no lxcfs) or its own
// cgroup (the cap is on the LXC ancestor). Prefer the guest cap from the Docker daemon (runs in the
// LXC). And use the controller's OWN committed-memory accounting for "used" — accurate and cheap —
// rather than host /proc RSS, which is unobservable-per-guest and would otherwise make this guard
// either never fire (host total) or always fire (host used > guest cap).
if gt, ok := system.GuestMemTotalMB(); ok && gt > 0 {
if gt, gok := system.GuestMemTotalMB(); gok && gt > 0 {
totalMB = gt
memErr = nil
}
@@ -1534,6 +1539,14 @@ func (m *Manager) memoryVerdict(newReqMB, newLimitMB, releasedReqMB, releasedLim
}
if memErr != nil {
m.logger.Printf("[WARN] [stacks] Cannot read system memory: %v — skipping memory check", memErr)
return 0, 0, 0, false
}
return totalMB, reservedMB, usedMB, true
}
func (m *Manager) memoryVerdict(newReqMB, newLimitMB, releasedReqMB, releasedLimitMB int) (refusal error, warningKey string) {
totalMB, reservedMB, usedMB, ok := m.InstallMemory()
if !ok {
return nil, ""
}
usedMB -= releasedReqMB