Files
steampipe/pkg/dashboard/dashboardexecute/dashboard_parent_impl.go

163 lines
5.1 KiB
Go

package dashboardexecute
import (
"context"
"github.com/turbot/steampipe/pkg/dashboard/dashboardtypes"
"github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
"log"
"sync"
)
type DashboardParentImpl struct {
DashboardTreeRunImpl
children []dashboardtypes.DashboardTreeRun
childCompleteChan chan dashboardtypes.DashboardTreeRun
// are we blocked by a child run
blockedByChild bool
childStatusLock sync.Mutex
}
func (r *DashboardParentImpl) initialiseChildren(ctx context.Context) error {
var errors []error
for _, child := range r.children {
child.Initialise(ctx)
if err := child.GetError(); err != nil {
errors = append(errors, err)
}
}
return error_helpers.CombineErrors(errors...)
}
// GetChildren implements DashboardTreeRun
func (r *DashboardParentImpl) GetChildren() []dashboardtypes.DashboardTreeRun {
return r.children
}
// ChildrenComplete implements DashboardTreeRun
func (r *DashboardParentImpl) ChildrenComplete() bool {
for _, child := range r.children {
if !child.RunComplete() {
log.Printf("[TRACE] %s ChildrenComplete child %s NOT complete state %s", r.Name, child.GetName(), child.GetRunStatus())
return false
}
}
return true
}
func (r *DashboardParentImpl) ChildCompleteChan() chan dashboardtypes.DashboardTreeRun {
return r.childCompleteChan
}
func (r *DashboardParentImpl) createChildCompleteChan() {
// create buffered child complete chan
if childCount := len(r.children); childCount > 0 {
r.childCompleteChan = make(chan dashboardtypes.DashboardTreeRun, childCount)
}
}
// if this leaf run has children (including with runs) execute them asynchronously
func (r *DashboardParentImpl) executeChildrenAsync(ctx context.Context) {
for _, c := range r.children {
go c.Execute(ctx)
}
}
// if this leaf run has with runs execute them asynchronously
func (r *DashboardParentImpl) executeWithsAsync(ctx context.Context) {
for _, c := range r.children {
if c.GetNodeType() == modconfig.BlockTypeWith {
go c.Execute(ctx)
}
}
}
func (r *DashboardParentImpl) waitForChildrenAsync(ctx context.Context) chan error {
log.Printf("[TRACE] %s waitForChildrenAsync", r.Name)
var doneChan = make(chan error)
if len(r.children) == 0 {
log.Printf("[TRACE] %s waitForChildrenAsync - no children so we're done", r.Name)
// if there are no children, return a closed channel so we do not wait
close(doneChan)
return doneChan
}
go func() {
// wait for children to complete
var errors []error
for !(r.ChildrenComplete()) {
completeChild := <-r.childCompleteChan
log.Printf("[TRACE] %s waitForChildrenAsync got child complete for %s", r.Name, completeChild.GetName())
if completeChild.GetRunStatus().IsError() {
errors = append(errors, completeChild.GetError())
log.Printf("[TRACE] %s child %s has error %v", r.Name, completeChild.GetName(), completeChild.GetError())
}
}
log.Printf("[TRACE] %s ALL children and withs complete, errors: %v", r.Name, errors)
// so all children have completed - check for errors
// TODO [node_reuse] format better error https://github.com/turbot/steampipe/issues/2920
err := error_helpers.CombineErrors(errors...)
// if context is cancelled, just return context cancellation error
if ctx.Err() != nil {
err = ctx.Err()
}
doneChan <- err
}()
return doneChan
}
func (r *DashboardParentImpl) ChildStatusChanged(ctx context.Context) {
// this function may be called asyncronously by children
r.childStatusLock.Lock()
defer r.childStatusLock.Unlock()
// if we are currently blocked by a child or we are currently in running state,
// call setRunning() to determine whether any of our children are now blocked
if r.blockedByChild || r.GetRunStatus() == dashboardtypes.RunRunning {
log.Printf("[TRACE] %s ChildStatusChanged - calling setRunning to see if we are still running, status %s blockedByChild %v", r.Name, r.GetRunStatus(), r.blockedByChild)
// try setting our status to running again
r.setRunning(ctx)
}
}
// override DashboardTreeRunImpl) setStatus(
func (r *DashboardParentImpl) setRunning(ctx context.Context) {
// if the run is already complete (for example, canceled), do nothing
if r.GetRunStatus().IsFinished() {
log.Printf("[TRACE] %s setRunning - run already terminated - current state %s - NOT setting running", r.Name, r.GetRunStatus())
return
}
status := dashboardtypes.RunRunning
// if we are trying to set status to running, check if any of our children are blocked,
// and if so set our status to blocked
// if any children are blocked, we are blocked
for _, c := range r.children {
if c.GetRunStatus() == dashboardtypes.RunBlocked {
status = dashboardtypes.RunBlocked
r.blockedByChild = true
break
}
// to get here, no children can be blocked - clear blockedByChild
r.blockedByChild = false
}
// set status if it has changed
if status != r.GetRunStatus() {
log.Printf("[TRACE] %s setRunning - setting state %s, blockedByChild %v", r.Name, status, r.blockedByChild)
r.DashboardTreeRunImpl.setStatus(ctx, status)
} else {
log.Printf("[TRACE] %s setRunning - state unchanged %s, blockedByChild %v", r.Name, status, r.blockedByChild)
}
}