woj-server/internal/service/runner/run_judge.go

223 lines
6.6 KiB
Go
Raw Normal View History

2024-01-06 19:21:37 +08:00
package runner
import (
"fmt"
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/pkg/file"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
2024-01-27 17:37:27 +08:00
"github.com/opencontainers/runtime-spec/specs-go"
2024-01-06 19:21:37 +08:00
"go.uber.org/zap"
"os"
"path/filepath"
2024-01-27 17:37:27 +08:00
"strings"
2024-01-06 19:21:37 +08:00
"time"
)
func (s *service) SandboxArgsBuilder(meta *JudgeMeta, cLang *ConfigLanguage, id int) string {
2024-01-27 17:37:27 +08:00
var args []string
args = append(args, fmt.Sprintf("--memory_limit=%d", cLang.Runtime.Run.MemoryLimit))
args = append(args, fmt.Sprintf("--nproc_limit=%d", cLang.Runtime.Run.NProcLimit))
args = append(args, fmt.Sprintf("--time_limit=%d", cLang.Runtime.Run.TimeLimit))
args = append(args, fmt.Sprintf("--sandbox_template=%s", cLang.Lang))
2024-01-27 17:37:27 +08:00
args = append(args, fmt.Sprintf("--sandbox_action=ret"))
args = append(args, fmt.Sprintf("--uid=1000"))
args = append(args, fmt.Sprintf("--gid=1000"))
args = append(args, fmt.Sprintf("--file_input=/woj/problem/data/input/%d.input", id))
args = append(args, fmt.Sprintf("--file_output=/woj/user/%d.out.usr", id))
args = append(args, fmt.Sprintf("--file_info=/woj/user/%d.info", id))
args = append(args, fmt.Sprintf("--program=/woj/user/%s.out", meta.User))
2024-01-27 17:37:27 +08:00
return strings.Join(args, " ")
}
func (s *service) ProblemRun(meta *JudgeMeta, config *Config, cLang *ConfigLanguage) {
workDir := filepath.Join(UserDir, meta.User)
dataDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Version), "data", "input")
runtimeArgs := RuntimeArgs{
Image: ContainerImageRun,
// sh, woj_launcher:program, woj_launcher:killer, woj_launcher:stat
Pid: int64(cLang.Runtime.Run.NProcLimit + 4),
Memory: uint64(cLang.Runtime.Run.MemoryLimit * 1024 * 1024),
// woj-sandbox killer will add 1 more second, here we add 1 also
Timeout: time.Duration((cLang.Runtime.Run.TimeLimit+1000)/1000+1+1) * time.Second,
}
2024-01-06 19:21:37 +08:00
ids := make([]int, 0)
for _, task := range config.Tasks {
f := func(id int) func() error {
return func() error {
2024-01-06 19:21:37 +08:00
testCase := filepath.Join(dataDir, fmt.Sprintf("%d.input", id))
targetFile := filepath.Join(workDir, fmt.Sprintf("%s.out", meta.User))
2024-01-06 19:21:37 +08:00
ansFile := filepath.Join(workDir, fmt.Sprintf("%d.out.usr", id))
ifoFile := filepath.Join(workDir, fmt.Sprintf("%d.info", id))
2024-01-27 17:37:27 +08:00
args := &RunArgs{
Program: ProgramArgs{
Args: []string{
"sh", "-c",
"cd /woj/user && /woj/framework/scripts/woj_launcher " +
s.SandboxArgsBuilder(meta, cLang, id),
2024-01-27 17:37:27 +08:00
},
},
Runtime: runtimeArgs,
2024-01-27 17:37:27 +08:00
}
args.Runtime.Mount = []specs.Mount{
{
Source: testCase,
Destination: fmt.Sprintf("/woj/problem/data/input/%d.input", id),
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: targetFile,
Destination: fmt.Sprintf("/woj/user/%s.out", meta.User),
2024-01-27 17:37:27 +08:00
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: ansFile,
Destination: fmt.Sprintf("/woj/user/%d.out.usr", id),
Type: "bind",
Options: []string{"rbind"},
},
{
Source: ifoFile,
Destination: fmt.Sprintf("/woj/user/%d.info", id),
Type: "bind",
Options: []string{"rbind"},
},
}
2024-01-06 19:21:37 +08:00
err := utils.NewMust().
DoAny(func() error { return os.Remove(ansFile) }).
DoAny(func() error { return os.Remove(ifoFile) }).
Do(func() error { return file.TouchErr(ansFile) }).
Do(func() error { return file.TouchErr(ifoFile) }).
2024-01-27 17:37:27 +08:00
Do(func() error { return s.ContainerRun(args) }).
2024-01-06 19:21:37 +08:00
Done()
if err != nil {
s.log.Info("[run] run failed", zap.Error(err), zap.Any("meta", *meta))
2024-01-06 19:21:37 +08:00
}
return err
2024-01-06 19:21:37 +08:00
}
}(task.Id)
id := s.pool.AddTask(f)
ids = append(ids, id)
}
for _, id := range ids {
_ = s.pool.WaitForTask(id)
2024-01-06 19:21:37 +08:00
}
}
func (s *service) ProblemJudge(meta *JudgeMeta, config *Config, cLang *ConfigLanguage) {
workDir := filepath.Join(UserDir, meta.User)
dataDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Version), "data")
judgeDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Version), "judge")
script := cLang.JudgeScript()
runtimeArgs := RuntimeArgs{
Image: ContainerImageFull,
Pid: int64(cLang.Runtime.Check.NProcLimit + 2), // bash + make
Memory: uint64(cLang.Runtime.Check.MemoryLimit * 1024 * 1024),
Timeout: time.Duration((cLang.Runtime.Check.TimeLimit+1000)/1000) * time.Second,
}
2024-01-06 19:21:37 +08:00
ids := make([]int, 0)
for _, task := range config.Tasks {
f := func(id int) func() error {
return func() error {
2024-01-06 19:21:37 +08:00
ansFile := filepath.Join(workDir, fmt.Sprintf("%d.out.usr", id))
jdgFile := filepath.Join(workDir, fmt.Sprintf("%d.judge", id))
2024-01-27 17:37:27 +08:00
args := &RunArgs{
Program: ProgramArgs{
Args: []string{
"sh", "-c",
fmt.Sprintf("cd /woj/user && make -f %s judge", script),
2024-01-27 17:37:27 +08:00
},
Env: []string{
fmt.Sprintf("TEST_NUM=%d", id),
fmt.Sprintf("CMP=%s", cLang.Judge.Cmp),
2024-01-27 17:37:27 +08:00
},
},
Runtime: runtimeArgs,
2024-01-27 17:37:27 +08:00
}
args.Runtime.Mount = []specs.Mount{
{
Source: judgeDir,
Destination: "/woj/problem/judge",
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: dataDir,
Destination: "/woj/problem/data",
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: ansFile,
Destination: fmt.Sprintf("/woj/user/%d.out.usr", id),
Type: "bind",
Options: []string{"rbind"},
},
{
Source: jdgFile,
Destination: fmt.Sprintf("/woj/user/%d.judge", id),
Type: "bind",
Options: []string{"rbind"},
},
}
2024-01-06 19:21:37 +08:00
err := utils.NewMust().
DoAny(func() error { return os.Remove(jdgFile) }).
Do(func() error { return file.TouchErr(jdgFile) }).
2024-01-27 17:37:27 +08:00
Do(func() error { return s.ContainerRun(args) }).
2024-01-06 19:21:37 +08:00
Done()
if err != nil {
s.log.Info("[judge] judge failed", zap.Error(err), zap.Any("meta", *meta))
2024-01-06 19:21:37 +08:00
}
return err
2024-01-06 19:21:37 +08:00
}
}(task.Id)
id := s.pool.AddTask(f)
ids = append(ids, id)
}
for _, id := range ids {
_ = s.pool.WaitForTask(id)
2024-01-06 19:21:37 +08:00
}
}
func (s *service) RunAndJudge(meta *JudgeMeta) (*JudgeStatus, int32, e.Status) {
2024-01-06 19:21:37 +08:00
// 1. ensure problem/user exists
status := s.ValidatePath(meta)
2024-01-06 19:21:37 +08:00
if status != e.Success {
return &JudgeStatus{Message: "check failed"}, 0, status
2024-01-06 19:21:37 +08:00
}
// 2. config
config, cLang, status := s.GetConfig(meta, true)
if status != e.Success {
return &JudgeStatus{Message: status.String()}, 0, status
2024-01-06 19:21:37 +08:00
}
// 3. run user program
s.ProblemRun(meta, config, cLang)
2024-01-06 19:21:37 +08:00
// 4. run judge
s.ProblemJudge(meta, config, cLang)
2024-01-06 19:21:37 +08:00
// 5. check result
result, pts := s.CheckResults(meta, config, cLang)
2024-01-06 19:21:37 +08:00
return result, pts, e.Success
}