Skip to content

Commit 4f2e23a

Browse files
committed
dap: add stack traces with next and continue functionality
It is now possible to send next and continue as separate signals. When executing a build, the debug adapter will divide the LLB graph into regions. Each region corresponds to an uninterrupted chain of instructions. It will also record which regions depend on which other ones. This determines the execution order and it also determines what the stack traces look like. When continue is used, we will attempt to evaluate the last leaf node (the head). If we push next, we will determine which digest would be the next one to be processed. In the future, this will be used to also support breakpoints. Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent 758ea75 commit 4f2e23a

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)