github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/dag/walk.go (about) 1 package dag 2 3 import ( 4 "errors" 5 "log" 6 "sync" 7 "time" 8 9 "github.com/eliastor/durgaform/internal/tfdiags" 10 ) 11 12 // Walker is used to walk every vertex of a graph in parallel. 13 // 14 // A vertex will only be walked when the dependencies of that vertex have 15 // been walked. If two vertices can be walked at the same time, they will be. 16 // 17 // Update can be called to update the graph. This can be called even during 18 // a walk, changing vertices/edges mid-walk. This should be done carefully. 19 // If a vertex is removed but has already been executed, the result of that 20 // execution (any error) is still returned by Wait. Changing or re-adding 21 // a vertex that has already executed has no effect. Changing edges of 22 // a vertex that has already executed has no effect. 23 // 24 // Non-parallelism can be enforced by introducing a lock in your callback 25 // function. However, the goroutine overhead of a walk will remain. 26 // Walker will create V*2 goroutines (one for each vertex, and dependency 27 // waiter for each vertex). In general this should be of no concern unless 28 // there are a huge number of vertices. 29 // 30 // The walk is depth first by default. This can be changed with the Reverse 31 // option. 32 // 33 // A single walker is only valid for one graph walk. After the walk is complete 34 // you must construct a new walker to walk again. State for the walk is never 35 // deleted in case vertices or edges are changed. 36 type Walker struct { 37 // Callback is what is called for each vertex 38 Callback WalkFunc 39 40 // Reverse, if true, causes the source of an edge to depend on a target. 41 // When false (default), the target depends on the source. 42 Reverse bool 43 44 // changeLock must be held to modify any of the fields below. Only Update 45 // should modify these fields. Modifying them outside of Update can cause 46 // serious problems. 47 changeLock sync.Mutex 48 vertices Set 49 edges Set 50 vertexMap map[Vertex]*walkerVertex 51 52 // wait is done when all vertices have executed. It may become "undone" 53 // if new vertices are added. 54 wait sync.WaitGroup 55 56 // diagsMap contains the diagnostics recorded so far for execution, 57 // and upstreamFailed contains all the vertices whose problems were 58 // caused by upstream failures, and thus whose diagnostics should be 59 // excluded from the final set. 60 // 61 // Readers and writers of either map must hold diagsLock. 62 diagsMap map[Vertex]tfdiags.Diagnostics 63 upstreamFailed map[Vertex]struct{} 64 diagsLock sync.Mutex 65 } 66 67 func (w *Walker) init() { 68 if w.vertices == nil { 69 w.vertices = make(Set) 70 } 71 if w.edges == nil { 72 w.edges = make(Set) 73 } 74 } 75 76 type walkerVertex struct { 77 // These should only be set once on initialization and never written again. 78 // They are not protected by a lock since they don't need to be since 79 // they are write-once. 80 81 // DoneCh is closed when this vertex has completed execution, regardless 82 // of success. 83 // 84 // CancelCh is closed when the vertex should cancel execution. If execution 85 // is already complete (DoneCh is closed), this has no effect. Otherwise, 86 // execution is cancelled as quickly as possible. 87 DoneCh chan struct{} 88 CancelCh chan struct{} 89 90 // Dependency information. Any changes to any of these fields requires 91 // holding DepsLock. 92 // 93 // DepsCh is sent a single value that denotes whether the upstream deps 94 // were successful (no errors). Any value sent means that the upstream 95 // dependencies are complete. No other values will ever be sent again. 96 // 97 // DepsUpdateCh is closed when there is a new DepsCh set. 98 DepsCh chan bool 99 DepsUpdateCh chan struct{} 100 DepsLock sync.Mutex 101 102 // Below is not safe to read/write in parallel. This behavior is 103 // enforced by changes only happening in Update. Nothing else should 104 // ever modify these. 105 deps map[Vertex]chan struct{} 106 depsCancelCh chan struct{} 107 } 108 109 // Wait waits for the completion of the walk and returns diagnostics describing 110 // any problems that arose. Update should be called to populate the walk with 111 // vertices and edges prior to calling this. 112 // 113 // Wait will return as soon as all currently known vertices are complete. 114 // If you plan on calling Update with more vertices in the future, you 115 // should not call Wait until after this is done. 116 func (w *Walker) Wait() tfdiags.Diagnostics { 117 // Wait for completion 118 w.wait.Wait() 119 120 var diags tfdiags.Diagnostics 121 w.diagsLock.Lock() 122 for v, vDiags := range w.diagsMap { 123 if _, upstream := w.upstreamFailed[v]; upstream { 124 // Ignore diagnostics for nodes that had failed upstreams, since 125 // the downstream diagnostics are likely to be redundant. 126 continue 127 } 128 diags = diags.Append(vDiags) 129 } 130 w.diagsLock.Unlock() 131 132 return diags 133 } 134 135 // Update updates the currently executing walk with the given graph. 136 // This will perform a diff of the vertices and edges and update the walker. 137 // Already completed vertices remain completed (including any errors during 138 // their execution). 139 // 140 // This returns immediately once the walker is updated; it does not wait 141 // for completion of the walk. 142 // 143 // Multiple Updates can be called in parallel. Update can be called at any 144 // time during a walk. 145 func (w *Walker) Update(g *AcyclicGraph) { 146 w.init() 147 v := make(Set) 148 e := make(Set) 149 if g != nil { 150 v, e = g.vertices, g.edges 151 } 152 153 // Grab the change lock so no more updates happen but also so that 154 // no new vertices are executed during this time since we may be 155 // removing them. 156 w.changeLock.Lock() 157 defer w.changeLock.Unlock() 158 159 // Initialize fields 160 if w.vertexMap == nil { 161 w.vertexMap = make(map[Vertex]*walkerVertex) 162 } 163 164 // Calculate all our sets 165 newEdges := e.Difference(w.edges) 166 oldEdges := w.edges.Difference(e) 167 newVerts := v.Difference(w.vertices) 168 oldVerts := w.vertices.Difference(v) 169 170 // Add the new vertices 171 for _, raw := range newVerts { 172 v := raw.(Vertex) 173 174 // Add to the waitgroup so our walk is not done until everything finishes 175 w.wait.Add(1) 176 177 // Add to our own set so we know about it already 178 w.vertices.Add(raw) 179 180 // Initialize the vertex info 181 info := &walkerVertex{ 182 DoneCh: make(chan struct{}), 183 CancelCh: make(chan struct{}), 184 deps: make(map[Vertex]chan struct{}), 185 } 186 187 // Add it to the map and kick off the walk 188 w.vertexMap[v] = info 189 } 190 191 // Remove the old vertices 192 for _, raw := range oldVerts { 193 v := raw.(Vertex) 194 195 // Get the vertex info so we can cancel it 196 info, ok := w.vertexMap[v] 197 if !ok { 198 // This vertex for some reason was never in our map. This 199 // shouldn't be possible. 200 continue 201 } 202 203 // Cancel the vertex 204 close(info.CancelCh) 205 206 // Delete it out of the map 207 delete(w.vertexMap, v) 208 w.vertices.Delete(raw) 209 } 210 211 // Add the new edges 212 changedDeps := make(Set) 213 for _, raw := range newEdges { 214 edge := raw.(Edge) 215 waiter, dep := w.edgeParts(edge) 216 217 // Get the info for the waiter 218 waiterInfo, ok := w.vertexMap[waiter] 219 if !ok { 220 // Vertex doesn't exist... shouldn't be possible but ignore. 221 continue 222 } 223 224 // Get the info for the dep 225 depInfo, ok := w.vertexMap[dep] 226 if !ok { 227 // Vertex doesn't exist... shouldn't be possible but ignore. 228 continue 229 } 230 231 // Add the dependency to our waiter 232 waiterInfo.deps[dep] = depInfo.DoneCh 233 234 // Record that the deps changed for this waiter 235 changedDeps.Add(waiter) 236 w.edges.Add(raw) 237 } 238 239 // Process removed edges 240 for _, raw := range oldEdges { 241 edge := raw.(Edge) 242 waiter, dep := w.edgeParts(edge) 243 244 // Get the info for the waiter 245 waiterInfo, ok := w.vertexMap[waiter] 246 if !ok { 247 // Vertex doesn't exist... shouldn't be possible but ignore. 248 continue 249 } 250 251 // Delete the dependency from the waiter 252 delete(waiterInfo.deps, dep) 253 254 // Record that the deps changed for this waiter 255 changedDeps.Add(waiter) 256 w.edges.Delete(raw) 257 } 258 259 // For each vertex with changed dependencies, we need to kick off 260 // a new waiter and notify the vertex of the changes. 261 for _, raw := range changedDeps { 262 v := raw.(Vertex) 263 info, ok := w.vertexMap[v] 264 if !ok { 265 // Vertex doesn't exist... shouldn't be possible but ignore. 266 continue 267 } 268 269 // Create a new done channel 270 doneCh := make(chan bool, 1) 271 272 // Create the channel we close for cancellation 273 cancelCh := make(chan struct{}) 274 275 // Build a new deps copy 276 deps := make(map[Vertex]<-chan struct{}) 277 for k, v := range info.deps { 278 deps[k] = v 279 } 280 281 // Update the update channel 282 info.DepsLock.Lock() 283 if info.DepsUpdateCh != nil { 284 close(info.DepsUpdateCh) 285 } 286 info.DepsCh = doneCh 287 info.DepsUpdateCh = make(chan struct{}) 288 info.DepsLock.Unlock() 289 290 // Cancel the older waiter 291 if info.depsCancelCh != nil { 292 close(info.depsCancelCh) 293 } 294 info.depsCancelCh = cancelCh 295 296 // Start the waiter 297 go w.waitDeps(v, deps, doneCh, cancelCh) 298 } 299 300 // Start all the new vertices. We do this at the end so that all 301 // the edge waiters and changes are set up above. 302 for _, raw := range newVerts { 303 v := raw.(Vertex) 304 go w.walkVertex(v, w.vertexMap[v]) 305 } 306 } 307 308 // edgeParts returns the waiter and the dependency, in that order. 309 // The waiter is waiting on the dependency. 310 func (w *Walker) edgeParts(e Edge) (Vertex, Vertex) { 311 if w.Reverse { 312 return e.Source(), e.Target() 313 } 314 315 return e.Target(), e.Source() 316 } 317 318 // walkVertex walks a single vertex, waiting for any dependencies before 319 // executing the callback. 320 func (w *Walker) walkVertex(v Vertex, info *walkerVertex) { 321 // When we're done executing, lower the waitgroup count 322 defer w.wait.Done() 323 324 // When we're done, always close our done channel 325 defer close(info.DoneCh) 326 327 // Wait for our dependencies. We create a [closed] deps channel so 328 // that we can immediately fall through to load our actual DepsCh. 329 var depsSuccess bool 330 var depsUpdateCh chan struct{} 331 depsCh := make(chan bool, 1) 332 depsCh <- true 333 close(depsCh) 334 for { 335 select { 336 case <-info.CancelCh: 337 // Cancel 338 return 339 340 case depsSuccess = <-depsCh: 341 // Deps complete! Mark as nil to trigger completion handling. 342 depsCh = nil 343 344 case <-depsUpdateCh: 345 // New deps, reloop 346 } 347 348 // Check if we have updated dependencies. This can happen if the 349 // dependencies were satisfied exactly prior to an Update occurring. 350 // In that case, we'd like to take into account new dependencies 351 // if possible. 352 info.DepsLock.Lock() 353 if info.DepsCh != nil { 354 depsCh = info.DepsCh 355 info.DepsCh = nil 356 } 357 if info.DepsUpdateCh != nil { 358 depsUpdateCh = info.DepsUpdateCh 359 } 360 info.DepsLock.Unlock() 361 362 // If we still have no deps channel set, then we're done! 363 if depsCh == nil { 364 break 365 } 366 } 367 368 // If we passed dependencies, we just want to check once more that 369 // we're not cancelled, since this can happen just as dependencies pass. 370 select { 371 case <-info.CancelCh: 372 // Cancelled during an update while dependencies completed. 373 return 374 default: 375 } 376 377 // Run our callback or note that our upstream failed 378 var diags tfdiags.Diagnostics 379 var upstreamFailed bool 380 if depsSuccess { 381 diags = w.Callback(v) 382 } else { 383 log.Printf("[TRACE] dag/walk: upstream of %q errored, so skipping", VertexName(v)) 384 // This won't be displayed to the user because we'll set upstreamFailed, 385 // but we need to ensure there's at least one error in here so that 386 // the failures will cascade downstream. 387 diags = diags.Append(errors.New("upstream dependencies failed")) 388 upstreamFailed = true 389 } 390 391 // Record the result (we must do this after execution because we mustn't 392 // hold diagsLock while visiting a vertex.) 393 w.diagsLock.Lock() 394 if w.diagsMap == nil { 395 w.diagsMap = make(map[Vertex]tfdiags.Diagnostics) 396 } 397 w.diagsMap[v] = diags 398 if w.upstreamFailed == nil { 399 w.upstreamFailed = make(map[Vertex]struct{}) 400 } 401 if upstreamFailed { 402 w.upstreamFailed[v] = struct{}{} 403 } 404 w.diagsLock.Unlock() 405 } 406 407 func (w *Walker) waitDeps( 408 v Vertex, 409 deps map[Vertex]<-chan struct{}, 410 doneCh chan<- bool, 411 cancelCh <-chan struct{}) { 412 413 // For each dependency given to us, wait for it to complete 414 for dep, depCh := range deps { 415 DepSatisfied: 416 for { 417 select { 418 case <-depCh: 419 // Dependency satisfied! 420 break DepSatisfied 421 422 case <-cancelCh: 423 // Wait cancelled. Note that we didn't satisfy dependencies 424 // so that anything waiting on us also doesn't run. 425 doneCh <- false 426 return 427 428 case <-time.After(time.Second * 5): 429 log.Printf("[TRACE] dag/walk: vertex %q is waiting for %q", 430 VertexName(v), VertexName(dep)) 431 } 432 } 433 } 434 435 // Dependencies satisfied! We need to check if any errored 436 w.diagsLock.Lock() 437 defer w.diagsLock.Unlock() 438 for dep := range deps { 439 if w.diagsMap[dep].HasErrors() { 440 // One of our dependencies failed, so return false 441 doneCh <- false 442 return 443 } 444 } 445 446 // All dependencies satisfied and successful 447 doneCh <- true 448 }