Skip to content

Commit 24d0a0e

Browse files
d
1 parent e326e46 commit 24d0a0e

File tree

10 files changed

+125
-52
lines changed

10 files changed

+125
-52
lines changed

backend/cmd/compile_user_code.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package cmd
33
import (
44
"context"
55
"io"
6+
//"log"
67
"path/filepath"
78

89
dockertypes "github.com/docker/docker/api/types"
910
"github.com/docker/docker/api/types/container"
1011
"github.com/docker/docker/client"
1112
"github.com/docker/docker/pkg/stdcopy"
12-
"github.com/markhuang1212/code-grader/types"
13+
"github.com/markhuang1212/code-grader/backend/types"
1314
"github.com/pkg/errors"
1415
)
1516

@@ -45,56 +46,74 @@ func CompileUserCode(ctx context.Context, gr types.GradeRequest) ([]byte, error)
4546
}, nil, nil, "")
4647

4748
if err != nil {
48-
return nil, errors.Wrap(InternalError, "cannot create container")
49+
return nil, errors.Wrap(ErrInternalError, "cannot create container")
4950
}
5051

51-
hjresp, err := cli.ContainerAttach(ctx, resp.ID, dockertypes.ContainerAttachOptions{
52-
Stream: true,
53-
Stdin: true,
52+
attachInput, err := cli.ContainerAttach(ctx, resp.ID, dockertypes.ContainerAttachOptions{
53+
Stdin: true,
54+
})
55+
56+
if err != nil {
57+
return nil, errors.Wrap(ErrInternalError, "cannot attach container input")
58+
}
59+
60+
attachOutput, err := cli.ContainerAttach(ctx, resp.ID, dockertypes.ContainerAttachOptions{
5461
Stdout: true,
5562
Stderr: true,
5663
})
5764

5865
if err != nil {
59-
return nil, errors.Wrap(InternalError, "cannot attach container")
66+
return nil, errors.Wrap(ErrInternalError, "cannot attach container output")
6067
}
6168

6269
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNextExit)
6370

6471
err = cli.ContainerStart(ctx, resp.ID, dockertypes.ContainerStartOptions{})
6572
if err != nil {
66-
return nil, errors.Wrap(InternalError, "cannot start container")
73+
return nil, errors.Wrap(ErrInternalError, "cannot start container")
6774
}
6875

76+
defer func() {
77+
// err := cli.ContainerRemove(ctx, resp.ID, dockertypes.ContainerRemoveOptions{
78+
// Force: true,
79+
// })
80+
// if err != nil {
81+
// log.Fatal("cannot kill and remove container")
82+
// panic(err)
83+
// }
84+
}()
85+
86+
attachInput.Conn.Write([]byte(gr.UserCode))
87+
attachInput.Close()
88+
6989
outR, outW := io.Pipe()
7090
errR, errW := io.Pipe()
71-
hjresp.Conn.Write([]byte(gr.UserCode))
72-
hjresp.Conn.Close()
73-
stdcopy.StdCopy(outW, errW, hjresp.Conn)
91+
stdcopy.StdCopy(outW, errW, attachOutput.Conn)
7492
outW.Close()
7593
errW.Close()
94+
attachOutput.Conn.Close()
7695

7796
select {
7897
case status := <-statusCh:
7998
switch status.StatusCode {
8099
case 0:
81100
result, err := io.ReadAll(outR)
82101
if err != nil {
83-
return nil, errors.Wrap(InternalError, "error reading outR")
102+
return nil, errors.Wrap(ErrInternalError, "error reading outR")
84103
}
85104
return result, nil
86105
case 1:
87106
result, err := io.ReadAll(errR)
88107
if err != nil {
89-
return nil, errors.Wrap(InternalError, "error reading errR")
108+
return nil, errors.Wrap(ErrInternalError, "error reading errR")
90109
}
91-
return result, CompilationError
110+
return result, ErrCompilationError
92111
case 2:
93-
return nil, InternalError
112+
return nil, ErrInternalError
94113
default:
95-
return nil, InternalError
114+
return nil, ErrInternalError
96115
}
97116
case <-errCh:
98-
return nil, errors.Wrap(InternalError, "error waiting container")
117+
return nil, errors.Wrap(ErrInternalError, "error waiting container")
99118
}
100119
}

backend/cmd/compile_user_code_test.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@ package cmd_test
22

33
import (
44
"context"
5-
"fmt"
65
"testing"
76

87
"github.com/markhuang1212/code-grader/backend/cmd"
9-
"github.com/markhuang1212/code-grader/types"
8+
"github.com/markhuang1212/code-grader/backend/types"
9+
"github.com/stretchr/testify/assert"
1010
)
1111

1212
func TestCompileUserCode(t *testing.T) {
1313
ctx := context.Background()
1414

15-
gr := types.GradeRequest{
15+
gr1 := types.GradeRequest{
1616
TestCaseName: "example-1",
1717
UserCode: "",
1818
}
1919

20-
data, err := cmd.CompileUserCode(ctx, gr)
20+
out, err := cmd.CompileUserCode(ctx, gr1)
21+
assert.ErrorIs(t, err, cmd.ErrCompilationError)
22+
t.Log(out)
2123

22-
fmt.Println(data)
23-
fmt.Println(err)
24+
gr2 := types.GradeRequest{
25+
TestCaseName: "example-1",
26+
UserCode: "int main() { cout << \"Hello\" << endl; }",
27+
}
28+
29+
out, err = cmd.CompileUserCode(ctx, gr2)
30+
assert.Equal(t, err, nil)
31+
t.Log(out)
2432

2533
}

backend/cmd/errors.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
)
77

88
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")
9+
ErrTimeLimitExceed = errors.New("time limit exceeds")
10+
ErrMemoryLimitExceed = errors.New("memory limit exceeds")
11+
ErrInternalError = errors.New("internal error")
12+
ErrCompilationError = errors.New("compilation error")
13+
AppRoot = os.Getenv("APP_ROOT")
1414
)

backend/cmd/exec_user_code.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,30 @@ import (
1212
"github.com/docker/docker/api/types/container"
1313
"github.com/docker/docker/client"
1414
"github.com/docker/docker/pkg/stdcopy"
15-
"github.com/markhuang1212/code-grader/types"
15+
"github.com/markhuang1212/code-grader/backend/types"
1616
"github.com/pkg/errors"
1717
)
1818

1919
const imageExec = "markhuang1212/code-grader/runtime-exec:latest"
2020

21-
func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error) {
21+
func ExecUserCode(ctx context.Context, gr types.GradeRequest) ([]byte, error) {
2222

2323
var testCase types.TestCaseOptions
2424

2525
testCaseJson, err := os.ReadFile(filepath.Join(AppRoot, "testcases", gr.TestCaseName, "testcase.json"))
2626
if err != nil {
27-
return nil, errors.Wrap(InternalError, "cannot open testcase.json")
27+
return nil, errors.Wrap(ErrInternalError, "cannot open testcase.json")
2828
}
2929

3030
err = json.Unmarshal(testCaseJson, &testCase)
3131
if err != nil {
32-
return nil, errors.Wrap(InternalError, "cannot parse testcase.json")
32+
return nil, errors.Wrap(ErrInternalError, "cannot parse testcase.json")
3333
}
3434

3535
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
36+
if err != nil {
37+
return nil, errors.Wrap(ErrInternalError, "cannot create docker client")
38+
}
3639

3740
resp, err := cli.ContainerCreate(ctx, &container.Config{
3841
Image: imageExec,
@@ -49,7 +52,7 @@ func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error)
4952
}, nil, nil, "")
5053

5154
if err != nil {
52-
return nil, errors.Wrap(InternalError, "cannot create container")
55+
return nil, errors.Wrap(ErrInternalError, "cannot create container")
5356
}
5457

5558
hjresp, err := cli.ContainerAttach(ctx, resp.ID, dockertypes.ContainerAttachOptions{
@@ -60,7 +63,7 @@ func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error)
6063
})
6164

6265
if err != nil {
63-
return nil, errors.Wrap(InternalError, "cannot attach container")
66+
return nil, errors.Wrap(ErrInternalError, "cannot attach container")
6467
}
6568

6669
ctxdl, cancel := context.WithTimeout(ctx, time.Second*time.Duration(testCase.RuntimeOptions.RuntimeLimit))
@@ -70,9 +73,14 @@ func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error)
7073

7174
err = cli.ContainerStart(ctxdl, resp.ID, dockertypes.ContainerStartOptions{})
7275
if err != nil {
73-
return nil, errors.Wrap(InternalError, "cannot start container")
76+
return nil, errors.Wrap(ErrInternalError, "cannot start container")
7477
}
7578

79+
defer cli.ContainerRemove(ctx, resp.ID, dockertypes.ContainerRemoveOptions{
80+
Force: true,
81+
})
82+
defer cli.ContainerStop(ctx, resp.ID, nil)
83+
7684
outR, outW := io.Pipe()
7785
errR, errW := io.Pipe()
7886
hjresp.Conn.Write([]byte(gr.UserCode))
@@ -84,28 +92,28 @@ func exec_user_code(ctx context.Context, gr types.GradeRequest) ([]byte, error)
8492
select {
8593
case status := <-statusCh:
8694
switch status.StatusCode {
87-
case 0:
95+
case 0: // success
8896
result, err := io.ReadAll(outR)
8997
if err != nil {
90-
return nil, errors.Wrap(InternalError, "error reading outR")
98+
return nil, errors.Wrap(ErrInternalError, "error reading outR")
9199
}
92100
return result, nil
93-
case 1:
101+
case 1: // wrong result
94102
result, err := io.ReadAll(errR)
95103
if err != nil {
96-
return nil, errors.Wrap(InternalError, "error reading errR")
104+
return nil, errors.Wrap(ErrInternalError, "error reading errR")
97105
}
98-
return result, CompilationError
99-
case 2:
100-
return nil, InternalError
106+
return result, ErrCompilationError
107+
case 2: // internal error
108+
return nil, ErrInternalError
101109
default:
102-
return nil, InternalError
110+
return nil, ErrInternalError
103111
}
104112
case err := <-errCh:
105113
if errors.Is(err, context.DeadlineExceeded) {
106-
return nil, TimeLimitExceed
114+
return nil, ErrTimeLimitExceed
107115
}
108-
return nil, errors.Wrap(InternalError, "error waiting container")
116+
return nil, errors.Wrap(ErrInternalError, "error waiting container")
109117
}
110118

111119
}

backend/cmd/result_cache.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,35 @@
11
package cmd
2+
3+
import (
4+
"sync"
5+
"time"
6+
7+
"github.com/markhuang1212/code-grader/backend/types"
8+
)
9+
10+
type ResultCache struct {
11+
timeout time.Duration
12+
lock sync.RWMutex
13+
data map[string]*types.GradeResult
14+
}
15+
16+
func (c *ResultCache) Set(key string, val types.GradeResult) {
17+
c.lock.Lock()
18+
defer c.lock.Unlock()
19+
c.data[key] = &val
20+
go func() {
21+
time.Sleep(c.timeout)
22+
23+
}()
24+
}
25+
26+
func (c *ResultCache) Get(key string) (types.GradeResult, bool) {
27+
c.lock.RLock()
28+
defer c.lock.RUnlock()
29+
v, ok := c.data[key]
30+
if ok {
31+
return *v, true
32+
} else {
33+
return types.GradeResult{}, false
34+
}
35+
}
File renamed without changes.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package types
22

3-
type GradeResult int
3+
type GradeResultStatus int
44

55
const (
6-
GradeResultSuccess GradeResult = iota
6+
GradeResultSuccess GradeResultStatus = iota
77
GradeResultWrongAnswer
88
GradeResultCompilationError
99
GradeResultTimeLimitExceed
1010
GradeResultMemoryExceed
1111
)
1212

13-
type GradeResultMsg struct {
14-
Result GradeResult
13+
type GradeResult struct {
14+
Status GradeResultStatus
1515
Msg string
1616
}
File renamed without changes.

go.mod

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ go 1.16
44

55
require (
66
github.com/containerd/containerd v1.5.2 // indirect
7-
github.com/docker/docker v20.10.7+incompatible // indirect
7+
github.com/docker/docker v20.10.7+incompatible
88
github.com/docker/go-connections v0.4.0 // indirect
9-
github.com/gin-gonic/gin v1.7.2 // indirect
10-
github.com/pkg/errors v0.9.1 // indirect
9+
github.com/gin-gonic/gin v1.7.2
10+
github.com/pkg/errors v0.9.1
1111
github.com/sirupsen/logrus v1.8.1 // indirect
12-
github.com/stretchr/testify v1.7.0 // indirect
12+
github.com/stretchr/testify v1.7.0
1313
google.golang.org/grpc v1.38.0 // indirect
14+
gotest.tools/v3 v3.0.3
1415
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
322322
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
323323
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
324324
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
325+
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
325326
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
326327
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
327328
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -902,8 +903,10 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
902903
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
903904
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
904905
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
906+
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
905907
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
906908
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
909+
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
907910
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
908911
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
909912
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

0 commit comments

Comments
 (0)