Files
judge/runner/limiter_windows.go
Mikhail Kornilovich 329a7eb132
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
fix windows memory limiter: instead of malloc return null make job object IO completion port
2026-04-10 23:56:11 +03:00

186 lines
4.3 KiB
Go

//go:build windows
package runner
import (
"fmt"
"os/exec"
"unsafe"
"golang.org/x/sys/windows"
)
type windowsLimiter struct {
memLimit int64
job windows.Handle
iocp windows.Handle
peak int64
exceeded bool
}
func newLimiter(memLimit int64) limiter {
return &windowsLimiter{memLimit: memLimit}
}
const (
jobObjectExtendedLimitInformationClass = 9
jobObjectAssociateCompletionPortInformationClass = 7
jobObjectLimitProcessMemory = 0x00000100
jobObjectLimitKillOnJobClose = 0x00002000
jobObjectMsgProcessMemoryLimit = 9
jobObjectMsgJobMemoryLimit = 10
)
type ioCounters struct {
ReadOperationCount uint64
WriteOperationCount uint64
OtherOperationCount uint64
ReadTransferCount uint64
WriteTransferCount uint64
OtherTransferCount uint64
}
type jobObjectBasicLimitInformation struct {
PerProcessUserTimeLimit int64
PerJobUserTimeLimit int64
LimitFlags uint32
MinimumWorkingSetSize uintptr
MaximumWorkingSetSize uintptr
ActiveProcessLimit uint32
Affinity uintptr
PriorityClass uint32
SchedulingClass uint32
}
type jobObjectExtendedLimitInformation struct {
BasicLimitInformation jobObjectBasicLimitInformation
IoInfo ioCounters
ProcessMemoryLimit uintptr
JobMemoryLimit uintptr
PeakProcessMemoryUsed uintptr
PeakJobMemoryUsed uintptr
}
type jobObjectAssociateCompletionPort struct {
CompletionKey uintptr
CompletionPort windows.Handle
}
func (l *windowsLimiter) prepare(cmd *exec.Cmd) error {
if l.memLimit <= 0 {
return nil
}
job, err := windows.CreateJobObject(nil, nil)
if err != nil {
return fmt.Errorf("CreateJobObject: %w", err)
}
info := jobObjectExtendedLimitInformation{
BasicLimitInformation: jobObjectBasicLimitInformation{
LimitFlags: jobObjectLimitProcessMemory | jobObjectLimitKillOnJobClose,
},
ProcessMemoryLimit: uintptr(l.memLimit),
}
if _, err := windows.SetInformationJobObject(
job,
jobObjectExtendedLimitInformationClass,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
); err != nil {
windows.CloseHandle(job)
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
}
func (l *windowsLimiter) afterStart(cmd *exec.Cmd) error {
if l.job == 0 || cmd.Process == nil {
return nil
}
procHandle, err := windows.OpenProcess(
windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_QUERY_INFORMATION,
false,
uint32(cmd.Process.Pid),
)
if err != nil {
return fmt.Errorf("OpenProcess: %w", err)
}
defer windows.CloseHandle(procHandle)
if err := windows.AssignProcessToJobObject(l.job, procHandle); err != nil {
return fmt.Errorf("AssignProcessToJobObject: %w", err)
}
return nil
}
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
if err := windows.QueryInformationJobObject(
l.job,
jobObjectExtendedLimitInformationClass,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
&ret,
); err == nil {
l.peak = int64(info.PeakProcessMemoryUsed)
}
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
}
}
func cleanupCgroupRoot() {}