feat(executor/local): simple local executor without limits
This commit is contained in:
parent
38f11788a0
commit
c3f7b0fa2b
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
35
internal/executor/local/buffer.go
Normal file
35
internal/executor/local/buffer.go
Normal 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()
|
||||
}
|
127
internal/executor/local/executor.go
Normal file
127
internal/executor/local/executor.go
Normal 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
|
||||
}
|
9
internal/executor/local/meta.go
Normal file
9
internal/executor/local/meta.go
Normal 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{})
|
||||
}
|
Loading…
Reference in New Issue
Block a user