diff --git a/client/interface.go b/client/interface.go index 27a5ba8..935b748 100644 --- a/client/interface.go +++ b/client/interface.go @@ -13,5 +13,5 @@ type Task interface { // it should sent received work through go channel (have background goroutine(s)) type Client interface { // C should return channel to receive works - C() <-chan types.JudgeTask + C() <-chan Task } diff --git a/go.mod b/go.mod index 5daba35..eb83c45 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/criyle/go-judge go 1.12 -require github.com/criyle/go-sandbox v0.0.0-20190902024918-1df3228aec4d +require ( + github.com/criyle/go-sandbox v0.0.0-20190906040622-be668a5e7548 + golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03 // indirect +) diff --git a/go.sum b/go.sum index 8964ca0..4b186b5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -github.com/criyle/go-sandbox v0.0.0-20190902024918-1df3228aec4d h1:JpdxvAKkHYskHPGuVm7hPnndOBuCygkOxc1p8V1rPeI= -github.com/criyle/go-sandbox v0.0.0-20190902024918-1df3228aec4d/go.mod h1:RZ6jui4DXDFZHojTt5pNxr6FXUbCcW+TLITDSANJllk= +github.com/criyle/go-sandbox v0.0.0-20190906040622-be668a5e7548 h1:5JzsTB6UUHCoZkgjBmU68WYhIdbs189xtkUHJs+qu4c= +github.com/criyle/go-sandbox v0.0.0-20190906040622-be668a5e7548/go.mod h1:gkn5TWck4A8kKs8xX0q1+yLA0WnV2e7ECm/OmYigDCI= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 h1:7z820YPX9pxWR59qM7BE5+fglp4D/mKqAwCvGt11b+8= -golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904005037-43c01164e931/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03 h1:b3JiLYVaG9kHjTcOQIoUh978YMCO7oVTQQBLudU47zY= +golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/judger/judger.go b/judger/judger.go index b6b76ca..f82b44c 100644 --- a/judger/judger.go +++ b/judger/judger.go @@ -1 +1,14 @@ package judger + +import ( + "github.com/criyle/go-judge/client" + "github.com/criyle/go-judge/problem" + "github.com/criyle/go-judge/taskqueue" +) + +// Judger receives task from client and translate to task for runner +type Judger struct { + client.Client + taskqueue.Queue + problem.Builder +} diff --git a/judger/loop.go b/judger/loop.go new file mode 100644 index 0000000..536b6e4 --- /dev/null +++ b/judger/loop.go @@ -0,0 +1,138 @@ +package judger + +import ( + "fmt" + "sync/atomic" + + "github.com/criyle/go-judge/client" + "github.com/criyle/go-judge/file" + "github.com/criyle/go-judge/types" +) + +// Loop fetch judge task from client and report results +// in a infinite loop +func (j *Judger) Loop(done <-chan struct{}) { + c := j.Client.C() +loop: + for { + select { + case t := <-c: + rt := j.run(done, t) + t.Finish(rt) + select { + case <-done: + break loop + default: + } + case <-done: + break loop + } + } +} + +func (j *Judger) run(done <-chan struct{}, t client.Task) types.JudgeResult { + var result types.JudgeResult + errResult := func(err error) types.JudgeResult { + result.Error = err.Error() + return result + } + + p := t.Param() + pconf, err := j.Build(p.TestData) + if err != nil { + return errResult(err) + } + + // compile + compileRet := make(chan types.RunTaskResult) + err = j.Enqueue(types.RunTask{ + Type: "compile", + Language: p.Language, + Code: p.Code, + ExtraFiles: pconf.ExtraFiles, + }, compileRet) + if err != nil { + return errResult(err) + } + compileTaskResult := <-compileRet + if compileTaskResult.Error != "" { + return errResult(err) + } + execFiles := compileTaskResult.ExecFiles + + // run + subTaskResult := make(chan types.JudgeSubTaskResult, len(pconf.Subtasks)) + pj := problemJudger{ + Judger: j, + ProblemConfig: &pconf, + Task: t, + JudgeTask: p, + total: count(&pconf), + } + for _, s := range pconf.Subtasks { + s := &s + go func() { + subTaskResult <- pj.runSubtask(done, execFiles, s) + }() + } + for range pconf.Subtasks { + result.SubTasks = append(result.SubTasks, <-subTaskResult) + } + return result +} + +type problemJudger struct { + *Judger + *types.ProblemConfig + *types.JudgeTask + client.Task + count int32 + total int32 +} + +func (pj *problemJudger) runSubtask(done <-chan struct{}, exec []file.File, s *types.SubTask) types.JudgeSubTaskResult { + var result types.JudgeSubTaskResult + caseResult := make(chan types.RunTaskResult, len(s.Cases)) + for _, c := range s.Cases { + pj.Enqueue(types.RunTask{ + Type: pj.ProblemConfig.Type, + Language: pj.Language, + TimeLimit: pj.TileLimit, + MemoryLimit: pj.MemoryLimit, + Executables: exec, + InputFile: c.Input, + AnswerFile: c.Answer, + }, caseResult) + } + for range s.Cases { + rt := <-caseResult + result.Cases = append(result.Cases, types.JudgeCaseResult{ + Status: rt.Status, + ScoreRate: rt.ScoringRate, + Error: rt.Error, + Time: rt.Time, + Memory: rt.Memory, + Input: rt.Input, + Answer: rt.Answer, + UserOutput: rt.UserOutput, + UserError: rt.UserError, + SpjOutput: rt.SpjOutput, + }) + result.Score += rt.ScoringRate + // report prograss + atomic.AddInt32(&pj.count, 1) + pj.Progress(types.JudgeProgress{ + Message: fmt.Sprintf("%d/%d", pj.count, pj.total), + }) + } + return result +} + +// count counts total number of cases +func count(pconf *types.ProblemConfig) int32 { + var count int32 + for _, s := range pconf.Subtasks { + count += int32(len(s.Cases)) + } + return count +} diff --git a/language/interface.go b/language/interface.go index 3af6c13..20f7091 100644 --- a/language/interface.go +++ b/language/interface.go @@ -1,15 +1,26 @@ package language +// Type defines compile / exec +type Type int + +// Defines the exec type +const ( + TypeCompile Type = iota + 1 + TypeExec +) + // Language defines the way to run program type Language interface { - Get(string, string) ExecParam // Get execparam for specific language and type (compile / run) + Get(string, Type) ExecParam // Get execparam for specific language and type (compile / run) } // ExecParam defines specs to compile / run program type ExecParam struct { - SourceFileName string - Args []string - CompiledFileNames []string + Args []string + + // Compile + SourceFileName string // put code when compile + CompiledFileNames []string // exec files // limits TimeLimit uint64 diff --git a/pkg/diff/diff.go b/pkg/diff/diff.go new file mode 100644 index 0000000..f8689a2 --- /dev/null +++ b/pkg/diff/diff.go @@ -0,0 +1 @@ +package diff diff --git a/problem/interface.go b/problem/interface.go new file mode 100644 index 0000000..c852b62 --- /dev/null +++ b/problem/interface.go @@ -0,0 +1,11 @@ +package problem + +import ( + "github.com/criyle/go-judge/file" + "github.com/criyle/go-judge/types" +) + +// Builder builds problem specs from file +type Builder interface { + Build([]file.File) (types.ProblemConfig, error) +} diff --git a/runner/pool.go b/runner/pool.go index cbface2..47f1ed5 100644 --- a/runner/pool.go +++ b/runner/pool.go @@ -3,11 +3,11 @@ package runner import ( "sync/atomic" - "github.com/criyle/go-sandbox/deamon" + "github.com/criyle/go-sandbox/daemon" ) type pool struct { - queue chan *deamon.Master + queue chan *daemon.Master count int32 root string } @@ -16,26 +16,26 @@ const maxPoolSize = 64 func newPool(root string) *pool { return &pool{ - queue: make(chan *deamon.Master, maxPoolSize), + queue: make(chan *daemon.Master, maxPoolSize), root: root, } } -func (p *pool) Get() (*deamon.Master, error) { +func (p *pool) Get() (*daemon.Master, error) { select { case m := <-p.queue: return m, nil default: } atomic.AddInt32(&p.count, 1) - return deamon.New(p.root) + return daemon.New(p.root) } -func (p *pool) Put(master *deamon.Master) { +func (p *pool) Put(master *daemon.Master) { p.queue <- master } -func (p *pool) Destroy(master *deamon.Master) { +func (p *pool) Destroy(master *daemon.Master) { master.Destroy() atomic.AddInt32(&p.count, -1) } diff --git a/runner/run.go b/runner/run.go index 16b88c5..8d31f54 100644 --- a/runner/run.go +++ b/runner/run.go @@ -1,13 +1,17 @@ package runner import ( + "io/ioutil" "time" - "github.com/criyle/go-sandbox/deamon" + "github.com/criyle/go-sandbox/daemon" "github.com/criyle/go-sandbox/pkg/cgroup" "github.com/criyle/go-sandbox/pkg/pipe" stypes "github.com/criyle/go-sandbox/types" + "github.com/criyle/go-judge/file" + "github.com/criyle/go-judge/file/memfile" + "github.com/criyle/go-judge/language" "github.com/criyle/go-judge/types" ) @@ -19,7 +23,11 @@ const checkIntervalMS = 50 var env = []string{"PATH=/usr/local/bin:/usr/bin:/bin"} func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskResult { - param := r.Language.Get(task.Language, task.Type) + t := language.TypeExec + if task.Type == "compile" { + t = language.TypeCompile + } + param := r.Language.Get(task.Language, t) // init input / output / error files inputFile, err := task.InputFile.Open() @@ -47,10 +55,10 @@ func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskRe } defer cg.Destroy() - // get deamon runner + // get daemon runner m, err := r.pool.Get() if err != nil { - return errResult("failed to get deamon instance") + return errResult("failed to get daemon instance") } defer r.pool.Put(m) @@ -64,7 +72,7 @@ func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskRe cg.SetPidsMax(param.ProcLimit) // set running parameters - execParam := deamon.ExecveParam{ + execParam := daemon.ExecveParam{ Args: param.Args, Envv: env, Fds: []uintptr{inputFile.Fd(), outputPipe.W.Fd(), errorPipe.W.Fd()}, @@ -72,7 +80,7 @@ func (r *Runner) run(done <-chan struct{}, task *types.RunTask) *types.RunTaskRe } // cancellable signal channel - cancelC := newCancelableChannel() + cancelC := newCancellableChannel() defer cancelC.cancel() // start the process @@ -169,6 +177,27 @@ loop: } inputContent, _ := task.InputFile.Content() + + // If compile read compiled files + var exec []file.File + if task.Type == "compile" { + for _, fn := range param.CompiledFileNames { + f, err := m.Open(fn) + if err != nil { + return errResult(err.Error()) + } + defer f.Close() + + c, err := ioutil.ReadAll(f) + if err != nil { + return errResult(err.Error()) + } + exec = append(exec, memfile.New(fn, c)) + } + } + + // TODO: diff + return &types.RunTaskResult{ Status: status, Time: cpuUsage / uint64(time.Millisecond), @@ -176,6 +205,7 @@ loop: Input: inputContent, UserOutput: outputPipe.Buffer.Bytes(), UserError: errorPipe.Buffer.Bytes(), + ExecFiles: exec, } } @@ -186,18 +216,18 @@ func errResult(err string) *types.RunTaskResult { } } -type cancelableChannel struct { +type cancellableChannel struct { Done chan struct{} canceled bool } -func newCancelableChannel() *cancelableChannel { - return &cancelableChannel{ +func newCancellableChannel() *cancellableChannel { + return &cancellableChannel{ Done: make(chan struct{}), } } -func (c *cancelableChannel) cancel() { +func (c *cancellableChannel) cancel() { if !c.canceled { close(c.Done) c.canceled = true diff --git a/taskqueue/channel/channel.go b/taskqueue/channel/channel.go new file mode 100644 index 0000000..219c566 --- /dev/null +++ b/taskqueue/channel/channel.go @@ -0,0 +1,50 @@ +package channel + +import ( + "github.com/criyle/go-judge/taskqueue" + "github.com/criyle/go-judge/types" +) + +const buffSize = 512 + +// Queue implements taskqueue by go channel +type Queue struct { + queue chan taskqueue.Task +} + +// New craetes new Queue with buffed go channel +func New() *Queue { + return &Queue{ + queue: make(chan taskqueue.Task, buffSize), + } +} + +// Enqueue puts task into run queue +func (q *Queue) Enqueue(t types.RunTask, r chan<- types.RunTaskResult) error { + q.queue <- Task{ + task: t, + result: r, + } + return nil +} + +// C returns the underlying channel +func (q *Queue) C() <-chan taskqueue.Task { + return q.queue +} + +// Task implements Task interface +type Task struct { + task types.RunTask + result chan<- types.RunTaskResult +} + +// Task returns task parameters +func (t Task) Task() *types.RunTask { + return &t.task +} + +// Finish returns the run task result +func (t Task) Finish(r *types.RunTaskResult) { + t.result <- *r +} diff --git a/types/judge.go b/types/judge.go index 8d3d250..cd44d7f 100644 --- a/types/judge.go +++ b/types/judge.go @@ -21,6 +21,7 @@ type JudgeProgress struct { // JudgeResult contains final result of current task type JudgeResult struct { SubTasks []JudgeSubTaskResult + Error string } // JudgeSubTaskResult contains result for single sub-task diff --git a/types/problem_conf.go b/types/problem_conf.go index 489b0bc..675351a 100644 --- a/types/problem_conf.go +++ b/types/problem_conf.go @@ -25,6 +25,6 @@ type SubTask struct { // Case defines single judge case type Case struct { - Input []file.File - Answer []file.File + Input file.File + Answer file.File } diff --git a/types/run_task.go b/types/run_task.go index 202cfa2..3017fa3 100644 --- a/types/run_task.go +++ b/types/run_task.go @@ -36,6 +36,8 @@ type RunTaskResult struct { Status string // error Error string + // compile result + ExecFiles []file.File // details Time uint64 // ms Memory uint64 // kb