You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buildx/util/walker/controller.go

180 lines
4.1 KiB
Go

package walker
import (
"context"
"sync"
solverpb "github.com/moby/buildkit/solver/pb"
"github.com/pkg/errors"
)
// Controller is a utility to control walkers with debugger-like interface like "continue" and "next".
type Controller struct {
def *solverpb.Definition
breakpoints *Breakpoints
breakHandler BreakHandlerFunc
onVertexHandler OnVertexHandlerFunc
onWalkDoneFunc func(error)
walker *Walker
walkerMu sync.Mutex
walkCancel func()
curStepDoneCh chan struct{}
curWalkErrCh chan error
curWalkDoneCh chan struct{}
}
// Status is a status of the controller.
type Status struct {
// Definition is the target definition where walking is performed.
Definition *solverpb.Definition
// Cursors is current cursor positions on the walker.
Cursors []solverpb.Range
}
// NewController returns a walker controller.
func NewController(def *solverpb.Definition, breakpoints *Breakpoints, breakHandler BreakHandlerFunc, onVertexHandler OnVertexHandlerFunc, onWalkDoneFunc func(error)) *Controller {
return &Controller{
def: def,
breakpoints: breakpoints,
breakHandler: breakHandler,
onVertexHandler: onVertexHandler,
onWalkDoneFunc: onWalkDoneFunc,
}
}
// Breakpoint returns a set of breakpoints currently recognized.
func (c *Controller) Breakpoints() *Breakpoints {
return c.breakpoints
}
// Inspect returns the current status.
func (c *Controller) Inspect() *Status {
c.walkerMu.Lock()
defer c.walkerMu.Unlock()
var cursors []solverpb.Range
if c.walker != nil {
cursors = c.walker.GetCursors()
}
return &Status{
Definition: c.def,
Cursors: cursors,
}
}
// IsStarted returns true when there is an on-going walker. Returns false otherwise.
func (c *Controller) IsStarted() bool {
c.walkerMu.Lock()
w := c.walker
c.walkerMu.Unlock()
return w != nil
}
// StartWalk starts walking in a gorouitne. This function returns immediately without waiting for the
// completion of the walking. Parallel invoking of this method isn't supported and an error will be returned
// if there is an on-going walker. Previous walking must be canceled using WalkCancel method.
func (c *Controller) StartWalk() error {
c.walkerMu.Lock()
if c.walker != nil {
c.walkerMu.Unlock()
return errors.Errorf("walker already running")
}
c.walkerMu.Unlock()
go func() {
w := NewWalker(c.breakpoints, func(ctx context.Context, bCtx *BreakContext) error {
if err := c.breakHandler(ctx, bCtx); err != nil {
return err
}
curStepDoneCh := make(chan struct{})
c.curStepDoneCh = curStepDoneCh
select {
case <-curStepDoneCh:
case <-ctx.Done():
return ctx.Err()
}
return nil
}, c.onVertexHandler)
c.walkerMu.Lock()
c.walker = w
c.walkerMu.Unlock()
ctx, cancel := context.WithCancel(context.TODO())
c.walkCancel = cancel
c.curWalkErrCh = make(chan error)
c.curWalkDoneCh = make(chan struct{})
err := w.Walk(ctx, c.def)
c.onWalkDoneFunc(err)
w.Close()
c.walkerMu.Lock()
c.walker = nil
c.walkerMu.Unlock()
if err != nil {
c.curWalkErrCh <- err
}
close(c.curWalkDoneCh)
}()
return nil
}
// WalkCancel cancels on-going walking.
func (c *Controller) WalkCancel() error {
c.walkerMu.Lock()
if c.walker != nil {
c.walker = nil
c.walkerMu.Unlock()
c.walkCancel()
select {
case err := <-c.curWalkErrCh:
return err
case <-c.curWalkDoneCh:
}
return nil
}
c.walkerMu.Unlock()
return nil
}
// Continue resumes the walker. The walker will stop at the next breakpoint.
func (c *Controller) Continue() {
c.walkerMu.Lock()
defer c.walkerMu.Unlock()
if c.walker != nil {
c.walker.BreakAllNode(false)
}
if c.curStepDoneCh != nil {
close(c.curStepDoneCh)
c.curStepDoneCh = nil
}
}
// Next resumes the walker. The walker will stop at the next vertex.
func (c *Controller) Next() error {
c.walkerMu.Lock()
defer c.walkerMu.Unlock()
if c.walker != nil {
c.walker.BreakAllNode(true)
} else {
return errors.Errorf("walker isn't running")
}
if c.curStepDoneCh != nil {
close(c.curStepDoneCh)
c.curStepDoneCh = nil
}
return nil
}
// Close closes this controller.
func (c *Controller) Close() error {
return c.WalkCancel()
}