111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"maps"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/criyle/go-judge/pb"
|
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func (e *Sandbox) Run(ctx context.Context, cmds []stage.Cmd) ([]stage.ExecutorResult, error) {
|
|
var err error
|
|
if e.execClient == nil {
|
|
slog.Debug("create exec client", "server", e.execServer)
|
|
e.execClient, e.conn, err = createExecClient(e.execServer, e.token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// cannot use range loop since we need to change the value
|
|
for i := 0; i < len(cmds); i += 1 {
|
|
cmd := &cmds[i]
|
|
if cmd.CopyIn == nil {
|
|
cmd.CopyIn = make(map[string]stage.CmdFile)
|
|
}
|
|
for k, v := range cmd.CopyInCached {
|
|
if fileID, ok := e.cachedMap[v]; ok {
|
|
cmd.CopyIn[k] = stage.CmdFile{FileID: &fileID}
|
|
}
|
|
}
|
|
}
|
|
pbCmds, err := convertPBCmd(cmds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i, pbCmd := range pbCmds {
|
|
slog.Debug("sandbox execute", "i", i, "pbCmd size", proto.Size(pbCmd))
|
|
}
|
|
pbReq := &pb.Request{}
|
|
pbReq.SetCmd(pbCmds)
|
|
slog.Debug("sandbox execute", "pbReq size", proto.Size(pbReq))
|
|
callCtx, cancel := context.WithTimeout(ctx, execRPCTimeout(cmds))
|
|
defer cancel()
|
|
pbRet, err := e.execClient.Exec(callCtx, pbReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if pbRet.GetError() != "" {
|
|
return nil, fmt.Errorf("sandbox execute error: %s", pbRet.GetError())
|
|
}
|
|
results := convertPBResult(pbRet.GetResults())
|
|
for _, result := range results {
|
|
maps.Copy(e.cachedMap, result.FileIDs)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (e *Sandbox) Cleanup(ctx context.Context) error {
|
|
var cleanupErr error
|
|
for k, fileID := range e.cachedMap {
|
|
req := &pb.FileID{}
|
|
req.SetFileID(fileID)
|
|
callCtx, cancel := context.WithTimeout(ctx, rpcTimeoutMargin)
|
|
_, err := e.execClient.FileDelete(callCtx, req)
|
|
cancel()
|
|
if err != nil {
|
|
slog.Error("sandbox cleanup", "error", err)
|
|
cleanupErr = errors.Join(cleanupErr, err)
|
|
}
|
|
delete(e.cachedMap, k)
|
|
}
|
|
if e.conn != nil {
|
|
cleanupErr = errors.Join(cleanupErr, e.conn.Close())
|
|
e.conn = nil
|
|
e.execClient = nil
|
|
}
|
|
return cleanupErr
|
|
}
|
|
|
|
func execRPCTimeout(cmds []stage.Cmd) time.Duration {
|
|
var maxLimit uint64
|
|
for _, cmd := range cmds {
|
|
limit := cmd.ClockLimit
|
|
if limit == 0 {
|
|
// Match the local executor's default wall-clock allowance when only a
|
|
// CPU limit is specified.
|
|
if cmd.CPULimit > math.MaxUint64/2 {
|
|
limit = math.MaxUint64
|
|
} else {
|
|
limit = cmd.CPULimit * 2
|
|
}
|
|
}
|
|
if limit > maxLimit {
|
|
maxLimit = limit
|
|
}
|
|
}
|
|
|
|
margin := uint64(rpcTimeoutMargin)
|
|
if maxLimit > uint64(math.MaxInt64)-margin {
|
|
return time.Duration(math.MaxInt64)
|
|
}
|
|
// The bound above guarantees the conversion fits in time.Duration.
|
|
return time.Duration(maxLimit + margin) // #nosec G115
|
|
}
|