Skip to content

Commit 611fbf0

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 <jonathan.sternberg@docker.com>
1 parent 758ea75 commit 611fbf0

File tree

4 files changed

+546
-84
lines changed

4 files changed

+546
-84
lines changed

dap/adapter.go

Lines changed: 32 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"sync"
9+
"sync/atomic"
910

1011
"github.com/docker/buildx/build"
1112
"github.com/google/go-dap"
@@ -28,6 +29,8 @@ type Adapter[T any] struct {
2829
threads map[int]*thread
2930
threadsMu sync.RWMutex
3031
nextThreadID int
32+
33+
idPool *idPool
3134
}
3235

3336
func New[T any](cfg *build.InvokeConfig) *Adapter[T] {
@@ -38,6 +41,7 @@ func New[T any](cfg *build.InvokeConfig) *Adapter[T] {
3841
evaluateReqCh: make(chan *evaluateRequest),
3942
threads: make(map[int]*thread),
4043
nextThreadID: 1,
44+
idPool: new(idPool),
4145
}
4246
if cfg != nil {
4347
d.cfg = *cfg
@@ -131,7 +135,16 @@ func (d *Adapter[T]) Continue(c Context, req *dap.ContinueRequest, resp *dap.Con
131135
t := d.threads[req.Arguments.ThreadId]
132136
d.threadsMu.RUnlock()
133137

134-
t.Resume(c)
138+
t.Continue(c)
139+
return nil
140+
}
141+
142+
func (d *Adapter[T]) Next(c Context, req *dap.NextRequest, resp *dap.NextResponse) error {
143+
d.threadsMu.RLock()
144+
t := d.threads[req.Arguments.ThreadId]
145+
d.threadsMu.RUnlock()
146+
147+
t.Next(c)
135148
return nil
136149
}
137150

@@ -182,7 +195,7 @@ func (d *Adapter[T]) launch(c Context) {
182195
started := c.Go(func(c Context) {
183196
defer d.deleteThread(c, t)
184197
defer close(req.errCh)
185-
req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta, d.cfg)
198+
req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta)
186199
})
187200

188201
if !started {
@@ -197,8 +210,9 @@ func (d *Adapter[T]) newThread(ctx Context, name string) (t *thread) {
197210
d.threadsMu.Lock()
198211
id := d.nextThreadID
199212
t = &thread{
200-
id: id,
201-
name: name,
213+
id: id,
214+
name: name,
215+
idPool: d.idPool,
202216
}
203217
d.threads[t.id] = t
204218
d.nextThreadID++
@@ -307,7 +321,7 @@ func (d *Adapter[T]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap
307321
return errors.Errorf("no such thread: %d", req.Arguments.ThreadId)
308322
}
309323

310-
resp.Body.StackFrames = t.StackFrames()
324+
resp.Body.StackFrames = t.StackTrace()
311325
return nil
312326
}
313327

@@ -341,6 +355,7 @@ func (d *Adapter[T]) dapHandler() Handler {
341355
Initialize: d.Initialize,
342356
Launch: d.Launch,
343357
Continue: d.Continue,
358+
Next: d.Next,
344359
SetBreakpoints: d.SetBreakpoints,
345360
ConfigurationDone: d.ConfigurationDone,
346361
Disconnect: d.Disconnect,
@@ -349,85 +364,6 @@ func (d *Adapter[T]) dapHandler() Handler {
349364
}
350365
}
351366

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-
431367
func (d *Adapter[T]) Out() io.Writer {
432368
return &adapterWriter[T]{d}
433369
}
@@ -454,3 +390,15 @@ func (d *adapterWriter[T]) Write(p []byte) (n int, err error) {
454390
}
455391
return n, nil
456392
}
393+
394+
type idPool struct {
395+
next atomic.Int64
396+
}
397+
398+
func (p *idPool) Get() int64 {
399+
return p.next.Add(1)
400+
}
401+
402+
func (p *idPool) Put(x int64) {
403+
// noop
404+
}

dap/handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ 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]

dap/server.go

Lines changed: 2 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:

0 commit comments

Comments
 (0)