1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Collections.Generic; 7 using System.Diagnostics; // for debugger display attribute 8 9 using Microsoft.Build.Framework; 10 using Microsoft.Build.BuildEngine.Shared; 11 12 13 namespace Microsoft.Build.BuildEngine 14 { 15 /// <summary> 16 /// This class is used to construct and analyze the graph of inprogress targets in order to find 17 /// cycles inside the graph. To find cycles a post order traversal is used to assign a post order 18 /// traversal to each node. Back edges indicate cycles in the graph and they can indentified by 19 /// a link from lower index node to a higher index node. 20 /// 21 /// The graph arrives in pieces from individual nodes and needs to be stiched together by identifying 22 /// the parent and child for each cross node link. To do that it is necessary to match up parent 23 /// build request for a child with and outstanding request from the parent (see LinkCrossNodeBuildRequests) 24 /// </summary> 25 internal class TargetCycleDetector 26 { 27 #region Constructors TargetCycleDetector(EngineLoggingServices engineLoggingService, EngineCallback engineCallback)28 internal TargetCycleDetector(EngineLoggingServices engineLoggingService, EngineCallback engineCallback) 29 { 30 this.engineLoggingService = engineLoggingService; 31 this.engineCallback = engineCallback; 32 33 dependencyGraph = new Hashtable(); 34 outstandingExternalRequests = new Hashtable(); 35 cycleParent = null; 36 cycleChild = null; 37 } 38 #endregion 39 40 #region Properties 41 internal TargetInProgessState CycleEdgeParent 42 { 43 get 44 { 45 return this.cycleParent; 46 } 47 } 48 49 internal TargetInProgessState CycleEdgeChild 50 { 51 get 52 { 53 return this.cycleChild; 54 } 55 } 56 #endregion 57 58 #region Methods 59 60 /// <summary> 61 /// Add a information about an array of inprogress targets to the graph 62 /// </summary> AddTargetsToGraph(TargetInProgessState[] inprogressTargets)63 internal void AddTargetsToGraph(TargetInProgessState[] inprogressTargets) 64 { 65 if (inprogressTargets != null) 66 { 67 for (int i = 0; i < inprogressTargets.Length; i++) 68 { 69 if (inprogressTargets[i] != null) 70 { 71 AddTargetToGraph(inprogressTargets[i]); 72 } 73 } 74 } 75 } 76 77 /// <summary> 78 /// Add a information about a given inprogress target to the graph 79 /// </summary> AddTargetToGraph(TargetInProgessState inProgressTarget)80 private void AddTargetToGraph(TargetInProgessState inProgressTarget) 81 { 82 // Check if the target is already in the graph in which 83 // case reuse the current object 84 GraphNode targetNode = (GraphNode)dependencyGraph[inProgressTarget.TargetId]; 85 if (targetNode == null) 86 { 87 targetNode = new GraphNode(inProgressTarget); 88 dependencyGraph.Add(inProgressTarget.TargetId, targetNode); 89 } 90 else 91 { 92 ErrorUtilities.VerifyThrow(targetNode.targetState == null, "Target should only be added once"); 93 targetNode.targetState = inProgressTarget; 94 } 95 96 // For each parent target - add parent links creating parent targets if necessary 97 foreach (TargetInProgessState.TargetIdWrapper parentTarget in inProgressTarget.ParentTargets) 98 { 99 GraphNode parentNode = (GraphNode)dependencyGraph[parentTarget]; 100 if (parentNode == null) 101 { 102 parentNode = new GraphNode(null); 103 dependencyGraph.Add(parentTarget, parentNode); 104 } 105 parentNode.children.Add(targetNode); 106 } 107 108 // For all outgoing requests add them to the list of outstanding requests for the system 109 if (inProgressTarget.OutstandingBuildRequests != null) 110 { 111 // Since the nodeIndex is not serialized it is necessary to restore it after the request 112 // travels across the wire 113 for (int i = 0; i < inProgressTarget.OutstandingBuildRequests.Length; i++) 114 { 115 inProgressTarget.OutstandingBuildRequests[i].NodeIndex = inProgressTarget.TargetId.nodeId; 116 } 117 outstandingExternalRequests.Add(inProgressTarget.TargetId, inProgressTarget.OutstandingBuildRequests); 118 } 119 120 // If the target has no parents mark it as root (such targets are created due to host requests) 121 if (inProgressTarget.RequestedByHost) 122 { 123 targetNode.isRoot = true; 124 } 125 } 126 127 /// <summary> 128 /// Analyze the graph and try to find cycles. Returns true if a cycle is found. 129 /// </summary> FindCycles()130 internal bool FindCycles() 131 { 132 // Add the edges for the cross node connections 133 LinkCrossNodeBuildRequests(); 134 135 // Perform post-order traversal of the forest of directed graphs 136 traversalCount = 0; 137 138 // First try to perform the traversal from the roots (i.e nodes that are due to host requests) 139 foreach (GraphNode node in dependencyGraph.Values) 140 { 141 if (node.isRoot == true && node.traversalIndex == GraphNode.InvalidIndex) 142 { 143 BreadthFirstTraversal(node); 144 } 145 } 146 // Verify that all nodes have been reached 147 foreach (GraphNode node in dependencyGraph.Values) 148 { 149 if (node.traversalIndex == GraphNode.InvalidIndex) 150 { 151 BreadthFirstTraversal(node); 152 } 153 } 154 155 // Check every edge for being a back edge 156 foreach (GraphNode node in dependencyGraph.Values) 157 { 158 // Check the edges from the current node to its children 159 FindBackEdges(node); 160 161 // Stop looking as soon as the first cycle is found 162 if (cycleParent != null) 163 { 164 break; 165 } 166 } 167 168 return (cycleParent != null); 169 } 170 171 /// <summary> 172 /// For each target that has a cross node build request waiting for it to complete, iterate 173 /// over the list of outstanding requests and find the matching out going request. Once 174 /// the matching request is found - link the parent and child targets. 175 /// </summary> LinkCrossNodeBuildRequests()176 private void LinkCrossNodeBuildRequests() 177 { 178 foreach (GraphNode node in dependencyGraph.Values) 179 { 180 TargetInProgessState.TargetIdWrapper[] parentsForBuildRequests = 181 new TargetInProgessState.TargetIdWrapper[node.targetState.ParentBuildRequests.Count]; 182 183 for (int j = 0; j < node.targetState.ParentBuildRequests.Count; j++ ) 184 { 185 BuildRequest buildRequest = node.targetState.ParentBuildRequests[j]; 186 int nodeIndex = buildRequest.NodeIndex; 187 int handleId = buildRequest.HandleId; 188 int requestId = buildRequest.RequestId; 189 bool foundParent = false; 190 191 // Skip requests that originated from the host 192 if (handleId == EngineCallback.invalidEngineHandle) 193 { 194 node.isRoot = true; 195 continue; 196 } 197 198 // If the request being analyzed came from one of the child nodes, its incoming external request's 199 // handleId will point at a routing context on the parent engine. If the outgoing request 200 // orginated from another child the two requests (outgoing and incoming) point at different 201 // routing contexts. In that case it is necessary to unwind the incoming request to the routing 202 // context of the outgoing request. If outgoing request originated from the parent node - 203 // there will be only one routing request. 204 if (node.targetState.TargetId.nodeId != 0) 205 { 206 ExecutionContext executionContext = engineCallback.GetExecutionContextFromHandleId(buildRequest.HandleId); 207 RequestRoutingContext routingContext = executionContext as RequestRoutingContext; 208 if (routingContext != null && routingContext.ParentHandleId != EngineCallback.invalidEngineHandle) 209 { 210 ExecutionContext nextExecutionContext = engineCallback.GetExecutionContextFromHandleId(routingContext.ParentHandleId); 211 212 if (nextExecutionContext is RequestRoutingContext) 213 { 214 nodeIndex = nextExecutionContext.NodeIndex; 215 handleId = routingContext.ParentHandleId; 216 requestId = routingContext.ParentRequestId; 217 } 218 } 219 else 220 { 221 // Skip requests that originated from the host 222 node.isRoot = true; 223 continue; 224 } 225 } 226 227 // Iterate over all outstanding requests until a match is found 228 foreach (DictionaryEntry entry in outstandingExternalRequests) 229 { 230 BuildRequest[] externalRequests = (BuildRequest[])entry.Value; 231 for (int i = 0; i < externalRequests.Length && !foundParent; i++) 232 { 233 if (handleId == externalRequests[i].HandleId && 234 requestId == externalRequests[i].RequestId && 235 nodeIndex == externalRequests[i].NodeIndex) 236 { 237 // Verify that the project name is the same 238 ErrorUtilities.VerifyThrow( 239 String.Compare(buildRequest.ProjectFileName, externalRequests[i].ProjectFileName, StringComparison.OrdinalIgnoreCase) == 0, 240 "The two requests should have the same project name"); 241 242 // Link the two graph nodes together 243 GraphNode parentNode = (GraphNode)dependencyGraph[entry.Key]; 244 parentNode.children.Add(node); 245 246 parentsForBuildRequests[j] = parentNode.targetState.TargetId; 247 248 foundParent = true; 249 } 250 } 251 252 if (foundParent) 253 { 254 break; 255 } 256 } 257 } 258 node.targetState.ParentTargetsForBuildRequests = parentsForBuildRequests; 259 } 260 } 261 262 /// <summary> 263 /// Breadth first traversal over the DAG, assigning post order indecies to each node in the graph. This 264 /// function should be called at least once for each tree in the forest in order to assign 265 /// indecies to every node in the graph 266 /// </summary> BreadthFirstTraversal(GraphNode node)267 private void BreadthFirstTraversal(GraphNode node) 268 { 269 ErrorUtilities.VerifyThrow(node.traversalIndex == GraphNode.InvalidIndex, 270 "Should only consider each node once"); 271 272 node.traversalIndex = GraphNode.InProgressIndex; 273 274 for (int i = 0; i < node.children.Count; i++) 275 { 276 if (node.children[i].traversalIndex == GraphNode.InvalidIndex) 277 { 278 BreadthFirstTraversal(node.children[i]); 279 } 280 } 281 282 node.traversalIndex = traversalCount; 283 traversalCount++; 284 } 285 286 /// <summary> 287 /// Check for back edges from the given node to its children 288 /// </summary> FindBackEdges(GraphNode node)289 private void FindBackEdges(GraphNode node) 290 { 291 ErrorUtilities.VerifyThrow(node.traversalIndex != GraphNode.InvalidIndex, 292 "Each node should have a valid traversal index"); 293 294 for (int i = 0; i < node.children.Count; i++) 295 { 296 // Check for a back edge 297 if (node.children[i].traversalIndex > node.traversalIndex ) 298 { 299 cycleParent = node.targetState; 300 cycleChild = node.children[i].targetState; 301 DumpCycleSequence(node.children[i], node); 302 break; 303 } 304 // Check for an edge from the node to itself 305 if (node.children[i].targetState.TargetId == node.targetState.TargetId) 306 { 307 cycleParent = node.targetState; 308 cycleChild = node.targetState; 309 break; 310 } 311 } 312 } 313 DumpCycleSequence(GraphNode parent, GraphNode child)314 private void DumpCycleSequence(GraphNode parent, GraphNode child) 315 { 316 foreach (GraphNode node in dependencyGraph.Values) 317 { 318 node.traversalIndex = GraphNode.InvalidIndex; 319 } 320 BuildEventContext buildEventContext = 321 new BuildEventContext(child.targetState.TargetId.nodeId, 322 child.targetState.TargetId.id, 323 BuildEventContext.InvalidProjectContextId, 324 BuildEventContext.InvalidTaskId 325 ); 326 DumpCycleSequenceOutput(parent, child, buildEventContext); 327 } 328 DumpCycleSequenceOutput(GraphNode parent, GraphNode child, BuildEventContext buildEventContext)329 private bool DumpCycleSequenceOutput(GraphNode parent, GraphNode child, BuildEventContext buildEventContext) 330 { 331 if (parent == child ) 332 { 333 engineLoggingService.LogComment(buildEventContext, "cycleTraceTitle"); 334 engineLoggingService.LogComment 335 (buildEventContext, "cycleTraceLine", parent.targetState.TargetId.name, parent.targetState.ProjectName); 336 return true; 337 } 338 339 if (parent.traversalIndex == GraphNode.InProgressIndex) 340 { 341 return false; 342 } 343 344 parent.traversalIndex = GraphNode.InProgressIndex; 345 346 for (int i = 0; i < parent.children.Count; i++) 347 { 348 if (DumpCycleSequenceOutput(parent.children[i], child, buildEventContext)) 349 { 350 engineLoggingService.LogComment 351 (buildEventContext, "cycleTraceLine", parent.targetState.TargetId.name, parent.targetState.ProjectName); 352 return true; 353 } 354 } 355 356 return false; 357 } 358 359 #endregion 360 361 #region Data 362 /// <summary> 363 /// The table of all targets in the dependency graph, indexed by TargetNameStructure which 364 /// contains Target name, Project Id, Node Id which uniquely identifies every target in the system 365 /// </summary> 366 private Hashtable dependencyGraph; 367 /// <summary> 368 /// List of all outstanding cross node build requests 369 /// </summary> 370 private Hashtable outstandingExternalRequests; 371 /// <summary> 372 /// The index used for the breadth first traversal 373 /// </summary> 374 private int traversalCount; 375 /// <summary> 376 /// The TargetNameStructure for the parent of the edge creating the cycle 377 /// </summary> 378 private TargetInProgessState cycleParent; 379 /// <summary> 380 /// The TargetNameStructure for the parent of the edge creating the cycle 381 /// </summary> 382 private TargetInProgessState cycleChild; 383 /// <summary> 384 /// Logging service for outputing the loop trace 385 /// </summary> 386 private EngineLoggingServices engineLoggingService; 387 /// <summary> 388 /// Engine callback which is to walk the inprogress execution contexts 389 /// </summary> 390 private EngineCallback engineCallback; 391 #endregion 392 393 [DebuggerDisplay("Node (Name = { targetState.TargetId.name }, Project = { targetState.TargetId.projectId }), Node = { targetState.TargetId.nodeId })")] 394 private class GraphNode 395 { 396 #region Constructors GraphNode(TargetInProgessState targetState)397 internal GraphNode(TargetInProgessState targetState) 398 { 399 this.targetState = targetState; 400 this.children = new List<GraphNode>(); 401 this.traversalIndex = InvalidIndex; 402 this.isRoot = false; 403 } 404 #endregion 405 406 #region Data 407 internal TargetInProgessState targetState; 408 internal List<GraphNode> children; 409 internal bool isRoot; 410 internal int traversalIndex; 411 412 internal const int InvalidIndex = -1; 413 internal const int InProgressIndex = -2; 414 #endregion 415 } 416 } 417 } 418