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"
|
|
|
|
)
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
func (s *service) SandboxArgsBuilder(meta *JudgeMeta, cLang *ConfigLanguage, id int) string {
|
2024-01-27 17:37:27 +08:00
|
|
|
var args []string
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
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))
|
2024-01-27 23:07:14 +08:00
|
|
|
args = append(args, fmt.Sprintf("--program=/woj/user/%s.out", meta.User))
|
2024-01-27 17:37:27 +08:00
|
|
|
|
|
|
|
return strings.Join(args, " ")
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
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 {
|
2024-01-28 18:16:04 +08:00
|
|
|
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))
|
2024-01-27 23:07:14 +08:00
|
|
|
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 " +
|
2024-01-27 23:07:14 +08:00
|
|
|
s.SandboxArgsBuilder(meta, cLang, id),
|
2024-01-27 17:37:27 +08:00
|
|
|
},
|
|
|
|
},
|
2024-01-27 23:07:14 +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,
|
2024-01-27 23:07:14 +08:00
|
|
|
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 {
|
2024-01-27 23:07:14 +08:00
|
|
|
s.log.Info("[run] run failed", zap.Error(err), zap.Any("meta", *meta))
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
2024-01-28 18:16:04 +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 {
|
2024-01-28 18:16:04 +08:00
|
|
|
_ = s.pool.WaitForTask(id)
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +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 {
|
2024-01-28 18:16:04 +08:00
|
|
|
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",
|
2024-01-27 23:07:14 +08:00
|
|
|
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),
|
2024-01-27 23:07:14 +08:00
|
|
|
fmt.Sprintf("CMP=%s", cLang.Judge.Cmp),
|
2024-01-27 17:37:27 +08:00
|
|
|
},
|
|
|
|
},
|
2024-01-27 23:07:14 +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 {
|
2024-01-27 23:07:14 +08:00
|
|
|
s.log.Info("[judge] judge failed", zap.Error(err), zap.Any("meta", *meta))
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
2024-01-28 18:16:04 +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 {
|
2024-01-28 18:16:04 +08:00
|
|
|
_ = s.pool.WaitForTask(id)
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:07:14 +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
|
2024-01-27 23:07:14 +08:00
|
|
|
status := s.ValidatePath(meta)
|
2024-01-06 19:21:37 +08:00
|
|
|
if status != e.Success {
|
2024-01-27 23:07:14 +08:00
|
|
|
return &JudgeStatus{Message: "check failed"}, 0, status
|
2024-01-06 19:21:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 2. config
|
2024-01-27 23:07:14 +08:00
|
|
|
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
|
2024-01-27 23:07:14 +08:00
|
|
|
s.ProblemRun(meta, config, cLang)
|
2024-01-06 19:21:37 +08:00
|
|
|
|
2024-01-27 23:07:14 +08:00
|
|
|
// 4. run judge
|
|
|
|
s.ProblemJudge(meta, config, cLang)
|
2024-01-06 19:21:37 +08:00
|
|
|
|
|
|
|
// 5. check result
|
2024-01-27 23:07:14 +08:00
|
|
|
result, pts := s.CheckResults(meta, config, cLang)
|
2024-01-06 19:21:37 +08:00
|
|
|
|
|
|
|
return result, pts, e.Success
|
|
|
|
}
|