github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/dashboard/dashboardexecute/dashboard_parent_impl.go (about) 1 package dashboardexecute 2 3 import ( 4 "context" 5 "github.com/turbot/steampipe/pkg/dashboard/dashboardtypes" 6 "github.com/turbot/steampipe/pkg/error_helpers" 7 "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" 8 "log" 9 "sync" 10 ) 11 12 type DashboardParentImpl struct { 13 DashboardTreeRunImpl 14 children []dashboardtypes.DashboardTreeRun 15 childCompleteChan chan dashboardtypes.DashboardTreeRun 16 // are we blocked by a child run 17 blockedByChild bool 18 childStatusLock *sync.Mutex 19 } 20 21 func newDashboardParentImpl(resource modconfig.DashboardLeafNode, parent dashboardtypes.DashboardParent, run dashboardtypes.DashboardTreeRun, executionTree *DashboardExecutionTree) DashboardParentImpl { 22 return DashboardParentImpl{ 23 DashboardTreeRunImpl: NewDashboardTreeRunImpl(resource, parent, run, executionTree), 24 childStatusLock: new(sync.Mutex), 25 } 26 } 27 28 func (r *DashboardParentImpl) initialiseChildren(ctx context.Context) error { 29 var errors []error 30 for _, child := range r.children { 31 child.Initialise(ctx) 32 33 if err := child.GetError(); err != nil { 34 errors = append(errors, err) 35 } 36 } 37 38 return error_helpers.CombineErrors(errors...) 39 40 } 41 42 // GetChildren implements DashboardTreeRun 43 func (r *DashboardParentImpl) GetChildren() []dashboardtypes.DashboardTreeRun { 44 return r.children 45 } 46 47 // ChildrenComplete implements DashboardTreeRun 48 func (r *DashboardParentImpl) ChildrenComplete() bool { 49 for _, child := range r.children { 50 if !child.RunComplete() { 51 log.Printf("[TRACE] %s ChildrenComplete child %s NOT complete state %s", r.Name, child.GetName(), child.GetRunStatus()) 52 return false 53 } 54 } 55 56 return true 57 } 58 59 func (r *DashboardParentImpl) ChildCompleteChan() chan dashboardtypes.DashboardTreeRun { 60 return r.childCompleteChan 61 } 62 func (r *DashboardParentImpl) createChildCompleteChan() { 63 // create buffered child complete chan 64 if childCount := len(r.children); childCount > 0 { 65 r.childCompleteChan = make(chan dashboardtypes.DashboardTreeRun, childCount) 66 } 67 } 68 69 // if this leaf run has children (including with runs) execute them asynchronously 70 func (r *DashboardParentImpl) executeChildrenAsync(ctx context.Context) { 71 for _, c := range r.children { 72 go c.Execute(ctx) 73 } 74 } 75 76 // if this leaf run has with runs execute them asynchronously 77 func (r *DashboardParentImpl) executeWithsAsync(ctx context.Context) { 78 for _, c := range r.children { 79 if c.GetNodeType() == modconfig.BlockTypeWith { 80 go c.Execute(ctx) 81 } 82 } 83 } 84 85 func (r *DashboardParentImpl) waitForChildrenAsync(ctx context.Context) chan error { 86 log.Printf("[TRACE] %s waitForChildrenAsync", r.Name) 87 var doneChan = make(chan error) 88 if len(r.children) == 0 { 89 log.Printf("[TRACE] %s waitForChildrenAsync - no children so we're done", r.Name) 90 // if there are no children, return a closed channel so we do not wait 91 close(doneChan) 92 return doneChan 93 } 94 95 go func() { 96 // wait for children to complete 97 var errors []error 98 for !(r.ChildrenComplete()) { 99 completeChild := <-r.childCompleteChan 100 log.Printf("[TRACE] %s waitForChildrenAsync got child complete for %s", r.Name, completeChild.GetName()) 101 if completeChild.GetRunStatus().IsError() { 102 errors = append(errors, completeChild.GetError()) 103 log.Printf("[TRACE] %s child %s has error %v", r.Name, completeChild.GetName(), completeChild.GetError()) 104 } 105 } 106 107 log.Printf("[TRACE] %s ALL children and withs complete, errors: %v", r.Name, errors) 108 109 // so all children have completed - check for errors 110 // TODO [node_reuse] format better error https://github.com/turbot/steampipe/issues/2920 111 err := error_helpers.CombineErrors(errors...) 112 113 // if context is cancelled, just return context cancellation error 114 if ctx.Err() != nil { 115 err = ctx.Err() 116 } 117 118 doneChan <- err 119 }() 120 121 return doneChan 122 } 123 124 func (r *DashboardParentImpl) ChildStatusChanged(ctx context.Context) { 125 // this function may be called asyncronously by children 126 r.childStatusLock.Lock() 127 defer r.childStatusLock.Unlock() 128 129 // if we are currently blocked by a child or we are currently in running state, 130 // call setRunning() to determine whether any of our children are now blocked 131 if r.blockedByChild || r.GetRunStatus() == dashboardtypes.RunRunning { 132 log.Printf("[TRACE] %s ChildStatusChanged - calling setRunning to see if we are still running, status %s blockedByChild %v", r.Name, r.GetRunStatus(), r.blockedByChild) 133 134 // try setting our status to running again 135 r.setRunning(ctx) 136 } 137 } 138 139 // override DashboardTreeRunImpl) setStatus( 140 func (r *DashboardParentImpl) setRunning(ctx context.Context) { 141 // if the run is already complete (for example, canceled), do nothing 142 if r.GetRunStatus().IsFinished() { 143 log.Printf("[TRACE] %s setRunning - run already terminated - current state %s - NOT setting running", r.Name, r.GetRunStatus()) 144 return 145 } 146 147 status := dashboardtypes.RunRunning 148 // if we are trying to set status to running, check if any of our children are blocked, 149 // and if so set our status to blocked 150 151 // if any children are blocked, we are blocked 152 for _, c := range r.children { 153 if c.GetRunStatus() == dashboardtypes.RunBlocked { 154 status = dashboardtypes.RunBlocked 155 r.blockedByChild = true 156 break 157 } 158 // to get here, no children can be blocked - clear blockedByChild 159 r.blockedByChild = false 160 } 161 162 // set status if it has changed 163 if status != r.GetRunStatus() { 164 log.Printf("[TRACE] %s setRunning - setting state %s, blockedByChild %v", r.Name, status, r.blockedByChild) 165 r.DashboardTreeRunImpl.setStatus(ctx, status) 166 } else { 167 log.Printf("[TRACE] %s setRunning - state unchanged %s, blockedByChild %v", r.Name, status, r.blockedByChild) 168 } 169 }