Skip to content

Commit 0c74726

Browse files
authored
Merge pull request #3279 from jsternberg/dap-step
dap: add stack traces with next and continue functionality
2 parents 3a3fc54 + 4f2e23a commit 0c74726

File tree

6 files changed

+644
-103
lines changed

6 files changed

+644
-103
lines changed

build/build.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ func toRepoOnly(in string) (string, error) {
314314
}
315315

316316
type (
317-
EvaluateFunc func(ctx context.Context, name string, c gateway.Client, res *gateway.Result) error
317+
EvaluateFunc func(ctx context.Context, name string, c gateway.Client, res *gateway.Result, opt Options) error
318318
Handler struct {
319319
Evaluate EvaluateFunc
320320
}
@@ -525,7 +525,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
525525

526526
// invoke custom evaluate handler if it is present
527527
if bh != nil && bh.Evaluate != nil {
528-
if err := bh.Evaluate(ctx, k, c, res); err != nil {
528+
if err := bh.Evaluate(ctx, k, c, res, opt); err != nil {
529529
return nil, err
530530
}
531531
} else if forceEval {

dap/adapter.go

Lines changed: 115 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package dap
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"fmt"
78
"io"
9+
"path"
810
"sync"
11+
"sync/atomic"
912

1013
"github.com/docker/buildx/build"
1114
"github.com/google/go-dap"
@@ -28,6 +31,9 @@ type Adapter[T any] struct {
2831
threads map[int]*thread
2932
threadsMu sync.RWMutex
3033
nextThreadID int
34+
35+
sourceMap sourceMap
36+
idPool *idPool
3137
}
3238

3339
func New[T any](cfg *build.InvokeConfig) *Adapter[T] {
@@ -38,6 +44,7 @@ func New[T any](cfg *build.InvokeConfig) *Adapter[T] {
3844
evaluateReqCh: make(chan *evaluateRequest),
3945
threads: make(map[int]*thread),
4046
nextThreadID: 1,
47+
idPool: new(idPool),
4148
}
4249
if cfg != nil {
4350
d.cfg = *cfg
@@ -131,7 +138,16 @@ func (d *Adapter[T]) Continue(c Context, req *dap.ContinueRequest, resp *dap.Con
131138
t := d.threads[req.Arguments.ThreadId]
132139
d.threadsMu.RUnlock()
133140

134-
t.Resume(c)
141+
t.Continue()
142+
return nil
143+
}
144+
145+
func (d *Adapter[T]) Next(c Context, req *dap.NextRequest, resp *dap.NextResponse) error {
146+
d.threadsMu.RLock()
147+
t := d.threads[req.Arguments.ThreadId]
148+
d.threadsMu.RUnlock()
149+
150+
t.Next()
135151
return nil
136152
}
137153

@@ -182,7 +198,7 @@ func (d *Adapter[T]) launch(c Context) {
182198
started := c.Go(func(c Context) {
183199
defer d.deleteThread(c, t)
184200
defer close(req.errCh)
185-
req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta, d.cfg)
201+
req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta, req.inputs)
186202
})
187203

188204
if !started {
@@ -197,8 +213,10 @@ func (d *Adapter[T]) newThread(ctx Context, name string) (t *thread) {
197213
d.threadsMu.Lock()
198214
id := d.nextThreadID
199215
t = &thread{
200-
id: id,
201-
name: name,
216+
id: id,
217+
name: name,
218+
sourceMap: &d.sourceMap,
219+
idPool: d.idPool,
202220
}
203221
d.threads[t.id] = t
204222
d.nextThreadID++
@@ -236,41 +254,43 @@ func (d *Adapter[T]) deleteThread(ctx Context, t *thread) {
236254
}
237255

238256
type evaluateRequest struct {
239-
name string
240-
c gateway.Client
241-
ref gateway.Reference
242-
meta map[string][]byte
243-
errCh chan<- error
257+
name string
258+
c gateway.Client
259+
ref gateway.Reference
260+
meta map[string][]byte
261+
inputs build.Inputs
262+
errCh chan<- error
244263
}
245264

246-
func (d *Adapter[T]) EvaluateResult(ctx context.Context, name string, c gateway.Client, res *gateway.Result) error {
265+
func (d *Adapter[T]) EvaluateResult(ctx context.Context, name string, c gateway.Client, res *gateway.Result, inputs build.Inputs) error {
247266
eg, _ := errgroup.WithContext(ctx)
248267
if res.Ref != nil {
249268
eg.Go(func() error {
250-
return d.evaluateRef(ctx, name, c, res.Ref, res.Metadata)
269+
return d.evaluateRef(ctx, name, c, res.Ref, res.Metadata, inputs)
251270
})
252271
}
253272

254273
for k, ref := range res.Refs {
255274
refName := fmt.Sprintf("%s (%s)", name, k)
256275
eg.Go(func() error {
257-
return d.evaluateRef(ctx, refName, c, ref, res.Metadata)
276+
return d.evaluateRef(ctx, refName, c, ref, res.Metadata, inputs)
258277
})
259278
}
260279
return eg.Wait()
261280
}
262281

263-
func (d *Adapter[T]) evaluateRef(ctx context.Context, name string, c gateway.Client, ref gateway.Reference, meta map[string][]byte) error {
282+
func (d *Adapter[T]) evaluateRef(ctx context.Context, name string, c gateway.Client, ref gateway.Reference, meta map[string][]byte, inputs build.Inputs) error {
264283
errCh := make(chan error, 1)
265284

266285
// Send a solve request to the launch routine
267286
// which will perform the solve in the context of the server.
268287
ereq := &evaluateRequest{
269-
name: name,
270-
c: c,
271-
ref: ref,
272-
meta: meta,
273-
errCh: errCh,
288+
name: name,
289+
c: c,
290+
ref: ref,
291+
meta: meta,
292+
inputs: inputs,
293+
errCh: errCh,
274294
}
275295
select {
276296
case d.evaluateReqCh <- ereq:
@@ -307,16 +327,28 @@ func (d *Adapter[T]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap
307327
return errors.Errorf("no such thread: %d", req.Arguments.ThreadId)
308328
}
309329

310-
resp.Body.StackFrames = t.StackFrames()
330+
resp.Body.StackFrames = t.StackTrace()
331+
return nil
332+
}
333+
334+
func (d *Adapter[T]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceResponse) error {
335+
fname := req.Arguments.Source.Path
336+
337+
dt, ok := d.sourceMap.Get(fname)
338+
if !ok {
339+
return errors.Errorf("file not found: %s", fname)
340+
}
341+
342+
resp.Body.Content = string(dt)
311343
return nil
312344
}
313345

314-
func (d *Adapter[T]) evaluate(ctx context.Context, name string, c gateway.Client, res *gateway.Result) error {
346+
func (d *Adapter[T]) evaluate(ctx context.Context, name string, c gateway.Client, res *gateway.Result, opt build.Options) error {
315347
errCh := make(chan error, 1)
316348

317349
started := d.srv.Go(func(ctx Context) {
318350
defer close(errCh)
319-
errCh <- d.EvaluateResult(ctx, name, c, res)
351+
errCh <- d.EvaluateResult(ctx, name, c, res, opt.Inputs)
320352
})
321353
if !started {
322354
return context.Canceled
@@ -341,93 +373,16 @@ func (d *Adapter[T]) dapHandler() Handler {
341373
Initialize: d.Initialize,
342374
Launch: d.Launch,
343375
Continue: d.Continue,
376+
Next: d.Next,
344377
SetBreakpoints: d.SetBreakpoints,
345378
ConfigurationDone: d.ConfigurationDone,
346379
Disconnect: d.Disconnect,
347380
Threads: d.Threads,
348381
StackTrace: d.StackTrace,
382+
Source: d.Source,
349383
}
350384
}
351385

352-
type thread struct {
353-
id int
354-
name string
355-
356-
paused chan struct{}
357-
rCtx *build.ResultHandle
358-
mu sync.Mutex
359-
}
360-
361-
func (t *thread) Evaluate(ctx Context, c gateway.Client, ref gateway.Reference, meta map[string][]byte, cfg build.InvokeConfig) error {
362-
err := ref.Evaluate(ctx)
363-
if reason, desc := t.needsDebug(cfg, err); reason != "" {
364-
rCtx := build.NewResultHandle(ctx, c, ref, meta, err)
365-
366-
select {
367-
case <-t.pause(ctx, rCtx, reason, desc):
368-
case <-ctx.Done():
369-
t.Resume(ctx)
370-
return context.Cause(ctx)
371-
}
372-
}
373-
return err
374-
}
375-
376-
func (t *thread) needsDebug(cfg build.InvokeConfig, err error) (reason, desc string) {
377-
if !cfg.NeedsDebug(err) {
378-
return
379-
}
380-
381-
if err != nil {
382-
reason = "exception"
383-
desc = "Encountered an error during result evaluation"
384-
} else {
385-
reason = "pause"
386-
desc = "Result evaluation completed"
387-
}
388-
return
389-
}
390-
391-
func (t *thread) pause(c Context, rCtx *build.ResultHandle, reason, desc string) <-chan struct{} {
392-
if t.paused == nil {
393-
t.paused = make(chan struct{})
394-
}
395-
t.rCtx = rCtx
396-
397-
c.C() <- &dap.StoppedEvent{
398-
Event: dap.Event{Event: "stopped"},
399-
Body: dap.StoppedEventBody{
400-
Reason: reason,
401-
Description: desc,
402-
ThreadId: t.id,
403-
},
404-
}
405-
return t.paused
406-
}
407-
408-
func (t *thread) Resume(c Context) {
409-
t.mu.Lock()
410-
defer t.mu.Unlock()
411-
412-
if t.paused == nil {
413-
return
414-
}
415-
416-
if t.rCtx != nil {
417-
t.rCtx.Done()
418-
t.rCtx = nil
419-
}
420-
421-
close(t.paused)
422-
t.paused = nil
423-
}
424-
425-
// TODO: return a suitable stack frame for the thread.
426-
// For now, just returns nothing.
427-
func (t *thread) StackFrames() []dap.StackFrame {
428-
return []dap.StackFrame{}
429-
}
430-
431386
func (d *Adapter[T]) Out() io.Writer {
432387
return &adapterWriter[T]{d}
433388
}
@@ -454,3 +409,63 @@ func (d *adapterWriter[T]) Write(p []byte) (n int, err error) {
454409
}
455410
return n, nil
456411
}
412+
413+
type idPool struct {
414+
next atomic.Int64
415+
}
416+
417+
func (p *idPool) Get() int64 {
418+
return p.next.Add(1)
419+
}
420+
421+
func (p *idPool) Put(x int64) {
422+
// noop
423+
}
424+
425+
type sourceMap struct {
426+
m sync.Map
427+
}
428+
429+
func (s *sourceMap) Put(c Context, fname string, dt []byte) {
430+
for {
431+
old, loaded := s.m.LoadOrStore(fname, dt)
432+
if !loaded {
433+
c.C() <- &dap.LoadedSourceEvent{
434+
Event: dap.Event{Event: "loadedSource"},
435+
Body: dap.LoadedSourceEventBody{
436+
Reason: "new",
437+
Source: dap.Source{
438+
Name: path.Base(fname),
439+
Path: fname,
440+
},
441+
},
442+
}
443+
}
444+
445+
if bytes.Equal(old.([]byte), dt) {
446+
// Nothing to do.
447+
return
448+
}
449+
450+
if s.m.CompareAndSwap(fname, old, dt) {
451+
c.C() <- &dap.LoadedSourceEvent{
452+
Event: dap.Event{Event: "loadedSource"},
453+
Body: dap.LoadedSourceEventBody{
454+
Reason: "changed",
455+
Source: dap.Source{
456+
Name: path.Base(fname),
457+
Path: fname,
458+
},
459+
},
460+
}
461+
}
462+
}
463+
}
464+
465+
func (s *sourceMap) Get(fname string) ([]byte, bool) {
466+
v, ok := s.m.Load(fname)
467+
if !ok {
468+
return nil, false
469+
}
470+
return v.([]byte), true
471+
}

dap/handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ type Handler struct {
5151
Disconnect HandlerFunc[*dap.DisconnectRequest, *dap.DisconnectResponse]
5252
Terminate HandlerFunc[*dap.TerminateRequest, *dap.TerminateResponse]
5353
Continue HandlerFunc[*dap.ContinueRequest, *dap.ContinueResponse]
54+
Next HandlerFunc[*dap.NextRequest, *dap.NextResponse]
5455
Restart HandlerFunc[*dap.RestartRequest, *dap.RestartResponse]
5556
Threads HandlerFunc[*dap.ThreadsRequest, *dap.ThreadsResponse]
5657
StackTrace HandlerFunc[*dap.StackTraceRequest, *dap.StackTraceResponse]
5758
Evaluate HandlerFunc[*dap.EvaluateRequest, *dap.EvaluateResponse]
59+
Source HandlerFunc[*dap.SourceRequest, *dap.SourceResponse]
5860
}

dap/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ func (s *Server) handleMessage(c Context, m dap.Message) (dap.ResponseMessage, e
117117
return s.h.Terminate.Do(c, req)
118118
case *dap.ContinueRequest:
119119
return s.h.Continue.Do(c, req)
120+
case *dap.NextRequest:
121+
return s.h.Next.Do(c, req)
120122
case *dap.RestartRequest:
121123
return s.h.Restart.Do(c, req)
122124
case *dap.ThreadsRequest:
@@ -125,6 +127,8 @@ func (s *Server) handleMessage(c Context, m dap.Message) (dap.ResponseMessage, e
125127
return s.h.StackTrace.Do(c, req)
126128
case *dap.EvaluateRequest:
127129
return s.h.Evaluate.Do(c, req)
130+
case *dap.SourceRequest:
131+
return s.h.Source.Do(c, req)
128132
default:
129133
return nil, errors.New("not implemented")
130134
}

0 commit comments

Comments
 (0)