Skip to content

Commit 13f87dd

Browse files
committed
Forward os.interrupt (aka CTRL-C) signal to the gdb process
This a challenging problem because we must wait on both an io.Read(...) and a channel-read but, unfortunately, go native select can wait only on channels. To overcome this limitation I had to resort to a conditional variable and write some boilerplate code to make everything synchronized.
1 parent d14711e commit 13f87dd

File tree

1 file changed

+90
-18
lines changed

1 file changed

+90
-18
lines changed

commands/service_debug.go

+90-18
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,38 @@ package commands
1818
import (
1919
"context"
2020
"errors"
21+
"fmt"
22+
"io"
2123
"os"
2224
"path/filepath"
2325
"runtime"
26+
"sync"
2427
"sync/atomic"
25-
26-
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
27-
"github.com/arduino/arduino-cli/internal/i18n"
28-
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
29-
"google.golang.org/grpc/metadata"
30-
31-
"fmt"
32-
"io"
3328
"time"
3429

3530
"github.com/arduino/arduino-cli/commands/cmderrors"
3631
"github.com/arduino/arduino-cli/commands/internal/instances"
32+
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
33+
"github.com/arduino/arduino-cli/internal/i18n"
34+
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3735
paths "github.com/arduino/go-paths-helper"
36+
"github.com/djherbis/buffer"
37+
"github.com/djherbis/nio/v3"
3838
"github.com/sirupsen/logrus"
39+
"google.golang.org/grpc/metadata"
3940
)
4041

4142
type debugServer struct {
4243
ctx context.Context
4344
req atomic.Pointer[rpc.GetDebugConfigRequest]
4445
in io.Reader
46+
inSignal bool
47+
inData bool
48+
inEvent *sync.Cond
49+
inLock sync.Mutex
4550
out io.Writer
4651
resultCB func(*rpc.DebugResponse_Result)
52+
done chan bool
4753
}
4854

4955
func (s *debugServer) Send(resp *rpc.DebugResponse) error {
@@ -54,6 +60,7 @@ func (s *debugServer) Send(resp *rpc.DebugResponse) error {
5460
}
5561
if res := resp.GetResult(); res != nil {
5662
s.resultCB(res)
63+
s.close()
5764
}
5865
return nil
5966
}
@@ -62,12 +69,33 @@ func (s *debugServer) Recv() (r *rpc.DebugRequest, e error) {
6269
if conf := s.req.Swap(nil); conf != nil {
6370
return &rpc.DebugRequest{DebugRequest: conf}, nil
6471
}
65-
buff := make([]byte, 4096)
66-
n, err := s.in.Read(buff)
67-
if err != nil {
68-
return nil, err
72+
73+
s.inEvent.L.Lock()
74+
for !s.inSignal && !s.inData {
75+
s.inEvent.Wait()
76+
}
77+
defer s.inEvent.L.Unlock()
78+
79+
if s.inSignal {
80+
s.inSignal = false
81+
return &rpc.DebugRequest{SendInterrupt: true}, nil
82+
}
83+
84+
if s.inData {
85+
s.inData = false
86+
buff := make([]byte, 4096)
87+
n, err := s.in.Read(buff)
88+
if err != nil {
89+
return nil, err
90+
}
91+
return &rpc.DebugRequest{Data: buff[:n]}, nil
6992
}
70-
return &rpc.DebugRequest{Data: buff[:n]}, nil
93+
94+
panic("invalid state in debug")
95+
}
96+
97+
func (s *debugServer) close() {
98+
close(s.done)
7199
}
72100

73101
func (s *debugServer) Context() context.Context { return s.ctx }
@@ -80,7 +108,7 @@ func (s *debugServer) SetTrailer(metadata.MD) {}
80108
// DebugServerToStreams creates a debug server that proxies the data to the given io streams.
81109
// The GetDebugConfigRequest is used to configure the debbuger. sig is a channel that can be
82110
// used to send os.Interrupt to the debug process. resultCB is a callback function that will
83-
// receive the Debug result.
111+
// receive the Debug result and closes the debug server.
84112
func DebugServerToStreams(
85113
ctx context.Context,
86114
req *rpc.GetDebugConfigRequest,
@@ -93,8 +121,45 @@ func DebugServerToStreams(
93121
in: in,
94122
out: out,
95123
resultCB: resultCB,
124+
done: make(chan bool),
96125
}
126+
serverIn, clientOut := nio.Pipe(buffer.New(32 * 1024))
127+
server.in = serverIn
128+
server.inEvent = sync.NewCond(&server.inLock)
97129
server.req.Store(req)
130+
go func() {
131+
for {
132+
select {
133+
case <-sig:
134+
server.inEvent.L.Lock()
135+
server.inSignal = true
136+
server.inEvent.Broadcast()
137+
server.inEvent.L.Unlock()
138+
case <-server.done:
139+
return
140+
}
141+
}
142+
}()
143+
go func() {
144+
defer clientOut.Close()
145+
buff := make([]byte, 4096)
146+
for {
147+
n, readErr := in.Read(buff)
148+
149+
server.inEvent.L.Lock()
150+
var writeErr error
151+
if readErr == nil {
152+
_, writeErr = clientOut.Write(buff[:n])
153+
}
154+
server.inData = true
155+
server.inEvent.Broadcast()
156+
server.inEvent.L.Unlock()
157+
if readErr != nil || writeErr != nil {
158+
// exit on error
159+
return
160+
}
161+
}
162+
}()
98163
return server
99164
}
100165

@@ -128,11 +193,18 @@ func (s *arduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer)
128193
outStream := feedStreamTo(sendData)
129194
defer outStream.Close()
130195
inStream := consumeStreamFrom(func() ([]byte, error) {
131-
command, err := stream.Recv()
132-
if command.GetSendInterrupt() {
133-
signalChan <- os.Interrupt
196+
for {
197+
req, err := stream.Recv()
198+
if err != nil {
199+
return nil, err
200+
}
201+
if req.GetSendInterrupt() {
202+
signalChan <- os.Interrupt
203+
}
204+
if data := req.GetData(); len(data) > 0 {
205+
return data, nil
206+
}
134207
}
135-
return command.GetData(), err
136208
})
137209

138210
pme, release, err := instances.GetPackageManagerExplorer(debugConfReq.GetInstance())

0 commit comments

Comments
 (0)