Skip to content

Fix Debug* gRPC function calls implementation #2672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Inlined function
  • Loading branch information
cmaglie committed Aug 7, 2024
commit ab34688fe1166ef5833faf76d30f403e3a02df47
89 changes: 71 additions & 18 deletions commands/service_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,30 @@ import (

"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"

"fmt"
"io"
"time"

"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
paths "github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
)

// Debug returns a stream response that can be used to fetch data from the
// target. The first message passed through the `Debug` request must
// contain DebugRequest configuration params, not data.
func (s *arduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer) error {
// Utility functions
syncSend := NewSynchronizedSend(stream.Send)
sendResult := func(res *rpc.DebugResponse_Result) error {
return syncSend.Send(&rpc.DebugResponse{Message: &rpc.DebugResponse_Result_{Result: res}})
}
sendData := func(data []byte) {
_ = syncSend.Send(&rpc.DebugResponse{Message: &rpc.DebugResponse_Data{Data: data}})
}

// Grab the first message
msg, err := stream.Recv()
if err != nil {
Expand All @@ -42,24 +60,59 @@ func (s *arduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer)
// Launch debug recipe attaching stdin and out to grpc streaming
signalChan := make(chan os.Signal)
defer close(signalChan)
outStream := feedStreamTo(func(data []byte) {
stream.Send(&rpc.DebugResponse{Message: &rpc.DebugResponse_Data{
Data: data,
}})
outStream := feedStreamTo(sendData)
defer outStream.Close()
inStream := consumeStreamFrom(func() ([]byte, error) {
command, err := stream.Recv()
if command.GetSendInterrupt() {
signalChan <- os.Interrupt
}
return command.GetData(), err
})
resp, debugErr := Debug(stream.Context(), req,
consumeStreamFrom(func() ([]byte, error) {
command, err := stream.Recv()
if command.GetSendInterrupt() {
signalChan <- os.Interrupt
}
return command.GetData(), err
}),
outStream,
signalChan)
outStream.Close()
if debugErr != nil {
return debugErr

pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
if err != nil {
return err
}
defer release()

// Exec debugger
commandLine, err := getCommandLine(req, pme)
if err != nil {
return err
}
entry := logrus.NewEntry(logrus.StandardLogger())
for i, param := range commandLine {
entry = entry.WithField(fmt.Sprintf("param%d", i), param)
}
entry.Debug("Executing debugger")
cmd, err := paths.NewProcess(pme.GetEnvVarsForSpawnedProcess(), commandLine...)
if err != nil {
return &cmderrors.FailedDebugError{Message: i18n.Tr("Cannot execute debug tool"), Cause: err}
}
in, err := cmd.StdinPipe()
if err != nil {
return sendResult(&rpc.DebugResponse_Result{Error: err.Error()})
}
defer in.Close()
cmd.RedirectStdoutTo(io.Writer(outStream))
cmd.RedirectStderrTo(io.Writer(outStream))
if err := cmd.Start(); err != nil {
return sendResult(&rpc.DebugResponse_Result{Error: err.Error()})
}

go func() {
for sig := range signalChan {
cmd.Signal(sig)
}
}()
go func() {
io.Copy(in, inStream)
time.Sleep(time.Second)
cmd.Kill()
}()
if err := cmd.Wait(); err != nil {
return sendResult(&rpc.DebugResponse_Result{Error: err.Error()})
}
return stream.Send(resp)
return sendResult(&rpc.DebugResponse_Result{})
}
94 changes: 0 additions & 94 deletions commands/service_debug_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,111 +16,17 @@
package commands

import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"time"

"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
)

// Debug command launches a debug tool for a sketch.
// It also implements streams routing:
// gRPC In -> tool stdIn
// grpc Out <- tool stdOut
// grpc Out <- tool stdErr
// It also implements tool process lifecycle management
func Debug(ctx context.Context, req *rpc.GetDebugConfigRequest, inStream io.Reader, out io.Writer, interrupt <-chan os.Signal) (*rpc.DebugResponse, error) {

// Get debugging command line to run debugger
pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
if err != nil {
return nil, err
}
defer release()

commandLine, err := getCommandLine(req, pme)
if err != nil {
return nil, err
}

for i, arg := range commandLine {
fmt.Printf("%2d: %s\n", i, arg)
}

// Run Tool
entry := logrus.NewEntry(logrus.StandardLogger())
for i, param := range commandLine {
entry = entry.WithField(fmt.Sprintf("param%d", i), param)
}
entry.Debug("Executing debugger")

cmd, err := paths.NewProcess(pme.GetEnvVarsForSpawnedProcess(), commandLine...)
if err != nil {
return nil, &cmderrors.FailedDebugError{Message: i18n.Tr("Cannot execute debug tool"), Cause: err}
}

// Get stdIn pipe from tool
in, err := cmd.StdinPipe()
if err != nil {
return &rpc.DebugResponse{Message: &rpc.DebugResponse_Result_{
Result: &rpc.DebugResponse_Result{Error: err.Error()},
}}, nil
}
defer in.Close()

// Merge tool StdOut and StdErr to stream them in the io.Writer passed stream
cmd.RedirectStdoutTo(out)
cmd.RedirectStderrTo(out)

// Start the debug command
if err := cmd.Start(); err != nil {
return &rpc.DebugResponse{Message: &rpc.DebugResponse_Result_{
Result: &rpc.DebugResponse_Result{Error: err.Error()},
}}, nil
}

if interrupt != nil {
go func() {
for {
sig, ok := <-interrupt
if !ok {
break
}
cmd.Signal(sig)
}
}()
}

go func() {
// Copy data from passed inStream into command stdIn
io.Copy(in, inStream)
// In any case, try process termination after a second to avoid leaving
// zombie process.
time.Sleep(time.Second)
cmd.Kill()
}()

// Wait for process to finish
if err := cmd.Wait(); err != nil {
return &rpc.DebugResponse{Message: &rpc.DebugResponse_Result_{
Result: &rpc.DebugResponse_Result{Error: err.Error()},
}}, nil
}
return &rpc.DebugResponse{Message: &rpc.DebugResponse_Result_{
Result: &rpc.DebugResponse_Result{},
}}, nil
}

// getCommandLine compose a debug command represented by a core recipe
func getCommandLine(req *rpc.GetDebugConfigRequest, pme *packagemanager.Explorer) ([]string, error) {
debugInfo, err := getDebugProperties(req, pme, false)
Expand Down