aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/game/hub.go3
-rw-r--r--backend/taskqueue/processor.go7
-rw-r--r--worker/swift/exec.go175
-rw-r--r--worker/swift/handlers.go42
-rw-r--r--worker/swift/main.go4
-rw-r--r--worker/swift/models.go56
6 files changed, 129 insertions, 158 deletions
diff --git a/backend/game/hub.go b/backend/game/hub.go
index 8f27466..436acf5 100644
--- a/backend/game/hub.go
+++ b/backend/game/hub.go
@@ -41,9 +41,8 @@ func (hub *Hub) CalcCodeSize(code string, language string) int {
trimmed := re.ReplaceAllString(code, "")
if language == "php" {
return len(strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(trimmed, "<?php"), "<?"), "?>"))
- } else {
- return len(trimmed)
}
+ return len(trimmed)
}
func (hub *Hub) EnqueueTestTasks(ctx context.Context, submissionID, gameID, userID int, language, code string) error {
diff --git a/backend/taskqueue/processor.go b/backend/taskqueue/processor.go
index 8dadfdf..c4cf60b 100644
--- a/backend/taskqueue/processor.go
+++ b/backend/taskqueue/processor.go
@@ -3,6 +3,7 @@ package taskqueue
import (
"bytes"
"context"
+ "crypto/md5"
"encoding/json"
"fmt"
"net/http"
@@ -16,6 +17,7 @@ func newProcessor() processor {
type testrunRequestData struct {
Code string `json:"code"`
+ CodeHash string `json:"code_hash"`
Stdin string `json:"stdin"`
MaxDuration int `json:"max_duration_ms"`
}
@@ -32,6 +34,7 @@ func (p *processor) doProcessTaskRunTestcase(
) (*TaskResultRunTestcase, error) {
reqData := testrunRequestData{
Code: payload.Code,
+ CodeHash: calcCodeHash(payload.Code),
Stdin: payload.Stdin,
MaxDuration: 5000,
}
@@ -64,3 +67,7 @@ func (p *processor) doProcessTaskRunTestcase(
Stderr: resData.Stderr,
}, nil
}
+
+func calcCodeHash(code string) string {
+ return fmt.Sprintf("%x", md5.Sum([]byte(code)))
+}
diff --git a/worker/swift/exec.go b/worker/swift/exec.go
index 37f542b..8ad5615 100644
--- a/worker/swift/exec.go
+++ b/worker/swift/exec.go
@@ -12,35 +12,20 @@ import (
const (
dataRootDir = "/app/data"
- // Stores *.swift files.
- dataSwiftRootDir = dataRootDir + "/swift"
- // Stores *.wasm files.
- dataWasmRootDir = dataRootDir + "/wasm"
- // Stores *.cwasm files (compiled wasm generated by "wasmtime compile").
- dataCwasmRootDir = dataRootDir + "/cwasm"
wasmMaxMemorySize = 10 * 1024 * 1024 // 10 MiB
)
func prepareDirectories() error {
- if err := os.MkdirAll(dataSwiftRootDir, 0755); err != nil {
- return err
- }
- if err := os.MkdirAll(dataWasmRootDir, 0755); err != nil {
- return err
- }
- if err := os.MkdirAll(dataCwasmRootDir, 0755); err != nil {
+ if err := os.MkdirAll(dataRootDir, 0755); err != nil {
return err
}
return nil
}
-func calcFilePath(hash, ext string) string {
- return fmt.Sprintf("%s/%s/%s.%s", dataRootDir, ext, hash, ext)
-}
-
func execCommandWithTimeout(
ctx context.Context,
+ workingDir string,
maxDuration time.Duration,
makeCmd func(context.Context) *exec.Cmd,
) (string, string, error) {
@@ -48,6 +33,7 @@ func execCommandWithTimeout(
defer cancel()
cmd := makeCmd(ctx)
+ cmd.Dir = workingDir
var stdout bytes.Buffer
var stderr bytes.Buffer
@@ -67,114 +53,171 @@ func execCommandWithTimeout(
}
}
-func convertCommandErrorToResultType(err error, isCompile bool) string {
+func convertCommandErrorToResultType(err error, defaultErrorStatus string) string {
if err != nil {
if err == context.DeadlineExceeded {
return resultTimeout
}
- if isCompile {
- return resultCompileError
- }
- return resultRuntimeError
+ return defaultErrorStatus
}
return resultSuccess
}
-func execSwiftCompile(
- ctx context.Context,
- code string,
- codeHash string,
- maxDuration time.Duration,
-) swiftCompileResponseData {
- inPath := calcFilePath(codeHash, "swift")
- outPath := calcFilePath(codeHash, "wasm")
-
- if err := os.WriteFile(inPath, []byte(code), 0644); err != nil {
- return swiftCompileResponseData{
+func prepareWorkingDir(workingDir string) execResponseData {
+ err := os.MkdirAll(workingDir, 0755)
+ if err != nil {
+ return execResponseData{
Status: resultInternalError,
Stdout: "",
- Stderr: err.Error(),
+ Stderr: "Failed to create project directory",
}
}
+ return execResponseData{
+ Status: resultSuccess,
+ Stdout: "",
+ Stderr: "",
+ }
+}
+
+func removeWorkingDir(workingDir string) {
+ err := os.RemoveAll(workingDir)
+ _ = err
+}
+func initSwiftProject(
+ ctx context.Context,
+ workingDir string,
+ maxDuration time.Duration,
+) execResponseData {
stdout, stderr, err := execCommandWithTimeout(
ctx,
+ workingDir,
maxDuration,
func(ctx context.Context) *exec.Cmd {
return exec.CommandContext(
ctx,
- "swiftc",
- "-target", "wasm32-unknown-wasi",
- "-o", outPath,
- inPath,
+ "swift",
+ "package",
+ "init",
+ "--type", "executable",
)
},
)
-
- return swiftCompileResponseData{
- Status: convertCommandErrorToResultType(err, true),
+ return execResponseData{
+ Status: convertCommandErrorToResultType(err, resultInternalError),
Stdout: stdout,
Stderr: stderr,
}
}
-func execWasmCompile(
+func putSwiftSourceFile(
+ workingDir string,
+ code string,
+) execResponseData {
+ err := os.WriteFile(workingDir+"/Sources/main.swift", []byte(code), 0644)
+
+ if err != nil {
+ return execResponseData{
+ Status: convertCommandErrorToResultType(err, resultInternalError),
+ Stdout: "",
+ Stderr: "Failed to copy source file",
+ }
+ }
+ return execResponseData{
+ Status: resultSuccess,
+ Stdout: "",
+ Stderr: "",
+ }
+}
+
+func buildSwiftProject(
ctx context.Context,
- codeHash string,
+ workingDir string,
maxDuration time.Duration,
-) wasmCompileResponseData {
- inPath := calcFilePath(codeHash, "wasm")
- outPath := calcFilePath(codeHash, "cwasm")
-
+) execResponseData {
stdout, stderr, err := execCommandWithTimeout(
ctx,
+ workingDir,
maxDuration,
func(ctx context.Context) *exec.Cmd {
return exec.CommandContext(
ctx,
- "wasmtime", "compile",
- "-O", "opt-level=0",
- "-C", "cache=n",
- "-W", fmt.Sprintf("max-memory-size=%d", wasmMaxMemorySize),
- "-o", outPath,
- inPath,
+ "swift",
+ "build",
+ "--swift-sdk", "wasm32-unknown-wasi",
)
},
)
-
- return wasmCompileResponseData{
- Status: convertCommandErrorToResultType(err, true),
+ return execResponseData{
+ Status: convertCommandErrorToResultType(err, resultCompileError),
Stdout: stdout,
Stderr: stderr,
}
}
-func execTestRun(
+func runWasm(
ctx context.Context,
+ workingDir string,
codeHash string,
stdin string,
maxDuration time.Duration,
-) testRunResponseData {
- inPath := calcFilePath(codeHash, "cwasm")
-
+) execResponseData {
stdout, stderr, err := execCommandWithTimeout(
ctx,
+ workingDir,
maxDuration,
func(ctx context.Context) *exec.Cmd {
cmd := exec.CommandContext(
ctx,
- "wasmtime", "run",
- "--allow-precompiled",
- inPath,
+ "wasmtime",
+ "-W", fmt.Sprintf("max-memory-size=%d", wasmMaxMemorySize),
+ ".build/wasm32-unknown-wasi/debug/"+codeHash+".wasm",
)
cmd.Stdin = strings.NewReader(stdin)
return cmd
},
)
-
- return testRunResponseData{
- Status: convertCommandErrorToResultType(err, false),
+ return execResponseData{
+ Status: convertCommandErrorToResultType(err, resultRuntimeError),
Stdout: stdout,
Stderr: stderr,
}
}
+
+func doExec(
+ ctx context.Context,
+ code string,
+ codeHash string,
+ stdin string,
+ maxDuration time.Duration,
+) execResponseData {
+ workingDir := dataRootDir + "/" + codeHash
+
+ res := prepareWorkingDir(workingDir)
+ if !res.success() {
+ return res
+ }
+ defer removeWorkingDir(workingDir)
+
+ res = initSwiftProject(ctx, workingDir, maxDuration)
+ if !res.success() {
+ return res
+ }
+
+ res = putSwiftSourceFile(workingDir, code)
+ if !res.success() {
+ return res
+ }
+
+ res = buildSwiftProject(ctx, workingDir, maxDuration)
+ if !res.success() {
+ return res
+ }
+
+ res = runWasm(ctx, workingDir, codeHash, stdin, maxDuration)
+ if !res.success() {
+ return res
+ }
+
+ return res
+}
diff --git a/worker/swift/handlers.go b/worker/swift/handlers.go
index ac9701f..4cb9999 100644
--- a/worker/swift/handlers.go
+++ b/worker/swift/handlers.go
@@ -11,8 +11,8 @@ func newBadRequestError(err error) *echo.HTTPError {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid request: %s", err.Error()))
}
-func handleSwiftCompile(c echo.Context) error {
- var req swiftCompileRequestData
+func handleExec(c echo.Context) error {
+ var req execRequestData
if err := c.Bind(&req); err != nil {
return newBadRequestError(err)
}
@@ -20,46 +20,10 @@ func handleSwiftCompile(c echo.Context) error {
return newBadRequestError(err)
}
- res := execSwiftCompile(
+ res := doExec(
c.Request().Context(),
req.Code,
req.CodeHash,
- req.maxDuration(),
- )
-
- return c.JSON(http.StatusOK, res)
-}
-
-func handleWasmCompile(c echo.Context) error {
- var req wasmCompileRequestData
- if err := c.Bind(&req); err != nil {
- return newBadRequestError(err)
- }
- if err := req.validate(); err != nil {
- return newBadRequestError(err)
- }
-
- res := execWasmCompile(
- c.Request().Context(),
- req.CodeHash,
- req.maxDuration(),
- )
-
- return c.JSON(http.StatusOK, res)
-}
-
-func handleTestRun(c echo.Context) error {
- var req testRunRequestData
- if err := c.Bind(&req); err != nil {
- return newBadRequestError(err)
- }
- if err := req.validate(); err != nil {
- return newBadRequestError(err)
- }
-
- res := execTestRun(
- c.Request().Context(),
- req.CodeHash,
req.Stdin,
req.maxDuration(),
)
diff --git a/worker/swift/main.go b/worker/swift/main.go
index 02a0a6a..21a9464 100644
--- a/worker/swift/main.go
+++ b/worker/swift/main.go
@@ -18,9 +18,7 @@ func main() {
e.Use(middleware.Logger())
e.Use(middleware.Recover())
- e.POST("/api/swiftc", handleSwiftCompile)
- e.POST("/api/wasmc", handleWasmCompile)
- e.POST("/api/testrun", handleTestRun)
+ e.POST("/exec", handleExec)
if err := e.Start(":80"); err != http.ErrServerClosed {
log.Fatal(err)
diff --git a/worker/swift/models.go b/worker/swift/models.go
index 4a318d0..f00265e 100644
--- a/worker/swift/models.go
+++ b/worker/swift/models.go
@@ -17,70 +17,30 @@ var (
errInvalidMaxDuration = errors.New("'max_duration_ms' must be positive")
)
-type swiftCompileRequestData struct {
- MaxDurationMilliseconds int `json:"max_duration_ms"`
+type execRequestData struct {
Code string `json:"code"`
CodeHash string `json:"code_hash"`
+ Stdin string `json:"stdin"`
+ MaxDurationMilliseconds int `json:"max_duration_ms"`
}
-func (req *swiftCompileRequestData) maxDuration() time.Duration {
+func (req *execRequestData) maxDuration() time.Duration {
return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond
}
-func (req *swiftCompileRequestData) validate() error {
+func (req *execRequestData) validate() error {
if req.MaxDurationMilliseconds <= 0 {
return errInvalidMaxDuration
}
return nil
}
-type swiftCompileResponseData struct {
+type execResponseData struct {
Status string `json:"status"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
}
-type wasmCompileRequestData struct {
- MaxDurationMilliseconds int `json:"max_duration_ms"`
- CodeHash string `json:"code_hash"`
-}
-
-type wasmCompileResponseData struct {
- Status string `json:"status"`
- Stdout string `json:"stdout"`
- Stderr string `json:"stderr"`
-}
-
-func (req *wasmCompileRequestData) maxDuration() time.Duration {
- return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond
-}
-
-func (req *wasmCompileRequestData) validate() error {
- if req.MaxDurationMilliseconds <= 0 {
- return errInvalidMaxDuration
- }
- return nil
-}
-
-type testRunRequestData struct {
- MaxDurationMilliseconds int `json:"max_duration_ms"`
- CodeHash string `json:"code_hash"`
- Stdin string `json:"stdin"`
-}
-
-func (req *testRunRequestData) maxDuration() time.Duration {
- return time.Duration(req.MaxDurationMilliseconds) * time.Millisecond
-}
-
-func (req *testRunRequestData) validate() error {
- if req.MaxDurationMilliseconds <= 0 {
- return errInvalidMaxDuration
- }
- return nil
-}
-
-type testRunResponseData struct {
- Status string `json:"status"`
- Stdout string `json:"stdout"`
- Stderr string `json:"stderr"`
+func (res *execResponseData) success() bool {
+ return res.Status == resultSuccess
}