评测机
Go to file
criyle a79543f0af Cache container work directory for performance
- 140 op/s -> 170 op/s for A+B problem
2020-03-16 20:16:18 -04:00
client Fix ghc compile 2020-03-12 00:45:41 -04:00
cmd Fix ghc compile 2020-03-12 00:45:41 -04:00
data update documentation 2019-08-28 23:51:07 -07:00
file Refactor & Documentation 2020-03-03 02:32:59 -05:00
judger Refactor & Documentation 2020-03-03 02:32:59 -05:00
language bump sandbox version 2020-03-02 03:32:42 -05:00
pkg Cache container work directory for performance 2020-03-16 20:16:18 -04:00
problem Refactor & Documentation 2020-03-03 02:32:59 -05:00
runner Implements the draft executor server 2020-03-04 02:12:26 -05:00
taskqueue Refactor & Documentation 2020-03-03 02:32:59 -05:00
.dockerignore Add demo Dockerfile 2020-03-04 17:45:48 -05:00
.gitignore Refactor & Documentation 2020-03-03 02:32:59 -05:00
Dockerfile.exec Use multi-stage build to reduce download 2020-03-04 23:15:40 -05:00
go.mod Cache container work directory for performance 2020-03-16 20:16:18 -04:00
go.sum Cache container work directory for performance 2020-03-16 20:16:18 -04:00
LICENSE Initial commit 2019-08-24 15:32:38 -07:00
README.md Add ExitStatus 2020-03-08 01:49:11 -05:00

go-judge

Under designing & development

GoDoc

The goal to to reimplement syzoj/judge-v3 in GO language using go-sandbox.

Planned Design

Executor Service Draft (under development)

A rest service to run program in restricted environment and it is basically a wrapper for pkg/envexec to run single / multiple programs.

  • /run POST execute program in the restricted environment
  • /file GET list all cached file
  • /file POST prepare a file in the executor service (in memory), returns fileId (can be referenced in /run parameter)
  • /file/:fileId GET downloads file from executor service (in memory), returns file content
  • /file/:fileId DELETE delete file specified by fileId

Install & Run Developing Server

Install GO 1.13+ from download

go get github.com/criyle/go-judge/cmd/executorserver
~/go/bin/executorserver # or executorserver if $(GOPATH)/bin is in your $PATH

Or, by docker

docker run -it --rm --privileged -p 5050:5050 criyle/executorserver:demo

Build by your own docker build -t executorserver -f Dockerfile.exec .

The executorserver need root privilege to create cgroup. Either creates sub-directory /sys/fs/cgroup/cpuacct/go-judger, /sys/fs/cgroup/memory/go-judger, /sys/fs/cgroup/pids/go-judger and make execution user readable or use sudo to run it.

The default binding address for the executor server is :5050. Can be specified with -http flag.

The default concurrency is 4, Can be specified with -parallism flag.

The default file store is in memory, local cache can be specified wieh -dir flag.

Planed API interface

interface LocalFile {
    src: string; // absolute path for the file
}

interface MemoryFile {
    content: string | Buffer; // file contents
}

interface PreparedFile {
    fileId: string; // fileId defines file uploaded by /file
}

interface Pipe {
    name: string; // file name in copyOut
    max: number;  // maximum bytes to collect from pipe
}

interface Cmd {
    args: string[]; // command line argument
    env?: string[]; // environment

    // specifies file input / pipe collector for program file descriptors
    files?: (LocalFile | MemoryFile | PreparedFile | Pipe | null)[];

    // limitations
    cpuLimit?: number;     // ns
    realCpuLimit?: number; // ns
    memoryLimit?: number;  // byte
    procLimit?: number;

    // copy the correspond file to the container dst path
    copyIn?: {[dst:string]:LocalFile | MemoryFile | PreparedFile};

    // copy out specifies files need to be copied out from the container after execution
    copyOut?: string[];
    // similar to copyOut but stores file in executor service and returns fileId, later download through /file/:fileId
    copyOutCached?: string[];
    // specifies the directory to dump container /w content
    copyOutDir: string
}

enum Status {
    Accepted,            // normal
    MemoryLimitExceeded, // mle
    TimeLimitExceeded,   // tle
    OutputLimitExceeded, // ole
    FileError,           // fe
    RuntimeError,        // re
    DangerousSyscall,    // dgs
    InternalError,       // system error
}

interface PipeIndex {
    index: number; // the index of cmd
    fd: number;    // the fd number of cmd
}

interface PipeMap {
    in: PipeIndex;  // input end of the pipe
    out: PipeIndex; // output end of the pipe
}

interface Request {
    cmd: Cmd[];
    pipeMapping: PipeMap[];
}

interface Result {
    status: Status;
    error?: string; // potential system error message
    time: number;   // ns
    memory: number; // byte
    // copyFile name -> content
    files?: {[name:string]:string};
    // copyFileCached name -> fileId
    fileIds?: {[name:string]:string};
}

Example Request & Response:

Single (Require apt install g++ inside the container):

{
	"cmd": [{
		"args": ["/usr/bin/g++", "a.cc", "-o", "a"],
		"env": ["PATH=/usr/bin:/bin"],
		"files": [{
			"content": ""
		}, {
			"name": "stdout",
			"max": 10240
		}, {
			"name": "stderr",
			"max": 10240
		}],
		"cpuLimit": 10000000000,
		"memoryLimit": 104857600,
		"procLimit": 50,
		"copyIn": {
			"a.cc": {
				"content": "#include <iostream>\nusing namespace std;\nint main() {\nint a, b;\ncin >> a >> b;\ncout << a + b << endl;\n}"
			}
		},
		"copyOut": ["stdout", "stderr"],
        "copyOutCached": ["a.cc", "a"],
        "copyOutDir": "1"
	}]
}
[
    {
        "status": "Accepted",
        "exitStatus": 0,
        "time": 303225231,
        "memory": 32243712,
        "files": {
            "stderr": "",
            "stdout": ""
        },
        "fileIds": {
            "a": "5LWIZAA45JHX4Y4Z",
            "a.cc": "NOHPGGDTYQUFRSLJ"
        }
    }
]

Multiple:

{
	"cmd": [{
		"args": ["/bin/cat", "1"],
		"env": ["PATH=/usr/bin:/bin"],
		"files": [{
			"content": ""
		}, null, {
			"name": "stderr",
			"max": 10240
		}],
		"cpuLimit": 1000000000,
		"memoryLimit": 1048576,
		"procLimit": 50,
		"copyIn": {
			"1": { "content": "TEST 1" }
		},
		"copyOut": ["stderr"]
	},
	{
		"args": ["/bin/cat"],
		"env": ["PATH=/usr/bin:/bin"],
		"files": [null, {
			"name": "stdout",
			"max": 10240
		}, {
			"name": "stderr",
			"max": 10240
		}],
		"cpuLimit": 1000000000,
		"memoryLimit": 1048576,
		"procLimit": 50,
		"copyOut": ["stdout", "stderr"]
	}],
	"pipeMapping": [{
		"in" : {"index": 0, "fd": 1 },
		"out" : {"index": 1, "fd" : 0 }
	}]
}
[
    {
        "status": "Accepted",
        "exitStatus": 0,
        "time": 1545123,
        "memory": 253952,
        "files": {
            "stderr": ""
        },
        "fileIds": {}
    },
    {
        "status": "Accepted",
        "exitStatus": 0,
        "time": 1501463,
        "memory": 253952,
        "files": {
            "stderr": "",
            "stdout": "TEST 1"
        },
        "fileIds": {}
    }
]

Workflow

    ^
    | Client (talk to the website)
    v
+------+    +----+
|      |<-->|Data|
|Judger|    +----+--+
|      |<-->|Problem|
+------+    +-------+
    ^
    | TaskQueue
    v
+------+   +--------+
|Runner|<->|Language|
+------+   +--------+
    ^
    | EnvExec
    v
+--------------------+
|ContainerEnvironment|
+--------------------+

Container Root Filesystem

  • necessary lib / exec / compiler / header readonly bind mounted from current file system: /lib /lib64 /bin /usr
  • work directory tmpfs mount: /w (work dir), /tmp (compiler temp files)
  • additional compiler scripts / exec readonly bind mounted: /c
  • additional header readonly bind mounted: /i

Interfaces

  • client: receive judge tasks (websocket / socket.io / RabbitMQ / REST API)
  • data: interface to download, cache, lock and access test data files from website (by dataId)
  • taskqueue: message queue to send run task and receive run task result (In memory / (RabbitMQ, Redis))
  • file: general file interface (disk / memory)
  • language: programming language compile & execute configurations
  • problem: parse problem definition from configuration files

Judge Logic

  • judger: execute judge logics (compile / standard / interactive / answer submit) and distribute as run task to queue, the collect and calculate results
  • runner: receive run task and execute in sandbox environment

Models

  • JudgeTask: judge task pushed from website (type, source, data)
  • JudgeResult: judge task result send back to website
  • JudgeSetting: problem setting (from yaml) and JudgeCase
  • RunTask: run task parameters send to run_queue
  • RunResult: run task result sent back from queue

Utilities

  • pkg/envexec: run single / group of programs in parallel within restricted environment and resource constraints

Planned API

Progress

Client is able to report progress to the web front-end. Task should maintain its states

Planned events are:

  • Parsed: problem data have been downloaded and problem configuration have been parsed (pass problem config to task)
  • Compiled: user code have been compiled (success / fail)
  • Progressed: single test case finished (success / fail - detail message)
  • Finished: all test cases finished / compile failed

TODO

  • socket.io client with namespace
  • judge_v3 protocol
  • executor server
  • syzoj problem YAML config parser
  • syzoj data downloader
  • syzoj compile configuration
  • file io
  • special judger
  • interact problem
  • answer submit
  • demo site