Skip to content

Commit e326e46

Browse files
d
1 parent 2ed8c19 commit e326e46

23 files changed

+276
-214
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
APP_ROOT=/workspaces/code-grader

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
{
22
"cSpell.words": [
3+
"CXXFLAGS",
4+
"cout",
5+
"ctxdl",
6+
"endl",
7+
"hjresp",
38
"lpthread",
49
"prepend",
10+
"stdc",
11+
"struct",
512
"usercode"
613
]
714
}

backend/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ RUN go build
1515

1616
EXPOSE 8080
1717
ENV GIN_MODE=release
18+
ENV APP_ROOT=/code-grader
1819

1920
ENTRYPOINT [ "/code-grader/backend/backend" ]

backend/cmd/compile_user_code.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ import (
1717
const CompilationMemoryLimit = 100 * 1024 * 1024
1818

1919
// it is required that the docker image is built before the program runs
20-
const imageName = "markhuang1212/code-grader/runtime-compile"
21-
22-
var InternalError = errors.New("internal error")
23-
var CompilationFailure = errors.New("compilation error")
20+
const imageCompile = "markhuang1212/code-grader/runtime-compile:latest"
2421

2522
// The function compiles user's code inside a docker container, and returns the
2623
// executable on success
@@ -32,18 +29,18 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) ([]byte, error)
3229
}
3330

3431
resp, err := cli.ContainerCreate(ctx, &container.Config{
35-
Image: imageName,
32+
Image: imageCompile,
3633
Env: []string{
3734
"TEST_CASE_DIR=" + filepath.Join("/code-grader/testcases", gr.TestCaseName),
35+
"CXX=g++",
36+
"CXXFLAGS=-std=c++11",
3837
},
39-
AttachStdin: true,
40-
AttachStdout: true,
41-
AttachStderr: true,
42-
OpenStdin: true,
38+
OpenStdin: true,
39+
StdinOnce: true,
4340
}, &container.HostConfig{
4441
NetworkMode: "none",
45-
Resources: container.Resources{
46-
// Memory: CompilationMemoryLimit,
42+
Resources: container.Resources{
43+
Memory: CompilationMemoryLimit,
4744
},
4845
}, nil, nil, "")
4946

@@ -62,7 +59,7 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) ([]byte, error)
6259
return nil, errors.Wrap(InternalError, "cannot attach container")
6360
}
6461

65-
statucCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNextExit)
62+
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNextExit)
6663

6764
err = cli.ContainerStart(ctx, resp.ID, dockertypes.ContainerStartOptions{})
6865
if err != nil {
@@ -74,21 +71,28 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) ([]byte, error)
7471
hjresp.Conn.Write([]byte(gr.UserCode))
7572
hjresp.Conn.Close()
7673
stdcopy.StdCopy(outW, errW, hjresp.Conn)
74+
outW.Close()
75+
errW.Close()
7776

7877
select {
79-
case status := <-statucCh:
80-
if status.StatusCode == 0 {
81-
out, err := io.ReadAll(outR)
78+
case status := <-statusCh:
79+
switch status.StatusCode {
80+
case 0:
81+
result, err := io.ReadAll(outR)
8282
if err != nil {
83-
return nil, errors.Wrap(InternalError, "cannot read stdout")
83+
return nil, errors.Wrap(InternalError, "error reading outR")
8484
}
85-
return out, err
86-
} else {
87-
out, err := io.ReadAll(errR)
85+
return result, nil
86+
case 1:
87+
result, err := io.ReadAll(errR)
8888
if err != nil {
89-
return nil, errors.Wrap(InternalError, "cannot read stderr")
89+
return nil, errors.Wrap(InternalError, "error reading errR")
9090
}
91-
return out, CompilationFailure
91+
return result, CompilationError
92+
case 2:
93+
return nil, InternalError
94+
default:
95+
return nil, InternalError
9296
}
9397
case <-errCh:
9498
return nil, errors.Wrap(InternalError, "error waiting container")

backend/cmd/compile_user_code_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ import (
1111

1212
func TestCompileUserCode(t *testing.T) {
1313
ctx := context.Background()
14+
1415
gr := types.GradeRequest{
1516
TestCaseName: "example-1",
1617
UserCode: "",
1718
}
1819

19-
_, err := cmd.CompileUserCode(ctx, gr)
20+
data, err := cmd.CompileUserCode(ctx, gr)
21+
22+
fmt.Println(data)
2023
fmt.Println(err)
24+
2125
}

backend/cmd/errors.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"os"
6+
)
7+
8+
var (
9+
TimeLimitExceed = errors.New("time limit exceeds")
10+
MemoryLimitExceed = errors.New("memory limit exceeds")
11+
InternalError = errors.New("internal error")
12+
CompilationError = errors.New("compilation error")
13+
AppRoot = os.Getenv("APP_ROOT")
14+
)

backend/cmd/exec_user_code.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"time"
10+
11+
dockertypes "github.com/docker/docker/api/types"
12+
"github.com/docker/docker/api/types/container"
13+
"github.com/docker/docker/client"
14+
"github.com/docker/docker/pkg/stdcopy"
15+
"github.com/markhuang1212/code-grader/types"
16+
"github.com/pkg/errors"
17+
)
18+
19+
const imageExec = "markhuang1212/code-grader/runtime-exec:latest"
20+
21+
func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error) {
22+
23+
var testCase types.TestCaseOptions
24+
25+
testCaseJson, err := os.ReadFile(filepath.Join(AppRoot, "testcases", gr.TestCaseName, "testcase.json"))
26+
if err != nil {
27+
return nil, errors.Wrap(InternalError, "cannot open testcase.json")
28+
}
29+
30+
err = json.Unmarshal(testCaseJson, &testCase)
31+
if err != nil {
32+
return nil, errors.Wrap(InternalError, "cannot parse testcase.json")
33+
}
34+
35+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
36+
37+
resp, err := cli.ContainerCreate(ctx, &container.Config{
38+
Image: imageExec,
39+
Env: []string{
40+
"TEST_CASE_DIR=" + filepath.Join("/code-grader/testcases", gr.TestCaseName),
41+
},
42+
OpenStdin: true,
43+
StdinOnce: true,
44+
}, &container.HostConfig{
45+
NetworkMode: "none",
46+
Resources: container.Resources{
47+
Memory: CompilationMemoryLimit,
48+
},
49+
}, nil, nil, "")
50+
51+
if err != nil {
52+
return nil, errors.Wrap(InternalError, "cannot create container")
53+
}
54+
55+
hjresp, err := cli.ContainerAttach(ctx, resp.ID, dockertypes.ContainerAttachOptions{
56+
Stream: true,
57+
Stdin: true,
58+
Stdout: true,
59+
Stderr: true,
60+
})
61+
62+
if err != nil {
63+
return nil, errors.Wrap(InternalError, "cannot attach container")
64+
}
65+
66+
ctxdl, cancel := context.WithTimeout(ctx, time.Second*time.Duration(testCase.RuntimeOptions.RuntimeLimit))
67+
defer cancel()
68+
69+
statusCh, errCh := cli.ContainerWait(ctxdl, resp.ID, container.WaitConditionNextExit)
70+
71+
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
72+
if err != nil {
73+
return nil, errors.Wrap(InternalError, "cannot start container")
74+
}
75+
76+
outR, outW := io.Pipe()
77+
errR, errW := io.Pipe()
78+
hjresp.Conn.Write([]byte(gr.UserCode))
79+
hjresp.Conn.Close()
80+
stdcopy.StdCopy(outW, errW, hjresp.Conn)
81+
outW.Close()
82+
errW.Close()
83+
84+
select {
85+
case status := <-statusCh:
86+
switch status.StatusCode {
87+
case 0:
88+
result, err := io.ReadAll(outR)
89+
if err != nil {
90+
return nil, errors.Wrap(InternalError, "error reading outR")
91+
}
92+
return result, nil
93+
case 1:
94+
result, err := io.ReadAll(errR)
95+
if err != nil {
96+
return nil, errors.Wrap(InternalError, "error reading errR")
97+
}
98+
return result, CompilationError
99+
case 2:
100+
return nil, InternalError
101+
default:
102+
return nil, InternalError
103+
}
104+
case err := <-errCh:
105+
if errors.Is(err, context.DeadlineExceeded) {
106+
return nil, TimeLimitExceed
107+
}
108+
return nil, errors.Wrap(InternalError, "error waiting container")
109+
}
110+
111+
}

backend/cmd/handle_grade_request.go

Lines changed: 0 additions & 22 deletions
This file was deleted.

backend/cmd/result_cache.go

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1 @@
11
package cmd
2-
3-
import (
4-
"sync"
5-
"time"
6-
7-
"github.com/markhuang1212/code-grader/types"
8-
)
9-
10-
type cachedItem struct {
11-
Result *types.GradeResult
12-
Deadline time.Time
13-
}
14-
15-
type TestResultCache struct {
16-
cachedData map[string]*cachedItem
17-
lock sync.RWMutex
18-
Timeout time.Duration
19-
}
20-
21-
func NewTestResultCache() *TestResultCache {
22-
23-
cache := TestResultCache{
24-
cachedData: make(map[string]*cachedItem),
25-
Timeout: 720 * time.Second,
26-
}
27-
28-
return &cache
29-
}
30-
31-
func (c *TestResultCache) Add(id string) {
32-
c.lock.Lock()
33-
defer c.lock.Unlock()
34-
c.cachedData[id] = &cachedItem{
35-
Result: nil,
36-
Deadline: time.Now().Add(c.Timeout),
37-
}
38-
go func() {
39-
delete(c.cachedData, id)
40-
}()
41-
}
42-
43-
func (c *TestResultCache) Update(id string, result types.GradeResult) {
44-
c.lock.Lock()
45-
defer c.lock.Unlock()
46-
if c.cachedData[id] != nil {
47-
c.cachedData[id].Result = &result
48-
}
49-
}
50-
51-
func (c *TestResultCache) Get(id string) (*cachedItem, bool) {
52-
c.lock.RLock()
53-
defer c.lock.RUnlock()
54-
item, ok := c.cachedData[id]
55-
return item, ok
56-
}

backend/cmd/setup_router.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ import (
77
)
88

99
func SetupRouter() *gin.Engine {
10+
1011
r := gin.Default()
12+
1113
r.GET("/ping", func(c *gin.Context) {
1214
c.String(http.StatusOK, "pong")
1315
})
1416

17+
r.POST("/grade", func(c *gin.Context) {
18+
19+
})
20+
1521
return r
1622
}

0 commit comments

Comments
 (0)