fix windows memory limiter: instead of malloc return null make job object IO completion port
Some checks failed
memory-limit / Build judge (push) Successful in 11s
memory-limit / Linux / clang (push) Successful in 9s
memory-limit / Linux / gcc (push) Successful in 9s
memory-limit / Windows / clang (push) Failing after 11s
memory-limit / Windows / msvc (push) Failing after 13s

This commit is contained in:
2026-04-10 23:56:11 +03:00
parent 319ac8f73d
commit 329a7eb132

View File

@@ -13,6 +13,7 @@ import (
type windowsLimiter struct {
memLimit int64
job windows.Handle
iocp windows.Handle
peak int64
exceeded bool
}
@@ -23,8 +24,13 @@ func newLimiter(memLimit int64) limiter {
const (
jobObjectExtendedLimitInformationClass = 9
jobObjectAssociateCompletionPortInformationClass = 7
jobObjectLimitProcessMemory = 0x00000100
jobObjectLimitKillOnJobClose = 0x00002000
jobObjectMsgProcessMemoryLimit = 9
jobObjectMsgJobMemoryLimit = 10
)
type ioCounters struct {
@@ -57,6 +63,11 @@ type jobObjectExtendedLimitInformation struct {
PeakJobMemoryUsed uintptr
}
type jobObjectAssociateCompletionPort struct {
CompletionKey uintptr
CompletionPort windows.Handle
}
func (l *windowsLimiter) prepare(cmd *exec.Cmd) error {
if l.memLimit <= 0 {
return nil
@@ -78,9 +89,31 @@ func (l *windowsLimiter) prepare(cmd *exec.Cmd) error {
uint32(unsafe.Sizeof(info)),
); err != nil {
windows.CloseHandle(job)
return fmt.Errorf("SetInformationJobObject: %w", err)
return fmt.Errorf("SetInformationJobObject(extended): %w", err)
}
iocp, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 1)
if err != nil {
windows.CloseHandle(job)
return fmt.Errorf("CreateIoCompletionPort: %w", err)
}
assoc := jobObjectAssociateCompletionPort{
CompletionKey: uintptr(job),
CompletionPort: iocp,
}
if _, err := windows.SetInformationJobObject(
job,
jobObjectAssociateCompletionPortInformationClass,
uintptr(unsafe.Pointer(&assoc)),
uint32(unsafe.Sizeof(assoc)),
); err != nil {
windows.CloseHandle(iocp)
windows.CloseHandle(job)
return fmt.Errorf("SetInformationJobObject(iocp): %w", err)
}
l.job = job
l.iocp = iocp
return nil
}
@@ -107,26 +140,42 @@ func (l *windowsLimiter) collect() limitStats {
if l.job == 0 {
return limitStats{}
}
if l.iocp != 0 {
for {
var bytes uint32
var key uintptr
var overlapped *windows.Overlapped
err := windows.GetQueuedCompletionStatus(l.iocp, &bytes, &key, &overlapped, 0)
if err != nil {
break
}
if bytes == jobObjectMsgProcessMemoryLimit || bytes == jobObjectMsgJobMemoryLimit {
l.exceeded = true
}
}
}
var info jobObjectExtendedLimitInformation
var ret uint32
err := windows.QueryInformationJobObject(
if err := windows.QueryInformationJobObject(
l.job,
jobObjectExtendedLimitInformationClass,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
&ret,
)
if err != nil {
return limitStats{}
}
); err == nil {
l.peak = int64(info.PeakProcessMemoryUsed)
if l.memLimit > 0 && l.peak >= l.memLimit {
l.exceeded = true
}
return limitStats{PeakMemory: l.peak, MemoryExceeded: l.exceeded}
}
func (l *windowsLimiter) cleanup() {
if l.iocp != 0 {
windows.CloseHandle(l.iocp)
l.iocp = 0
}
if l.job != 0 {
windows.CloseHandle(l.job)
l.job = 0