feat(executor/local): simple local executor without limits
Some checks failed
build / trigger-build-image (push) Blocked by required conditions
submodules sync / sync (push) Has been cancelled
build / build (push) Has been cancelled

This commit is contained in:
张泊明518370910136 2024-11-28 10:05:44 -05:00
parent 38f11788a0
commit c3f7b0fa2b
GPG Key ID: D47306D7062CDA9D
4 changed files with 172 additions and 0 deletions

View File

@ -2,6 +2,7 @@ package executors
import (
_ "github.com/joint-online-judge/JOJ3/internal/executor/dummy"
_ "github.com/joint-online-judge/JOJ3/internal/executor/local"
"github.com/joint-online-judge/JOJ3/internal/executor/sandbox"
)

View File

@ -0,0 +1,35 @@
package local
import (
"bytes"
"errors"
)
// LimitedBuffer wraps a bytes.Buffer and limits its size.
type LimitedBuffer struct {
buf *bytes.Buffer
maxSize int
}
// Write writes data to the buffer and checks the size limit.
func (lb *LimitedBuffer) Write(p []byte) (n int, err error) {
if lb.buf.Len()+len(p) > lb.maxSize {
// Truncate to fit within the limit
allowed := lb.maxSize - lb.buf.Len()
if allowed > 0 {
n, _ = lb.buf.Write(p[:allowed])
}
return n, errors.New("buffer size limit exceeded")
}
return lb.buf.Write(p)
}
// Bytes returns the buffer's content.
func (lb *LimitedBuffer) Bytes() []byte {
return lb.buf.Bytes()
}
// String returns the buffer's content as a string.
func (lb *LimitedBuffer) String() string {
return lb.buf.String()
}

View File

@ -0,0 +1,127 @@
package local
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
"time"
"github.com/criyle/go-judge/envexec"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
type Local struct{}
func (e *Local) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
var results []stage.ExecutorResult
for _, cmd := range cmds {
execCmd := exec.Command(cmd.Args[0], cmd.Args[1:]...) // #nosec G204
if len(cmd.Env) > 0 {
execCmd.Env = cmd.Env
}
if cmd.Stdin != nil {
if cmd.Stdin.Content != nil {
execCmd.Stdin = strings.NewReader(*cmd.Stdin.Content)
} else if cmd.Stdin.Src != nil {
file, err := os.Open(*cmd.Stdin.Src)
if err != nil {
return nil, fmt.Errorf("failed to open stdin file: %v", err)
}
defer file.Close()
execCmd.Stdin = file
}
}
var stdoutBuffer, stderrBuffer bytes.Buffer
execCmd.Stdout = &stdoutBuffer
execCmd.Stderr = &stderrBuffer
startTime := time.Now()
err := execCmd.Start()
if err != nil {
return nil, fmt.Errorf("failed to start command: %v", err)
}
err = execCmd.Wait()
endTime := time.Now()
runTime := endTime.Sub(startTime)
result := stage.ExecutorResult{
Status: stage.Status(envexec.StatusAccepted),
ExitStatus: 0,
Error: "",
RunTime: func() uint64 {
nanos := runTime.Nanoseconds()
if nanos < 0 {
return 0
}
return uint64(nanos)
}(),
Files: map[string]string{},
FileIDs: map[string]string{},
}
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
result.ExitStatus = exitErr.ExitCode()
result.Status = stage.Status(envexec.StatusNonzeroExitStatus)
result.Error = exitErr.Error()
} else {
result.Status = stage.Status(envexec.StatusInternalError)
result.Error = err.Error()
}
}
if cmd.Stdout != nil && cmd.Stdout.Name != nil {
result.Files[*cmd.Stdout.Name] = stdoutBuffer.String()
}
if cmd.Stderr != nil && cmd.Stderr.Name != nil {
result.Files[*cmd.Stderr.Name] = stderrBuffer.String()
}
if err := handleCopyOut(&result, cmd); err != nil {
result.Status = stage.Status(envexec.StatusFileError)
result.Error = err.Error()
}
results = append(results, result)
}
return results, nil
}
// Helper function to handle copyOut files
func handleCopyOut(result *stage.ExecutorResult, cmd stage.Cmd) error {
for _, filename := range cmd.CopyOut {
optional := false
if strings.HasSuffix(filename, "?") {
optional = true
filename = strings.TrimSuffix(filename, "?")
}
result.Files[filename] = ""
// Read file and add to result.Files
file, err := os.Open(filename)
if err != nil {
if !optional {
return err
}
continue
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
return err
}
result.Files[filename] = string(content)
}
return nil
}
func (e *Local) Cleanup() error {
return nil
}

View File

@ -0,0 +1,9 @@
package local
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "local"
func init() {
stage.RegisterExecutor(name, &Local{})
}