From 9fe356feb5ada200f94bfa25a5c01d3133379949 Mon Sep 17 00:00:00 2001 From: criyle Date: Mon, 5 Feb 2024 11:55:58 +0000 Subject: [PATCH] shell: decouple terminal with grpc via stream interface --- cmd/go-judge-shell/grpc.go | 273 ++++++++++++++++++++++ cmd/go-judge-shell/shell.go | 157 ++++--------- cmd/go-judge-shell/shell_linux.go | 20 +- cmd/go-judge-shell/shell_other.go | 4 +- cmd/go-judge-shell/util.go | 11 + cmd/go-judge/grpc_executor/grpc.go | 14 +- cmd/go-judge/grpc_executor/grpc_stream.go | 14 +- cmd/go-judge/model/model.go | 58 ++++- worker/model.go | 9 +- 9 files changed, 410 insertions(+), 150 deletions(-) create mode 100644 cmd/go-judge-shell/grpc.go create mode 100644 cmd/go-judge-shell/util.go diff --git a/cmd/go-judge-shell/grpc.go b/cmd/go-judge-shell/grpc.go new file mode 100644 index 0000000..43a8516 --- /dev/null +++ b/cmd/go-judge-shell/grpc.go @@ -0,0 +1,273 @@ +package main + +import ( + "context" + "errors" + "log" + "os" + "strings" + + "github.com/criyle/go-judge/cmd/go-judge/model" + "github.com/criyle/go-judge/cmd/go-judge/stream" + "github.com/criyle/go-judge/pb" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +var _ Stream = &grpcWrapper{} + +type grpcWrapper struct { + sc pb.Executor_ExecStreamClient +} + +func newGrpc(args []string, srvAddr *string) Stream { + token := os.Getenv("TOKEN") + opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + if token != "" { + opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token))) + } + conn, err := grpc.Dial(*srvAddr, opts...) + if err != nil { + log.Fatalln("client", err) + } + client := pb.NewExecutorClient(conn) + sc, err := client.ExecStream(context.TODO()) + if err != nil { + log.Fatalln("exec_stream", err) + } + log.Println("start", args) + return &grpcWrapper{sc: sc} +} + +func (w *grpcWrapper) Send(req *stream.Request) error { + switch { + case req.Request != nil: + w.sc.Send(convertPBRequest(req.Request)) + case req.Input != nil: + w.sc.Send(&pb.StreamRequest{Request: &pb.StreamRequest_ExecInput{ExecInput: &pb.StreamRequest_Input{ + Name: req.Input.Name, + Content: req.Input.Content, + }}}) + case req.Resize != nil: + w.sc.Send(&pb.StreamRequest{Request: &pb.StreamRequest_ExecResize{ExecResize: &pb.StreamRequest_Resize{ + Name: req.Resize.Name, + Rows: uint32(req.Resize.Rows), + Cols: uint32(req.Resize.Cols), + X: uint32(req.Resize.X), + Y: uint32(req.Resize.Y), + }}}) + case req.Cancel != nil: + w.sc.Send(&pb.StreamRequest{Request: &pb.StreamRequest_ExecCancel{}}) + default: + return errors.New("send: unknown operation") + } + return nil +} + +func (w *grpcWrapper) Recv() (*stream.Response, error) { + resp, err := w.sc.Recv() + if err != nil { + return nil, err + } + switch i := resp.Response.(type) { + case *pb.StreamResponse_ExecOutput: + return &stream.Response{Output: &stream.OutputResponse{ + Name: i.ExecOutput.Name, + Content: i.ExecOutput.Content, + }}, nil + case *pb.StreamResponse_ExecResponse: + return &stream.Response{Response: &model.Response{ + RequestID: i.ExecResponse.RequestID, + Results: convertPBResult(i.ExecResponse.Results), + ErrorMsg: i.ExecResponse.Error, + }}, nil + } + return nil, errors.New("recv: invalid response") +} + +func convertPBResult(res []*pb.Response_Result) []model.Result { + var ret []model.Result + for _, r := range res { + ret = append(ret, model.Result{ + Status: model.Status(r.Status), + ExitStatus: int(r.ExitStatus), + Error: r.Error, + Time: r.Time, + RunTime: r.RunTime, + Memory: r.Memory, + Files: convertFiles(r.Files), + Buffs: r.Files, + FileIDs: r.FileIDs, + FileError: convertPBFileError(r.FileError), + }) + } + return ret +} + +func convertFiles(buf map[string][]byte) map[string]string { + ret := make(map[string]string, len(buf)) + for k, v := range buf { + ret[k] = byteArrayToString(v) + } + return ret +} + +func convertPBRequest(req *model.Request) *pb.StreamRequest { + ret := &pb.StreamRequest{ + Request: &pb.StreamRequest_ExecRequest{ + ExecRequest: &pb.Request{ + RequestID: req.RequestID, + Cmd: convertPBCmd(req.Cmd), + PipeMapping: convertPBPipeMapping(req.PipeMapping), + }, + }, + } + return ret +} + +func convertPBFileError(fe []*pb.Response_FileError) []model.FileError { + var ret []model.FileError + for _, v := range fe { + ret = append(ret, model.FileError{ + Name: v.Name, + Type: model.FileErrorType(v.Type), + Message: v.Message, + }) + } + return ret +} + +func convertPBCmd(cmd []model.Cmd) []*pb.Request_CmdType { + var ret []*pb.Request_CmdType + for _, c := range cmd { + ret = append(ret, &pb.Request_CmdType{ + Args: c.Args, + Env: c.Env, + Tty: c.TTY, + Files: convertPBFiles(c.Files), + CpuTimeLimit: c.CPULimit, + ClockTimeLimit: c.ClockLimit, + MemoryLimit: c.MemoryLimit, + StackLimit: c.StackLimit, + ProcLimit: c.ProcLimit, + CpuRateLimit: c.CPURateLimit, + CpuSetLimit: c.CPUSetLimit, + DataSegmentLimit: c.DataSegmentLimit, + AddressSpaceLimit: c.AddressSpaceLimit, + CopyIn: convertPBCopyIn(c.CopyIn), + CopyOut: convertPBCopyOut(c.CopyOut), + CopyOutCached: convertPBCopyOut(c.CopyOutCached), + CopyOutMax: c.CopyOutMax, + CopyOutDir: c.CopyOutDir, + Symlinks: convertSymlink(c.CopyIn), + }) + } + return ret +} + +func convertPBCopyIn(copyIn map[string]model.CmdFile) map[string]*pb.Request_File { + rt := make(map[string]*pb.Request_File, len(copyIn)) + for k, i := range copyIn { + if i.Symlink != nil { + continue + } + rt[k] = convertPBFile(i) + } + return rt +} + +func convertPBCopyOut(copyOut []string) []*pb.Request_CmdCopyOutFile { + rt := make([]*pb.Request_CmdCopyOutFile, len(copyOut)) + for _, n := range copyOut { + optional := false + if strings.HasSuffix(n, "?") { + optional = true + n = strings.TrimSuffix(n, "?") + } + rt = append(rt, &pb.Request_CmdCopyOutFile{ + Name: n, + Optional: optional, + }) + } + return rt +} + +func convertSymlink(copyIn map[string]model.CmdFile) map[string]string { + ret := make(map[string]string) + for k, v := range copyIn { + if v.Symlink == nil { + continue + } + ret[k] = *v.Symlink + } + return ret +} + +func convertPBFiles(files []*model.CmdFile) []*pb.Request_File { + var ret []*pb.Request_File + for _, f := range files { + if f == nil { + ret = append(ret, nil) + } else { + ret = append(ret, convertPBFile(*f)) + } + } + return ret +} + +func convertPBFile(i model.CmdFile) *pb.Request_File { + switch { + case i.Src != nil: + return &pb.Request_File{File: &pb.Request_File_Local{Local: &pb.Request_LocalFile{Src: *i.Src}}} + case i.Content != nil: + s := strToBytes(*i.Content) + return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}} + case i.FileID != nil: + return &pb.Request_File{File: &pb.Request_File_Cached{Cached: &pb.Request_CachedFile{FileID: *i.FileID}}} + case i.Name != nil && i.Max != nil: + return &pb.Request_File{File: &pb.Request_File_Pipe{Pipe: &pb.Request_PipeCollector{Name: *i.Name, Max: *i.Max, Pipe: i.Pipe}}} + case i.StreamIn != nil: + return &pb.Request_File{File: &pb.Request_File_StreamIn{StreamIn: &pb.Request_StreamInput{Name: *i.StreamIn}}} + case i.StreamOut != nil: + return &pb.Request_File{File: &pb.Request_File_StreamOut{StreamOut: &pb.Request_StreamOutput{Name: *i.StreamOut}}} + } + return nil +} + +func convertPBPipeMapping(pm []model.PipeMap) []*pb.Request_PipeMap { + var ret []*pb.Request_PipeMap + for _, p := range pm { + ret = append(ret, &pb.Request_PipeMap{ + In: convertPBPipeIndex(p.In), + Out: convertPBPipeIndex(p.Out), + Name: p.Name, + Proxy: p.Proxy, + Max: uint64(p.Max), + }) + } + return ret +} + +func convertPBPipeIndex(pi model.PipeIndex) *pb.Request_PipeMap_PipeIndex { + return &pb.Request_PipeMap_PipeIndex{Index: int32(pi.Index), Fd: int32(pi.Fd)} +} + +type tokenAuth struct { + token string +} + +func newTokenAuth(token string) credentials.PerRPCCredentials { + return &tokenAuth{token: token} +} + +// Return value is mapped to request headers. +func (t *tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + t.token, + }, nil +} + +func (*tokenAuth) RequireTransportSecurity() bool { + return false +} diff --git a/cmd/go-judge-shell/shell.go b/cmd/go-judge-shell/shell.go index 568e566..20f2ae3 100644 --- a/cmd/go-judge-shell/shell.go +++ b/cmd/go-judge-shell/shell.go @@ -1,7 +1,6 @@ package main import ( - "context" "flag" "fmt" "io" @@ -11,11 +10,9 @@ import ( "syscall" "time" - "github.com/criyle/go-judge/pb" + "github.com/criyle/go-judge/cmd/go-judge/model" + "github.com/criyle/go-judge/cmd/go-judge/stream" "golang.org/x/term" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" ) var ( @@ -36,75 +33,46 @@ var env = []string{ "TERM=" + os.Getenv("TERM"), } +type Stream interface { + Send(*stream.Request) error + Recv() (*stream.Response, error) +} + func main() { flag.Parse() args := flag.Args() if len(args) == 0 { args = []string{"/bin/bash"} } + w := newGrpc(args, srvAddr) + r, err := run(w, args) + log.Printf("finished: %+v %v", r, err) +} - token := os.Getenv("TOKEN") - opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} - if token != "" { - opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token))) - } - conn, err := grpc.Dial(*srvAddr, opts...) - - if err != nil { - log.Fatalln("client", err) - } - client := pb.NewExecutorClient(conn) - sc, err := client.ExecStream(context.TODO()) - if err != nil { - log.Fatalln("ExecStream", err) - } - log.Println("Starts", args) - r, err := run(sc, args) - log.Println("ExecStream Finished", r, err) +func stringPointer(s string) *string { + return &s } -func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { - req := &pb.Request{ - Cmd: []*pb.Request_CmdType{{ +func run(sc Stream, args []string) (*model.Response, error) { + req := model.Request{ + Cmd: []model.Cmd{{ Args: args, Env: env, - Files: []*pb.Request_File{ - { - File: &pb.Request_File_StreamIn{ - StreamIn: &pb.Request_StreamInput{ - Name: "stdin", - }, - }, - }, - { - File: &pb.Request_File_StreamOut{ - StreamOut: &pb.Request_StreamOutput{ - Name: "stdout", - }, - }, - }, - { - File: &pb.Request_File_StreamOut{ - StreamOut: &pb.Request_StreamOutput{ - Name: "stderr", - }, - }, - }, + Files: []*model.CmdFile{ + {StreamIn: stringPointer("stdin")}, + {StreamOut: stringPointer("stdout")}, + {StreamOut: stringPointer("stderr")}, }, - CpuTimeLimit: uint64(cpuLimit), - ClockTimeLimit: uint64(sessionLimit), - MemoryLimit: memoryLimit, - ProcLimit: procLimit, - Tty: true, + CPULimit: uint64(cpuLimit.Nanoseconds()), + ClockLimit: uint64(sessionLimit.Nanoseconds()), + MemoryLimit: memoryLimit, + ProcLimit: procLimit, + TTY: true, }}, } - err := sc.Send(&pb.StreamRequest{ - Request: &pb.StreamRequest_ExecRequest{ - ExecRequest: req, - }, - }) + err := sc.Send(&stream.Request{Request: &req}) if err != nil { - return nil, fmt.Errorf("ExecStream Send request %v", err) + return nil, fmt.Errorf("send request: %w", err) } // Set stdin in raw mode. @@ -115,7 +83,7 @@ func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. // pump msg - sendCh := make(chan *pb.StreamRequest, 64) + sendCh := make(chan *stream.Request, 64) defer close(sendCh) go func() { for r := range sendCh { @@ -134,20 +102,18 @@ func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { for { n, err := os.Stdin.Read(buf) if err == io.EOF { - sendCh <- &pb.StreamRequest{ - Request: &pb.StreamRequest_ExecInput{ - ExecInput: &pb.StreamRequest_Input{ - Name: "stdin", - Content: []byte("\004"), - }, + sendCh <- &stream.Request{ + Input: &stream.InputRequest{ + Name: "stdin", + Content: []byte("\004"), }, } continue } if n == 1 && buf[0] == 3 { if forceQuit { - sendCh <- &pb.StreamRequest{ - Request: &pb.StreamRequest_ExecCancel{}, + sendCh <- &stream.Request{ + Cancel: &struct{}{}, } } forceQuit = true @@ -159,12 +125,10 @@ func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { log.Println("stdin", err) return } - sendCh <- &pb.StreamRequest{ - Request: &pb.StreamRequest_ExecInput{ - ExecInput: &pb.StreamRequest_Input{ - Name: "stdin", - Content: buf[:n], - }, + sendCh <- &stream.Request{ + Input: &stream.InputRequest{ + Name: "stdin", + Content: buf[:n], }, } } @@ -175,12 +139,10 @@ func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { // pump ^C go func() { for range sigCh { - sendCh <- &pb.StreamRequest{ - Request: &pb.StreamRequest_ExecInput{ - ExecInput: &pb.StreamRequest_Input{ - Name: "stdin", - Content: []byte("\003"), - }, + sendCh <- &stream.Request{ + Input: &stream.InputRequest{ + Name: "stdin", + Content: []byte("\003"), }, } } @@ -193,37 +155,18 @@ func run(sc pb.Executor_ExecStreamClient, args []string) (*pb.Response, error) { for { sr, err := sc.Recv() if err != nil { - return nil, fmt.Errorf("ExecStream recv %v", err) + return nil, fmt.Errorf("recv: %w", err) } - switch sr := sr.Response.(type) { - case *pb.StreamResponse_ExecOutput: - switch sr.ExecOutput.Name { + switch { + case sr.Output != nil: + switch sr.Output.Name { case "stdout": - os.Stdout.Write(sr.ExecOutput.Content) + os.Stdout.Write(sr.Output.Content) case "stderr": - os.Stderr.Write(sr.ExecOutput.Content) + os.Stderr.Write(sr.Output.Content) } - case *pb.StreamResponse_ExecResponse: - return sr.ExecResponse, nil + case sr.Response != nil: + return sr.Response, nil } } } - -type tokenAuth struct { - token string -} - -func newTokenAuth(token string) credentials.PerRPCCredentials { - return &tokenAuth{token: token} -} - -// Return value is mapped to request headers. -func (t *tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) { - return map[string]string{ - "authorization": "Bearer " + t.token, - }, nil -} - -func (*tokenAuth) RequireTransportSecurity() bool { - return false -} diff --git a/cmd/go-judge-shell/shell_linux.go b/cmd/go-judge-shell/shell_linux.go index d49b594..258cb69 100644 --- a/cmd/go-judge-shell/shell_linux.go +++ b/cmd/go-judge-shell/shell_linux.go @@ -7,10 +7,10 @@ import ( "syscall" "github.com/creack/pty" - "github.com/criyle/go-judge/pb" + "github.com/criyle/go-judge/cmd/go-judge/stream" ) -func handleSizeChange(sendCh chan<- *pb.StreamRequest) { +func handleSizeChange(sendCh chan *stream.Request) { // pump resize ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGWINCH) @@ -21,15 +21,13 @@ func handleSizeChange(sendCh chan<- *pb.StreamRequest) { log.Println("get win size", err) return } - sendCh <- &pb.StreamRequest{ - Request: &pb.StreamRequest_ExecResize{ - ExecResize: &pb.StreamRequest_Resize{ - Name: "stdin", - Rows: uint32(winSize.Rows), - Cols: uint32(winSize.Cols), - X: uint32(winSize.X), - Y: uint32(winSize.Y), - }, + sendCh <- &stream.Request{ + Resize: &stream.ResizeRequest{ + Name: "stdin", + Rows: int(winSize.Rows), + Cols: int(winSize.Cols), + X: int(winSize.X), + Y: int(winSize.Y), }, } } diff --git a/cmd/go-judge-shell/shell_other.go b/cmd/go-judge-shell/shell_other.go index 728d58d..b9ed61a 100644 --- a/cmd/go-judge-shell/shell_other.go +++ b/cmd/go-judge-shell/shell_other.go @@ -2,7 +2,7 @@ package main -import "github.com/criyle/go-judge/pb" +import "github.com/criyle/go-judge/cmd/go-judge/stream" -func handleSizeChange(sendCh chan<- *pb.StreamRequest) { +func handleSizeChange(sendCh chan *stream.Request) { } diff --git a/cmd/go-judge-shell/util.go b/cmd/go-judge-shell/util.go new file mode 100644 index 0000000..d31b831 --- /dev/null +++ b/cmd/go-judge-shell/util.go @@ -0,0 +1,11 @@ +package main + +import "unsafe" + +func strToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +func byteArrayToString(buf []byte) string { + return *(*string)(unsafe.Pointer(&buf)) +} diff --git a/cmd/go-judge/grpc_executor/grpc.go b/cmd/go-judge/grpc_executor/grpc.go index 4134995..77a99bc 100644 --- a/cmd/go-judge/grpc_executor/grpc.go +++ b/cmd/go-judge/grpc_executor/grpc.go @@ -176,20 +176,18 @@ func convertPBRequest(r *pb.Request, srcPrefix []string) (req *worker.Request, e func convertPBPipeMap(p *pb.Request_PipeMap) worker.PipeMap { return worker.PipeMap{ - In: worker.PipeIndex{ - Index: int(p.GetIn().GetIndex()), - Fd: int(p.GetIn().GetFd()), - }, - Out: worker.PipeIndex{ - Index: int(p.GetOut().GetIndex()), - Fd: int(p.GetOut().GetFd()), - }, + In: convertPBPipeIndex(p.GetIn()), + Out: convertPBPipeIndex(p.GetOut()), Proxy: p.GetProxy(), Name: p.GetName(), Limit: worker.Size(p.Max), } } +func convertPBPipeIndex(p *pb.Request_PipeMap_PipeIndex) worker.PipeIndex { + return worker.PipeIndex{Index: int(p.GetIndex()), Fd: int(p.GetFd())} +} + func convertPBCmd(c *pb.Request_CmdType, srcPrefix []string) (cm worker.Cmd, err error) { cm = worker.Cmd{ Args: c.GetArgs(), diff --git a/cmd/go-judge/grpc_executor/grpc_stream.go b/cmd/go-judge/grpc_executor/grpc_stream.go index 0034db3..711438e 100644 --- a/cmd/go-judge/grpc_executor/grpc_stream.go +++ b/cmd/go-judge/grpc_executor/grpc_stream.go @@ -89,14 +89,8 @@ func convertPBStreamRequest(req *pb.Request) *model.Request { } for _, p := range req.PipeMapping { ret.PipeMapping = append(ret.PipeMapping, model.PipeMap{ - In: model.PipeIndex{ - Index: int(p.In.Index), - Fd: int(p.In.Fd), - }, - Out: model.PipeIndex{ - Index: int(p.Out.Index), - Fd: int(p.Out.Fd), - }, + In: convertPBStreamPipeIndex(p.In), + Out: convertPBStreamPipeIndex(p.Out), Max: int64(p.Max), Name: p.Name, Proxy: p.Proxy, @@ -105,6 +99,10 @@ func convertPBStreamRequest(req *pb.Request) *model.Request { return ret } +func convertPBStreamPipeIndex(pi *pb.Request_PipeMap_PipeIndex) model.PipeIndex { + return model.PipeIndex{Index: int(pi.Index), Fd: int(pi.Fd)} +} + func convertPBStreamFiles(files []*pb.Request_File) []*model.CmdFile { var rt []*model.CmdFile for _, f := range files { diff --git a/cmd/go-judge/model/model.go b/cmd/go-judge/model/model.go index 06beb30..19ab408 100644 --- a/cmd/go-judge/model/model.go +++ b/cmd/go-judge/model/model.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" @@ -11,6 +12,9 @@ import ( "github.com/criyle/go-judge/worker" ) +type FileError = envexec.FileError +type FileErrorType = envexec.FileErrorType + // CmdFile defines file from multiple source including local / memory / cached or pipe collector type CmdFile struct { Src *string `json:"src"` @@ -77,9 +81,14 @@ type Request struct { // Status offers JSON marshal for envexec.Status type Status envexec.Status +// String converts status to string +func (s Status) String() string { + return envexec.Status(s).String() +} + // MarshalJSON convert status into string func (s Status) MarshalJSON() ([]byte, error) { - return []byte("\"" + (envexec.Status)(s).String() + "\""), nil + return []byte("\"" + envexec.Status(s).String() + "\""), nil } // UnmarshalJSON convert string into status @@ -95,20 +104,49 @@ func (s *Status) UnmarshalJSON(b []byte) error { // Result defines single command result type Result struct { - Status Status `json:"status"` - ExitStatus int `json:"exitStatus"` - Error string `json:"error,omitempty"` - Time uint64 `json:"time"` - Memory uint64 `json:"memory"` - RunTime uint64 `json:"runTime"` - Files map[string]string `json:"files,omitempty"` - FileIDs map[string]string `json:"fileIds,omitempty"` - FileError []envexec.FileError `json:"fileError,omitempty"` + Status Status `json:"status"` + ExitStatus int `json:"exitStatus"` + Error string `json:"error,omitempty"` + Time uint64 `json:"time"` + Memory uint64 `json:"memory"` + RunTime uint64 `json:"runTime"` + Files map[string]string `json:"files,omitempty"` + FileIDs map[string]string `json:"fileIds,omitempty"` + FileError []FileError `json:"fileError,omitempty"` files []string Buffs map[string][]byte `json:"-"` } +func (r Result) String() string { + type Result struct { + Status Status + ExitStatus int + Error string + Time time.Duration + RunTime time.Duration + Memory envexec.Size + Files map[string]string + FileIDs map[string]string + FileError []FileError + } + d := Result{ + Status: r.Status, + ExitStatus: r.ExitStatus, + Error: r.Error, + Time: time.Duration(r.Time), + RunTime: time.Duration(r.RunTime), + Memory: envexec.Size(r.Memory), + Files: make(map[string]string), + FileIDs: r.FileIDs, + FileError: r.FileError, + } + for k, v := range r.Files { + d.Files[k] = "len:" + strconv.Itoa(len(v)) + } + return fmt.Sprintf("%+v", d) +} + // Response defines worker response for single request type Response struct { RequestID string `json:"requestId"` diff --git a/worker/model.go b/worker/model.go index 4669e58..0d13e09 100644 --- a/worker/model.go +++ b/worker/model.go @@ -13,6 +13,7 @@ type Size = envexec.Size type CmdCopyOutFile = envexec.CmdCopyOutFile type PipeMap = envexec.Pipe type PipeIndex = envexec.PipeIndex +type FileError = envexec.FileError // Cmd defines command and limits to start a program using in envexec type Cmd struct { @@ -57,10 +58,10 @@ type Result struct { Error string Time time.Duration RunTime time.Duration - Memory envexec.Size + Memory Size Files map[string]*os.File FileIDs map[string]string - FileError []envexec.FileError + FileError []FileError } // Response defines worker response for single request @@ -77,10 +78,10 @@ func (r Result) String() string { Error string Time time.Duration RunTime time.Duration - Memory envexec.Size + Memory Size Files map[string]string FileIDs map[string]string - FileError []envexec.FileError + FileError []FileError } d := Result{ Status: r.Status,