dap: make exec shell persistent across the build

Invoking the shell will cause it to persist across the entire build and
to re-execute whenever the builder pauses at another location again.

This still requires using `exec` to launch the shell. Launching by frame
id is also removed since it no longer applies to this version.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2025-08-11 12:40:09 -05:00
parent 46463d93bf
commit dbda218489
5 changed files with 414 additions and 62 deletions
+35 -19
View File
@@ -22,10 +22,8 @@ type thread struct {
name string
// Persistent state from the adapter.
idPool *idPool
sourceMap *sourceMap
breakpointMap *breakpointMap
variables *variableReferences
sharedState
variables *variableReferences
// Inputs to the evaluate call.
c gateway.Client
@@ -50,7 +48,7 @@ type thread struct {
mu sync.Mutex
// Attributes set when a thread is paused.
cancel context.CancelCauseFunc
cancel context.CancelCauseFunc // invoked when the thread is resumed
rCtx *build.ResultHandle
curPos digest.Digest
stackTrace []int32
@@ -254,9 +252,6 @@ func (t *thread) pause(c Context, ref gateway.Reference, err error, pos *step, e
}
t.paused = make(chan stepType, 1)
if ref != nil || err != nil {
t.rCtx = build.NewResultHandle(c, t.c, ref, t.meta, err)
}
if err != nil {
var solveErr *errdefs.SolveError
if errors.As(err, &solveErr) {
@@ -270,6 +265,10 @@ func (t *thread) pause(c Context, ref gateway.Reference, err error, pos *step, e
t.collectStackTrace(ctx, pos, ref)
t.cancel = cancel
if ref != nil || err != nil {
t.prepareResultHandle(c, ref, err)
}
event.ThreadId = t.id
c.C() <- &dap.StoppedEvent{
Event: dap.Event{Event: "stopped"},
@@ -278,6 +277,27 @@ func (t *thread) pause(c Context, ref gateway.Reference, err error, pos *step, e
return t.paused
}
func (t *thread) prepareResultHandle(c Context, ref gateway.Reference, err error) {
// Create a context for cancellations and make the cancel function
// block on the wait group.
var wg sync.WaitGroup
ctx, cancel := context.WithCancelCause(c)
t.cancel = func(cause error) {
defer wg.Wait()
cancel(cause)
}
t.rCtx = build.NewResultHandle(ctx, t.c, ref, t.meta, err)
// Start the attach. Use the context we created and perform it in
// a goroutine. We aren't necessarily assuming this will actually work.
wg.Add(1)
go func() {
defer wg.Done()
t.sh.Attach(ctx, t)
}()
}
func (t *thread) Continue() {
t.resume(stepContinue)
}
@@ -308,13 +328,6 @@ func (t *thread) resume(step stepType) {
t.paused = nil
}
func (t *thread) isPaused() bool {
t.mu.Lock()
defer t.mu.Unlock()
return t.paused != nil
}
func (t *thread) StackTrace() []dap.StackFrame {
t.mu.Lock()
defer t.mu.Unlock()
@@ -494,17 +507,20 @@ func (t *thread) solve(ctx context.Context, target digest.Digest) (gateway.Refer
}
func (t *thread) releaseState() {
if t.cancel != nil {
t.cancel(context.Canceled)
t.cancel = nil
}
if t.rCtx != nil {
t.rCtx.Done()
t.rCtx = nil
}
for _, f := range t.frames {
f.ResetVars()
}
if t.cancel != nil {
t.cancel(context.Canceled)
t.cancel = nil
}
t.stackTrace = t.stackTrace[:0]
t.variables.Reset()
}