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