fix: GOMAXPROCS auto-detection failure - #98
Conversation
Adding I thought this shouldn't happen that's why didn't touched landlock defaults.
If we want runtimes other than Go, to be able to read their own cgroup limits then we can introduce a new Let me know if this makes sense. |
Right, but can't we expose just the subtree for this PID? |
Yeah, but it cannot be done inside Landlock's setup phase as it has no knowledge of the task's runtime The task-specific So If this feels right i will start making the changes. |
|
Should we even bother making this a configuration knob? What's the harm in exposing it (read-only!) to the task by default? edit but otherwise, yeah, that seems like the right approach |
No, we don't need any config knobs here; this can be tied to Exposing Go 1.24 and below never read cgroup so env-var injection is the fix for this(already present), and it gives Since Go checks the env-var first and skips all cgroup reads if set, the injection always wins for Go, the cgroup unveil is purely for Java and other runtimes. If we want to drop the support for Go 1.24 we can remove this block of code?
|
Go 1.24 is out of support itself. But I also have no idea why we'd be special-casing environment variables for Go in this driver at all. What makes Go special? Are there well-known environment variables that Haskell can use to set threadpools too? What about Luau? 😁 I think you get my point: we should be supplying a generic facility that well-behaved runtimes can use (cgroups), not special-casing environment variables for particular runtimes. |
|
Agreed. We should provide generic cgroup facility for all runtimes, rather than runtime-specific handling. Removed the GOMAXPROCS env var injection. |
Fixes: #82
Summary
exec2 tasks saw
GOMAXPROCSequal to the total host CPU count rather than their allocated CPU quota. This caused over-threading, excessive cgroup throttling, and degraded throughput for any Go binary scheduled with a fractional CPU share (e.g. cpu = 500 on a 32-core host). The driver now injects the correct value before the task process starts.Root cause
The Go runtime auto-detects
GOMAXPROCSat startup by reading two kernel interfaces. Both are silently blocked by Landlock inside an exec2 task. The Landlock allow-list even withunveil_defaults = truedoes not include/sys/fs/cgroupor/sys/devices/system/cpu. So Go silently falls through to the host core count every time.Fix
The fix injects
GOMAXPROCSdirectly into the task environment inStartTask(), derived from the same bandwidth value already computed for writing to cpu.max. The formula uses integer ceiling division, so fractional shares round up to the next whole thread and reserved-core allocations (cores = N) produce an exact result because the per-core MHz cancels out.The injection is skipped if the operator has already set
GOMAXPROCSexplicitly in the job's env block. Because the value arrives as an environment variable, the Go runtime reads it via os.Getenv during runtime.init with no file I/O, and the kernel-enforced cgroup limit and the Go thread pool size are now always derived from the same source.Why integer ceiling, not float64?
bandwidth is uint64. Converting to
float64loses precision above 2^53. Integer ceiling division (n + d - 1) / d is exact for all representable values.Data flow — how the value reaches the process
Job spec examples
Scenario A — fractional share (cpu = N MHz)
Scenario B — whole core reservation (cores = N)
Scenario C — verify inside the task
Testing
Details
Result before:
Result After:
Before Result:
After Result:
Changes to Security Controls
Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.