add memory limit

This commit is contained in:
2026-04-10 18:45:40 +03:00
parent 86b8d83643
commit a977d4d9f5
13 changed files with 662 additions and 37 deletions

View File

@@ -169,8 +169,19 @@ func (p *Parser) parseFile() (*File, error) {
}
f.Timeout = d
case "memory_limit":
p.advance()
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
return nil, err
}
n, err := p.parseSize()
if err != nil {
return nil, err
}
f.MemoryLimit = n
case "group":
g, err := p.parseGroup(f.Timeout)
g, err := p.parseGroup(f.Timeout, f.MemoryLimit)
if err != nil {
return nil, err
}
@@ -202,7 +213,7 @@ func (p *Parser) validateWeights(f *File) error {
return nil
}
func (p *Parser) parseGroup(defaultTimeout time.Duration) (*Group, error) {
func (p *Parser) parseGroup(defaultTimeout time.Duration, defaultMemory int64) (*Group, error) {
if err := p.expectIdent("group"); err != nil {
return nil, err
}
@@ -221,10 +232,11 @@ func (p *Parser) parseGroup(defaultTimeout time.Duration) (*Group, error) {
}
g := &Group{
Name: name.Value,
Timeout: defaultTimeout,
Env: map[string]string{},
Scoring: ScoringPartial,
Name: name.Value,
Timeout: defaultTimeout,
MemoryLimit: defaultMemory,
Env: map[string]string{},
Scoring: ScoringPartial,
}
for !p.isRBrace() {
@@ -256,6 +268,17 @@ func (p *Parser) parseGroup(defaultTimeout time.Duration) (*Group, error) {
}
g.Timeout = d
case "memory_limit":
p.advance()
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
return nil, err
}
n, err := p.parseSize()
if err != nil {
return nil, err
}
g.MemoryLimit = n
case "scoring":
p.advance()
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
@@ -307,7 +330,7 @@ func (p *Parser) parseGroup(defaultTimeout time.Duration) (*Group, error) {
g.Wrapper = s.Value
case "test":
test, err := p.parseTest(g.Timeout)
test, err := p.parseTest(g.Timeout, g.MemoryLimit)
if err != nil {
return nil, err
}
@@ -331,7 +354,7 @@ func (p *Parser) parseGroup(defaultTimeout time.Duration) (*Group, error) {
return g, nil
}
func (p *Parser) parseTest(defaultTimeout time.Duration) (*Test, error) {
func (p *Parser) parseTest(defaultTimeout time.Duration, defaultMemory int64) (*Test, error) {
if err := p.expectIdent("test"); err != nil {
return nil, err
}
@@ -351,14 +374,15 @@ func (p *Parser) parseTest(defaultTimeout time.Duration) (*Test, error) {
zero := 0
test := &Test{
Name: name.Value,
Timeout: defaultTimeout,
Env: map[string]string{},
InFiles: map[string]string{},
OutFiles: map[string]string{},
ExitCode: &zero,
Stdout: NoMatcher{},
Stderr: NoMatcher{},
Name: name.Value,
Timeout: defaultTimeout,
MemoryLimit: defaultMemory,
Env: map[string]string{},
InFiles: map[string]string{},
OutFiles: map[string]string{},
ExitCode: &zero,
Stdout: NoMatcher{},
Stderr: NoMatcher{},
}
for !p.isRBrace() {
@@ -428,6 +452,17 @@ func (p *Parser) parseTest(defaultTimeout time.Duration) (*Test, error) {
}
test.Timeout = d
case "memory_limit":
p.advance()
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
return nil, err
}
n, err := p.parseSize()
if err != nil {
return nil, err
}
test.MemoryLimit = n
case "wrapper":
p.advance()
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
@@ -680,6 +715,59 @@ func (p *Parser) parseInt() (int, error) {
return n, nil
}
// parseSize accepts either a TOKEN_SIZE (e.g. "256MB", "1GiB", "512K") or a bare
// TOKEN_INT interpreted as bytes. MiB/MB are both 1024² — we use IEC semantics.
func (p *Parser) parseSize() (int64, error) {
t := p.peek()
switch t.Type {
case TOKEN_SIZE:
p.advance()
return parseSizeLiteral(t.Value, t.Line, t.Col)
case TOKEN_INT:
p.advance()
n, err := strconv.ParseInt(t.Value, 10, 64)
if err != nil {
return 0, fmt.Errorf("%d:%d: invalid size %q", t.Line, t.Col, t.Value)
}
return n, nil
default:
return 0, fmt.Errorf("%d:%d: expected size (e.g. 256MB, 1GiB), got %s %q", t.Line, t.Col, t.Type, t.Value)
}
}
func parseSizeLiteral(s string, line, col int) (int64, error) {
i := 0
for i < len(s) && (s[i] >= '0' && s[i] <= '9') {
i++
}
if i == 0 {
return 0, fmt.Errorf("%d:%d: invalid size %q", line, col, s)
}
numPart := s[:i]
unit := s[i:]
n, err := strconv.ParseInt(numPart, 10, 64)
if err != nil {
return 0, fmt.Errorf("%d:%d: invalid size %q", line, col, s)
}
var mult int64
switch unit {
case "", "B":
mult = 1
case "K", "KB", "KiB":
mult = 1024
case "M", "MB", "MiB":
mult = 1024 * 1024
case "G", "GB", "GiB":
mult = 1024 * 1024 * 1024
default:
return 0, fmt.Errorf("%d:%d: unknown size unit %q (use B/K/M/G or KiB/MiB/GiB)", line, col, unit)
}
if n < 0 {
return 0, fmt.Errorf("%d:%d: size must be non-negative", line, col)
}
return n * mult, nil
}
func (p *Parser) parseDuration() (time.Duration, error) {
t := p.peek()
if t.Type != TOKEN_DURATION {