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 { type windowsLimiter struct {
memLimit int64 memLimit int64
job windows.Handle job windows.Handle
iocp windows.Handle
peak int64 peak int64
exceeded bool exceeded bool
} }
@@ -22,9 +23,14 @@ func newLimiter(memLimit int64) limiter {
} }
const ( const (
jobObjectExtendedLimitInformationClass = 9 jobObjectExtendedLimitInformationClass = 9
jobObjectLimitProcessMemory = 0x00000100 jobObjectAssociateCompletionPortInformationClass = 7
jobObjectLimitKillOnJobClose = 0x00002000
jobObjectLimitProcessMemory = 0x00000100
jobObjectLimitKillOnJobClose = 0x00002000
jobObjectMsgProcessMemoryLimit = 9
jobObjectMsgJobMemoryLimit = 10
) )
type ioCounters struct { type ioCounters struct {
@@ -57,6 +63,11 @@ type jobObjectExtendedLimitInformation struct {
PeakJobMemoryUsed uintptr PeakJobMemoryUsed uintptr
} }
type jobObjectAssociateCompletionPort struct {
CompletionKey uintptr
CompletionPort windows.Handle
}
func (l *windowsLimiter) prepare(cmd *exec.Cmd) error { func (l *windowsLimiter) prepare(cmd *exec.Cmd) error {
if l.memLimit <= 0 { if l.memLimit <= 0 {
return nil return nil
@@ -78,9 +89,31 @@ func (l *windowsLimiter) prepare(cmd *exec.Cmd) error {
uint32(unsafe.Sizeof(info)), uint32(unsafe.Sizeof(info)),
); err != nil { ); err != nil {
windows.CloseHandle(job) 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.job = job
l.iocp = iocp
return nil return nil
} }
@@ -107,26 +140,42 @@ func (l *windowsLimiter) collect() limitStats {
if l.job == 0 { if l.job == 0 {
return limitStats{} 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 info jobObjectExtendedLimitInformation
var ret uint32 var ret uint32
err := windows.QueryInformationJobObject( if err := windows.QueryInformationJobObject(
l.job, l.job,
jobObjectExtendedLimitInformationClass, jobObjectExtendedLimitInformationClass,
uintptr(unsafe.Pointer(&info)), uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)), uint32(unsafe.Sizeof(info)),
&ret, &ret,
) ); err == nil {
if err != nil { l.peak = int64(info.PeakProcessMemoryUsed)
return limitStats{}
}
l.peak = int64(info.PeakProcessMemoryUsed)
if l.memLimit > 0 && l.peak >= l.memLimit {
l.exceeded = true
} }
return limitStats{PeakMemory: l.peak, MemoryExceeded: l.exceeded} return limitStats{PeakMemory: l.peak, MemoryExceeded: l.exceeded}
} }
func (l *windowsLimiter) cleanup() { func (l *windowsLimiter) cleanup() {
if l.iocp != 0 {
windows.CloseHandle(l.iocp)
l.iocp = 0
}
if l.job != 0 { if l.job != 0 {
windows.CloseHandle(l.job) windows.CloseHandle(l.job)
l.job = 0 l.job = 0