Files
buildx/dap/conn.go
T
Jonathan A. Sternberg 42599a7d49 dap: add debug adapter implementation
Adds a simple implementation of the debug adapter that supports the very
basics of a debug adapter.

It supports the launch request, the configuration done request, the
creation of threads, stopping, resuming, and disconnecting from server.

It does not support custom breakpoints, stack traces, or variable
inspection yet. These are planned to be added in the future.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-30 10:51:20 -05:00

110 lines
2.0 KiB
Go

package dap
import (
"bufio"
"context"
"io"
"sync"
"github.com/google/go-dap"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
type Conn interface {
SendMsg(m dap.Message) error
RecvMsg(ctx context.Context) (dap.Message, error)
io.Closer
}
type conn struct {
recvCh <-chan dap.Message
sendCh chan<- dap.Message
ctx context.Context
cancel context.CancelCauseFunc
eg *errgroup.Group
once sync.Once
}
func NewConn(rd io.Reader, wr io.Writer) Conn {
recvCh := make(chan dap.Message, 100)
sendCh := make(chan dap.Message, 100)
errCh := make(chan error, 1)
// Reader input may never close so this is an orphaned goroutine.
// It's ok if it does actually close but not necessary for the
// proper functioning of this connection.
//
// The reason this might not close is because stdin close is controlled
// by the OS and can't be closed from within the program.
go func() {
defer close(errCh)
defer close(recvCh)
rd := bufio.NewReader(rd)
for {
m, err := dap.ReadProtocolMessage(rd)
if err != nil {
if !errors.Is(err, io.EOF) {
// TODO: not actually using this yet
errCh <- err
}
return
}
recvCh <- m
}
}()
eg, _ := errgroup.WithContext(context.Background())
eg.Go(func() error {
for m := range sendCh {
if err := dap.WriteProtocolMessage(wr, m); err != nil {
return err
}
}
return nil
})
ctx, cancel := context.WithCancelCause(context.Background())
return &conn{
recvCh: recvCh,
sendCh: sendCh,
ctx: ctx,
cancel: cancel,
eg: eg,
}
}
func (c *conn) SendMsg(m dap.Message) error {
select {
case c.sendCh <- m:
return nil
default:
return errors.New("send channel full")
}
}
func (c *conn) RecvMsg(ctx context.Context) (dap.Message, error) {
select {
case m, ok := <-c.recvCh:
if !ok {
return nil, io.EOF
}
return m, nil
case <-ctx.Done():
return nil, context.Cause(ctx)
case <-c.ctx.Done():
return nil, c.ctx.Err()
}
}
func (c *conn) Close() error {
c.cancel(context.Canceled)
c.once.Do(func() {
close(c.sendCh)
})
return c.eg.Wait()
}