mirror of
https://github.com/criyle/go-judge.git
synced 2025-11-04 14:50:02 +08:00
168 lines
3.7 KiB
Go
168 lines
3.7 KiB
Go
package runner
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/criyle/go-sandbox/pkg/cgroup"
|
|
|
|
"github.com/criyle/go-judge/file"
|
|
"github.com/criyle/go-judge/file/memfile"
|
|
"github.com/criyle/go-judge/language"
|
|
"github.com/criyle/go-judge/pkg/runner"
|
|
"github.com/criyle/go-judge/types"
|
|
)
|
|
|
|
const maxOutput = 4 << 20 // 4M
|
|
|
|
func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskResult {
|
|
t := language.TypeExec
|
|
if task.Type == "compile" {
|
|
t = language.TypeCompile
|
|
}
|
|
param := r.Language.Get(task.Language, t)
|
|
|
|
// init input / output / error files
|
|
outputCollector := runner.PipeCollector{Name: "stdout", SizeLimit: maxOutput}
|
|
errorCollector := runner.PipeCollector{Name: "stderr", SizeLimit: maxOutput}
|
|
|
|
// calculate time limits
|
|
timeLimit := time.Duration(param.TimeLimit) * time.Millisecond
|
|
if task.TimeLimit > 0 {
|
|
timeLimit = time.Duration(task.TimeLimit) * time.Millisecond
|
|
}
|
|
wait := &waiter{timeLimit: timeLimit}
|
|
|
|
memoryLimit := param.MemoryLimit << 10
|
|
if task.MemoryLimit > 0 {
|
|
memoryLimit = task.MemoryLimit << 10
|
|
}
|
|
|
|
// copyin files
|
|
copyIn := make(map[string]file.File)
|
|
// copyin source code for compile or exec files for exec
|
|
if t == language.TypeCompile {
|
|
copyIn[param.SourceFileName] = memfile.New("source", []byte(task.Code))
|
|
} else {
|
|
for _, f := range task.ExecFiles {
|
|
copyIn[f.Name()] = f
|
|
}
|
|
}
|
|
|
|
// copyout files: If compile read compiled files
|
|
var copyOut []string
|
|
if task.Type == "compile" {
|
|
copyOut = param.CompiledFileNames
|
|
}
|
|
|
|
// build run specs
|
|
c := &runner.Cmd{
|
|
Args: param.Args,
|
|
Env: param.Env,
|
|
Files: []interface{}{task.InputFile, outputCollector, errorCollector},
|
|
MemoryLimit: memoryLimit,
|
|
PidLimit: param.ProcLimit,
|
|
CopyIn: copyIn,
|
|
CopyOut: copyOut,
|
|
Waiter: wait.Wait,
|
|
}
|
|
|
|
// run
|
|
rn := &runner.Runner{
|
|
CGBuilder: r.CgroupBuilder,
|
|
MasterPool: r.pool,
|
|
Cmds: []*runner.Cmd{c},
|
|
}
|
|
|
|
rt, err := rn.Run()
|
|
if err != nil {
|
|
return errResult(err.Error())
|
|
}
|
|
r0 := rt[0]
|
|
|
|
inputContent, err := task.InputFile.Content()
|
|
if err != nil {
|
|
return errResult(err.Error())
|
|
}
|
|
userOutput, err := r0.Files["stdout"].Content()
|
|
if err != nil {
|
|
return errResult(err.Error())
|
|
}
|
|
userError, err := r0.Files["stderr"].Content()
|
|
if err != nil {
|
|
return errResult(err.Error())
|
|
}
|
|
|
|
// compile copyout
|
|
var exec []file.File
|
|
if task.Type == "compile" {
|
|
for _, n := range param.CompiledFileNames {
|
|
exec = append(exec, r0.Files[n])
|
|
}
|
|
}
|
|
|
|
// TODO: diff
|
|
|
|
return &types.RunTaskResult{
|
|
Status: r0.Status.String(),
|
|
Time: uint64(r0.Time / time.Millisecond),
|
|
Memory: r0.Memory >> 10,
|
|
Input: inputContent,
|
|
UserOutput: userOutput,
|
|
UserError: userError,
|
|
ExecFiles: exec,
|
|
}
|
|
}
|
|
|
|
func errResult(err string) *types.RunTaskResult {
|
|
return &types.RunTaskResult{
|
|
Status: "JGF",
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
const minCPUPercent = 40 // 40%
|
|
const checkIntervalMS = 50
|
|
|
|
type waiter struct {
|
|
timeLimit time.Duration
|
|
}
|
|
|
|
func (w *waiter) Wait(done chan struct{}, cg *cgroup.CGroup) bool {
|
|
var lastCPUUsage uint64
|
|
var totalTime time.Duration
|
|
lastCheckTime := time.Now()
|
|
// wait task done (check each interval)
|
|
ticker := time.NewTicker(checkIntervalMS * time.Millisecond)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case now := <-ticker.C: // interval
|
|
cpuUsage, err := cg.CpuacctUsage()
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
cpuUsageDelta := time.Duration(cpuUsage - lastCPUUsage)
|
|
timeDelta := now.Sub(lastCheckTime)
|
|
|
|
totalTime += durationMax(cpuUsageDelta, timeDelta*minCPUPercent/100)
|
|
if totalTime > w.timeLimit {
|
|
return true
|
|
}
|
|
|
|
lastCheckTime = now
|
|
lastCPUUsage = cpuUsage
|
|
|
|
case <-done: // returned
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func durationMax(a, b time.Duration) time.Duration {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|