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.IO;
8 using System.Text;
9 using System.Threading;
10 
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.BuildEngine.Shared;
13 
14 namespace Microsoft.Build.BuildEngine
15 {
16     /// <summary>
17     /// This class is responsible for representing the task execution subsystem to the engine. This
18     /// class can be instantiated in a different appdomain from the engine and doesn't share
19     /// any pointers/data with the engine(except for the argument to the functions).
20     /// </summary>
21     internal class TaskExecutionModule
22     {
23         #region Constructors
24 
25         /// <summary>
26         /// The TaskExecutionModule is a the external view into a subsystem responsible for executing user
27         /// tasks. The subsystem consists of TaskWorkerThread, TaskEngine, TaskExecutionState and EngineProxy.
28         /// The engine thread passes the TaskExecutionState to the TEM which after the task finishes passes the
29         /// results back via the engineCallback.
30         /// </summary>
TaskExecutionModule( EngineCallback engineCallback, TaskExecutionModuleMode moduleMode, bool profileExecution )31         internal TaskExecutionModule
32         (
33             EngineCallback engineCallback,
34             TaskExecutionModuleMode moduleMode,
35             bool profileExecution
36         )
37         {
38             this.engineCallback = engineCallback;
39             this.moduleMode = moduleMode;
40             // By default start in breadthFirst traversal. This is done to gather enough work at the start of the build to get all the nodes at least working on something.
41             this.breadthFirstTraversal = true;
42             this.profileExecution = profileExecution;
43             this.totalTaskTime = 0;
44             // Get the node the TEM is running on, this is so the parent engine knows which node is requesting a traversal strategy change.
45             nodeId = engineCallback.GetParentEngine().NodeId;
46 
47             SetBatchRequestSize();
48 
49             // In singleproc mode the task execution module executes tasks on the engine thread. In multi proc mode a new thread is
50             // created so the TEM can submit tasks to a worker queue which will run the tasks on a new thread.
51             if (moduleMode != TaskExecutionModuleMode.SingleProcMode)
52             {
53                 this.isRunningMultipleNodes = true;
54                 this.activeThreadCount = 0;
55                 this.overallThreadCount = 0;
56                 this.threadActiveCountEvent  = new ManualResetEvent(false);
57                 this.threadOverallCountEvent = new ManualResetEvent(false);
58                 this.lastTaskActivity = 0;
59 
60                 // Create a worker thread and make it the active node thread
61                 workerThread = new TaskWorkerThread(this, profileExecution);
62                 workerThread.ActivateThread();
63             }
64             else
65             {
66                 this.isRunningMultipleNodes = false;
67             }
68         }
69 
70         /// <summary>
71         /// Sets the requestBatch size based on an environment variable set by the user.
72         /// </summary>
SetBatchRequestSize()73         private void SetBatchRequestSize()
74         {
75             // The RequestBatchSize is how many buildrequests will be sent to the parent engine at once when the system
76             // is running in multiproc mode. The idea of only sending a certain ammount of build requests was implemented
77             // due to the parent engine being flooded with build requests if the tree being built is very broad.
78             string requestBatchSizeEnvironmentVariable = Environment.GetEnvironmentVariable("MSBUILDREQUESTBATCHSIZE");
79             if (!String.IsNullOrEmpty(requestBatchSizeEnvironmentVariable))
80             {
81                 if (!int.TryParse(requestBatchSizeEnvironmentVariable, out batchRequestSize) || batchRequestSize < 1)
82                 {
83                     // If an invalid RequestBatchSize is passed in set the batchRequestSize back to the default and log a warning.
84                     batchRequestSize = defaultBatchRequestSize;
85                     BuildEventContext buildEventContext = new BuildEventContext(
86                                            nodeId,
87                                            BuildEventContext.InvalidTargetId,
88                                            BuildEventContext.InvalidProjectContextId,
89                                            BuildEventContext.InvalidTaskId
90                                            );
91 
92                     engineCallback.GetParentEngine().LoggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(/* there is truly no file associated with this warning */ String.Empty), "BatchRequestSizeOutOfRange", requestBatchSizeEnvironmentVariable);
93                 }
94             }
95         }
96 
97         #endregion
98 
99         #region Properties
100 
101         /// <summary>
102         /// This property allows a task to query whether or not the system is running in single process mode or multi process mode.
103         /// Single process mode (IsRunningMultipleNodes = false) is where the engine is initialized with the number of cpus = 1 and the engine is not a child engine.
104         /// The engine is in multi process mode (IsRunningMultipleNodes = true) when the engine is initialized with a number of cpus > 1 or the engine is a child engine.
105         /// </summary>
106         internal bool IsRunningMultipleNodes
107         {
108             get
109             {
110                 return isRunningMultipleNodes;
111             }
112         }
113 
114         /// <summary>
115         /// Specifies the traversal type for IBuildEngine callbacks. If true multiple build requests will be sent
116         /// to the engine if false the requests will be sent one at a time.
117         /// </summary>
118         internal bool UseBreadthFirstTraversal
119         {
120             get
121             {
122                 return breadthFirstTraversal;
123             }
124             set
125             {
126                 breadthFirstTraversal = value;
127             }
128         }
129 
130         /// <summary>
131         /// Returns true if the TEM doesn't have a thread in user code and there are no pending
132         /// workitems
133         /// </summary>
134         internal bool IsIdle
135         {
136             get
137             {
138                 return (activeThreadCount == 0 && workerThread.WorkItemCount == 0);
139             }
140         }
141 
142         /// <summary>
143         /// Return total time spent executing the tasks by this TEM. This value is only valid if the TEM is created with
144         /// profileExecution set to true, otherwise this value will be 0
145         /// </summary>
146         internal long TaskExecutionTime
147         {
148             get
149             {
150                 return totalTaskTime;
151             }
152         }
153 
154         #endregion
155         #region Method used internally inside the TEM boundary (i.e. not called from the engine)
156 
157         /// <summary>
158         /// This method passes the task outputs to the engine, it is virtual for testing purposes to
159         /// create a mock TEM
160         /// </summary>
PostTaskOutputs( int handleId, bool taskExecutedSuccessfully, Exception thrownException, long executionTime )161         virtual internal void PostTaskOutputs
162         (
163             int handleId,
164             bool taskExecutedSuccessfully,
165             Exception thrownException,
166             long executionTime
167         )
168         {
169             totalTaskTime += executionTime;
170             engineCallback.PostTaskOutputs(handleId, taskExecutedSuccessfully, thrownException, executionTime);
171         }
172 
173         /// <summary>
174         /// This function implements the callback via the IBuildEngine interface
175         /// </summary>
176         /// <returns>result of call to engine</returns>
BuildProjectFile( int handleId, string[] projectFileNames, string[] targetNames, IDictionary[] globalPropertiesPerProject, IDictionary[] targetOutputsPerProject, EngineLoggingServices loggingServices, string [] toolsVersions, bool useResultsCache, bool unloadProjectsOnCompletion, BuildEventContext taskContext )177         virtual internal bool BuildProjectFile
178         (
179             int handleId,
180             string[] projectFileNames,
181             string[] targetNames,
182             IDictionary[] globalPropertiesPerProject,
183             IDictionary[] targetOutputsPerProject,
184             EngineLoggingServices loggingServices,
185             string [] toolsVersions,
186             bool useResultsCache,
187             bool unloadProjectsOnCompletion,
188             BuildEventContext taskContext
189         )
190         {
191             if (projectFileNames.Length == 0)
192             {
193                 // Nothing to do, just return success
194                 return true;
195             }
196 
197             string currentDir = FileUtilities.GetCurrentDirectoryStaticBuffer(currentDirectoryBuffer);
198 
199             if (Engine.debugMode)
200             {
201                 string targetName = targetNames == null ? "null" : targetNames[0];
202 
203                 bool remoteNode = false;
204                 for (int r = 0; r < projectFileNames.Length; r++)
205                 {
206                     string fullProjectName = projectFileNames[r] != null ?
207                        projectFileNames[r] : "null";
208                     Console.WriteLine("RemoteNode: " + remoteNode + " Project " + fullProjectName + " T:" + targetName + " NodeProdyId# " + handleId + " Time " + DateTime.Now.ToLongTimeString());
209                     if (globalPropertiesPerProject[r] != null)
210                     {
211                         foreach (DictionaryEntry entry in globalPropertiesPerProject[r])
212                         {
213                             Console.WriteLine(currentDir + " :GLOBAL " + entry.Key + "=" + entry.Value.ToString());
214                         }
215                     }
216                 }
217             }
218 
219             BuildRequest[] buildRequests = new BuildRequest[projectFileNames.Length];
220             for (int i = 0; i < buildRequests.Length; i++)
221             {
222                 // We need to get the full path to the project before we call back
223                 // into the engine which has no control over current path
224                 string fullProjectName = projectFileNames[i] != null ?
225                     Path.GetFullPath(projectFileNames[i]) : null;
226 
227                 buildRequests[i] = new BuildRequest(handleId, fullProjectName, targetNames, globalPropertiesPerProject[i],
228                                                     toolsVersions[i], i, useResultsCache, unloadProjectsOnCompletion);
229                 ErrorUtilities.VerifyThrow(buildRequests[i].IsGeneratedRequest == true, "Should not be sending non generated requests from TEM to engine");
230                 buildRequests[i].ParentBuildEventContext = taskContext;
231             }
232 
233             BuildResult[] buildResultsLocal = new BuildResult[projectFileNames.Length];
234 
235             if (moduleMode == TaskExecutionModuleMode.SingleProcMode)
236             {
237                 for (int i = 0; i < projectFileNames.Length; i++)
238                 {
239                     // If we are running in a single threaded mode we need to
240                     // re-enter the main build loop on the current thread in order
241                     // to build the requested project, because the main build is below
242                     // us on the stack
243                     engineCallback.PostBuildRequestsToHost(new BuildRequest[] { buildRequests[i] });
244                     buildResultsLocal[i] = engineCallback.GetParentEngine().EngineBuildLoop(buildRequests[i]);
245                     buildResultsLocal[i].ConvertToTaskItems();
246                 }
247             }
248             else
249             {
250                 WaitForBuildResults(handleId, buildResultsLocal, buildRequests);
251             }
252 
253             // Store the outputs in the hashtables provided by the caller
254             bool overallResult = true;
255             for (int i = 0; i < buildResultsLocal.Length; i++)
256             {
257                 // Users of the Object Model can pass in null targetOutputs for projects they do not want outputs for
258                 // therefore we need to make sure that there are targetoutputs and the users want the results
259                 if (buildResultsLocal[i] != null)
260                 {
261                     if (buildResultsLocal[i].OutputsByTarget != null && targetOutputsPerProject[i] != null)
262                     {
263                         foreach (DictionaryEntry entry in buildResultsLocal[i].OutputsByTarget)
264                         {
265                             targetOutputsPerProject[i].Add(entry.Key, entry.Value);
266                         }
267                         overallResult = overallResult && buildResultsLocal[i].EvaluationResult;
268                     }
269                 }
270                 else
271                 {
272                     // The calculation was terminated prior to receiving the result
273                     overallResult = false;
274                 }
275             }
276 
277             // We're now returning from an IBuildEngine callback;
278             // set the current directory back to what the tasks expect
279             if (Directory.GetCurrentDirectory() != currentDir)
280             {
281                 Directory.SetCurrentDirectory(currentDir);
282             }
283 
284             if (Engine.debugMode)
285             {
286                 bool remoteNode = false;
287                 string targetName = targetNames == null ? "null" : targetNames[0];
288                 Console.WriteLine("RemoteNode: " + remoteNode + " T:" + targetName + " HandleId# " + handleId + " Result " + overallResult);
289             }
290 
291             return overallResult;
292         }
293 
294        /// <summary>
295        /// Once the buildRequests from the EngineCallback have been created they are sent to this method which will
296        /// post the build requests to the parent engine and then wait on the results to come back.
297        /// This method uses either a breadthFirst or depthFirst traversal strategy when sending buildRequests to the parent engine.
298        /// This method will start in breadthFirst traversal. It will continue to use this strategy until one of two events occur:
299        ///     1. The parent node sents a message indicating the TEM should switch to depthFirst traversal.
300        ///     2. The number of buildRequests is larger than the batchRequestSize.
301        /// In both of these cases the system will go from a breadthFirstTraversal to a depthFirst Traversal. In the second case
302        /// a message will be sent to the parent engine to switch the system to depthFirst traversal as the system is starting to
303        /// be overloaded with work.
304        /// In a depth first strategy the buildRequests will be sent to the parent engine one at a time and waiting for results for
305        /// each buildRequest sent. In a breadthFirst traversal strategy some number of the buildrequests will be sent to the parent engine
306        /// in a batch of requests. The system will then wait on the results of ALL the build requests sent before continuing
307        /// to send more build requests.
308        /// </summary>
WaitForBuildResults(int handleId, BuildResult[] buildResultsLocal, BuildRequest[] buildRequests)309        private void WaitForBuildResults(int handleId, BuildResult[] buildResultsLocal, BuildRequest[] buildRequests)
310         {
311             // If the traversal strategy is breadth first and the number of requests is less than the batchRequestSize
312             // or if there is only 1 build request then send ALL build requests to the parent engine and wait on the results.
313             if ((breadthFirstTraversal == true && buildRequests.Length < batchRequestSize) || buildRequests.Length == 1)
314             {
315                 engineCallback.PostBuildRequestsToHost(buildRequests);
316                 workerThread.WaitForResults(handleId, buildResultsLocal, buildRequests);
317             }
318             else
319             {
320                 int currentRequestIndex = 0; // Which build request is being processed
321                 int numberOfRequestsToSend = 0; // How many buildRequests are going to be sent based on the number of buildRequests remaining and the build request batch size.
322 
323                 // Arrays that will be used to partion the buildRequests array when sending batches of builds requests at a time.
324                 BuildRequest[] wrapperArrayBreadthFirst = new BuildRequest[batchRequestSize];
325                 BuildResult[] resultsArrayBreadthFirst = new BuildResult[batchRequestSize];
326 
327                 // Pre allocate these arrays as they will always be only one element in size. They are assigned to and filled when doing a depth first traversal.
328                 BuildRequest[] wrapperArrayDepthFirst = new BuildRequest[1];
329                 BuildResult[] resultsArrayDepthFirst = new BuildResult[1];
330 
331                 // While there are still requests to send
332                 while (currentRequestIndex < buildRequests.Length)
333                 {
334                     // If there is a breadth first traversal and there are more than batchRequestSize build requests, send the first batchRequestSize, then do the rest depth first
335                     if (breadthFirstTraversal == true)
336                     {
337                         // Figure out how many requests to send, either the full batch size or only part of a batch
338                         numberOfRequestsToSend = (buildRequests.Length - currentRequestIndex) <batchRequestSize ? (buildRequests.Length - currentRequestIndex) : batchRequestSize;
339 
340                         // Initialize the wrapper array to how many requests are going to be sent
341                         if (numberOfRequestsToSend != wrapperArrayBreadthFirst.Length)
342                         {
343                             wrapperArrayBreadthFirst = new BuildRequest[numberOfRequestsToSend];
344                             resultsArrayBreadthFirst = new BuildResult[numberOfRequestsToSend];
345                         }
346 
347                         // Fill the wrapper array with one batch of build requests
348                         for (int i = 0; i < numberOfRequestsToSend; i++)
349                         {
350                             wrapperArrayBreadthFirst[i] = buildRequests[currentRequestIndex + i];
351                             wrapperArrayBreadthFirst[i].RequestId = i;
352                             resultsArrayBreadthFirst[i] = null;
353                         }
354 
355                         engineCallback.PostBuildRequestsToHost(wrapperArrayBreadthFirst);
356 
357                         // Only switch from breadth to depth if there are more thanbatchRequestSize items
358                         if ((buildRequests.Length - currentRequestIndex) > batchRequestSize)
359                         {
360                             engineCallback.PostStatus(nodeId, new NodeStatus(false /* use depth first traversal*/), false /* don't block waiting on the send */);
361                             breadthFirstTraversal = false;
362                         }
363 
364                         workerThread.WaitForResults(handleId, resultsArrayBreadthFirst, wrapperArrayBreadthFirst);
365                         Array.Copy(resultsArrayBreadthFirst, 0, buildResultsLocal, currentRequestIndex, numberOfRequestsToSend);
366                         currentRequestIndex += numberOfRequestsToSend;
367                     }
368 
369                     // Proceed with depth first traversal
370                     while ((currentRequestIndex < buildRequests.Length) && !breadthFirstTraversal)
371                     {
372                         wrapperArrayDepthFirst[0] = buildRequests[currentRequestIndex];
373                         buildRequests[currentRequestIndex].RequestId = 0;
374                         resultsArrayDepthFirst[0] = null;
375 
376                         engineCallback.PostBuildRequestsToHost(wrapperArrayDepthFirst);
377                         workerThread.WaitForResults(handleId, resultsArrayDepthFirst, wrapperArrayDepthFirst);
378                         //Copy the result from an intermediate array to the full array
379                         buildResultsLocal[currentRequestIndex] = resultsArrayDepthFirst[0];
380                         //Check if the call failed (null result was returned)
381                         if (buildResultsLocal[currentRequestIndex] == null)
382                         {
383                             return;
384                         }
385 
386                         //Move to the next request
387                         currentRequestIndex++;
388                     }
389                 }
390             }
391         }
392 
393         /// <summary>
394         /// Call into the engine to figure out the line and column number of the task XML node in the original
395         /// project context
396         /// </summary>
GetLineColumnOfXmlNode(int handleId, out int lineNumber, out int columnNumber)397         virtual internal void GetLineColumnOfXmlNode(int handleId, out int lineNumber, out int columnNumber)
398         {
399             engineCallback.GetLineColumnOfXmlNode(handleId, out lineNumber, out columnNumber);
400         }
401 
402         /// <summary>
403         /// Gets the global tasks registry defined by the *.tasks files.
404         /// </summary>
405         /// <returns>Global/default tasks registry.</returns>
GetDefaultTasksRegistry(int handleId)406         internal ITaskRegistry GetDefaultTasksRegistry(int handleId)
407         {
408             return engineCallback.GetEngineTaskRegistry(handleId);
409         }
410 
411         /// <summary>
412         /// Gets the tasks registry for the given project.
413         /// </summary>
414         /// <returns>Project task registry.</returns>
GetProjectTasksRegistry(int handleId)415         internal ITaskRegistry GetProjectTasksRegistry(int handleId)
416         {
417             return engineCallback.GetProjectTaskRegistry(handleId);
418         }
419 
420         /// <summary>
421         /// Gets the path to the tools used for the particular task
422         /// </summary>
GetToolsPath(int handleId)423         internal string GetToolsPath(int handleId)
424         {
425             return engineCallback.GetToolsPath(handleId);
426         }
427 
RethrowTaskExceptions()428         internal bool RethrowTaskExceptions()
429         {
430             return (moduleMode == TaskExecutionModuleMode.SingleProcMode);
431         }
432 
433         #endregion
434 
435         #region Methods called from the engine
436         /// <summary>
437         /// Called to execute a task within a target. This method instantiates the task, sets its parameters,
438         /// and executes it.
439         /// </summary>
440         /// <param name="taskState"></param>
ExecuteTask(TaskExecutionState taskState)441         public void ExecuteTask(TaskExecutionState taskState)
442         {
443             // Fill out the local fields of the task state
444             taskState.ParentModule = this;
445             taskState.LoggingService = engineCallback.GetParentEngine().LoggingServices;
446             taskState.ProfileExecution = profileExecution;
447 
448             // If we running in single proc mode, we should execute this task on the current thread
449             if (moduleMode == TaskExecutionModuleMode.SingleProcMode)
450             {
451                taskState.ExecuteTask();
452             }
453             else
454             {
455                 // In multiproc mode post the work item to the workerThread queue so it can be executed by the worker thread.
456                 workerThread.PostWorkItem(taskState);
457             }
458         }
459 
460         /// <summary>
461         /// Uses the parent engine to get the next unique TaskID.
462         /// </summary>
463         /// <returns></returns>
GetNextTaskId()464         internal int GetNextTaskId()
465         {
466             return engineCallback.GetParentEngine().GetNextTaskId();
467         }
468 
469         /// <summary>
470         /// This method lets the engine provide node with results of an evaluation it was waiting on.
471         /// </summary>
472         /// <param name="buildResult"></param>
PostBuildResults(BuildResult buildResult)473         internal void PostBuildResults(BuildResult buildResult)
474         {
475             // Do nothing in the single proc mode
476             if (moduleMode == TaskExecutionModuleMode.SingleProcMode)
477             {
478                 return;
479             }
480 
481             buildResult.ConvertToTaskItems();
482             workerThread.PostBuildResult(buildResult);
483         }
484 
485         /// <summary>
486         /// This function returns the last time TEM was active executing a task
487         /// </summary>
LastTaskActivity()488         internal long LastTaskActivity()
489         {
490             if (moduleMode != TaskExecutionModuleMode.SingleProcMode)
491             {
492                 if (activeThreadCount == 0 && workerThread.WorkItemCount == 0)
493                 {
494                     return lastTaskActivity;
495                 }
496                 else
497                 {
498                     return DateTime.Now.Ticks;
499                 }
500             }
501             return DateTime.Now.Ticks;
502         }
503 
GetWaitingTaskData(List<BuildRequest []> outstandingRequests)504         internal int[] GetWaitingTaskData(List<BuildRequest []> outstandingRequests)
505         {
506             if (moduleMode != TaskExecutionModuleMode.SingleProcMode)
507             {
508                 return workerThread.GetWaitingTasksData(outstandingRequests);
509             }
510             return new int [0];
511         }
512 
Shutdown()513         internal void Shutdown()
514         {
515             if (moduleMode != TaskExecutionModuleMode.SingleProcMode)
516             {
517                 workerThread.Shutdown();
518 
519                 while (overallThreadCount != 0)
520                 {
521                     threadOverallCountEvent.WaitOne();
522                     threadOverallCountEvent.Reset();
523                 }
524 
525                 if (profileExecution)
526                 {
527                     int taskTimeMs = 0;
528 
529                     if (totalTaskTime != 0)
530                     {
531                         TimeSpan taskTimeSpan = new TimeSpan(totalTaskTime);
532                         taskTimeMs = (int)taskTimeSpan.TotalMilliseconds;
533                     }
534                     Console.WriteLine("Node time spent " + taskTimeMs);
535                 }
536             }
537         }
538         #endregion
539 
540         #region Methods used by worker threads
541 
IncrementOverallThreadCount()542         internal void IncrementOverallThreadCount()
543         {
544             Interlocked.Increment(ref overallThreadCount);
545         }
546 
DecrementOverallThreadCount()547         internal void DecrementOverallThreadCount()
548         {
549             Interlocked.Decrement(ref overallThreadCount);
550             threadOverallCountEvent.Set();
551         }
552 
DecrementActiveThreadCount()553         internal void DecrementActiveThreadCount()
554         {
555             Interlocked.Decrement(ref activeThreadCount);
556             threadActiveCountEvent.Set();
557         }
558 
WaitForZeroActiveThreadCount()559         internal void WaitForZeroActiveThreadCount()
560         {
561             while (Interlocked.CompareExchange(ref activeThreadCount, 1, 0) != 0)
562             {
563                 threadActiveCountEvent.WaitOne();
564                 threadActiveCountEvent.Reset();
565             }
566             lastTaskActivity = DateTime.Now.Ticks;
567         }
568 
569         #endregion
570 
571         #region Methods used for unittest only
572         /// <summary>
573         /// ONLY for unit testing
574         /// </summary>
GetExecutionModuleMode()575         internal TaskExecutionModuleMode GetExecutionModuleMode()
576         {
577            // The Execution module mode is used to determine if they system is running under single proc or multiproc for the purposes of creating a new thread
578            // to execute tasks on.
579            return moduleMode;
580         }
581 
582         /// <summary>
583         ///  ONLY for unit testing
584         /// </summary>
GetWorkerThread()585         internal TaskWorkerThread GetWorkerThread()
586         {
587             return workerThread;
588         }
589         #endregion
590 
591         #region Member data
592         /// <summary>
593         /// Callback interface to communicate with the engine
594         /// </summary>
595         private EngineCallback engineCallback;
596         /// <summary>
597         /// The mode in which the TEM is running
598         /// </summary>
599         TaskExecutionModuleMode moduleMode;
600         /// <summary>
601         /// The class used to execute user tasks.
602         /// </summary>
603         TaskWorkerThread workerThread;
604 
605         // Data shared between all worker threads within the TEM
606         /// <summary>
607         /// Total count of worker threads both active and inactive
608         /// </summary>
609         private int overallThreadCount;
610         /// <summary>
611         /// Event indicated a decrease in overallThreadCount due to an exit of a thread
612         /// </summary>
613         private ManualResetEvent threadOverallCountEvent;
614         /// <summary>
615         /// Count of active thread (i.e. threads in user code). Has to be either 0 or 1
616         /// </summary>
617         private int activeThreadCount;
618         /// <summary>
619         /// Event indicating a decrease in activeThreadCount due to a thread leaving user code
620         /// </summary>
621         private ManualResetEvent threadActiveCountEvent;
622         /// <summary>
623         /// Time stamp of last execution of user code. Only valid if activeThreadCount == 0.
624         /// </summary>
625         private long lastTaskActivity;
626         /// <summary>
627         /// Specifies the traversal type for callbacks. If true multiple build requests will be sent
628         /// to the engine if false the requests will be sent one at a time.
629         /// </summary>
630         private bool breadthFirstTraversal;
631         /// <summary>
632         /// Specifies if the timing data on task execution should be collected
633         /// </summary>
634         private bool profileExecution;
635         /// <summary>
636         /// Total time spent executing task code. Only valid if profileExecution is true.
637         /// </summary>
638         private long totalTaskTime;
639 
640         /// <summary>
641         /// This property allows a task to query whether or not the system is running in single process mode or multi process mode.
642         /// Single process mode (IsRunningMultipleNodes = false) is where the engine is initialized with the number of cpus = 1 and the engine is not a child engine.
643         /// The engine is in multi process mode (IsRunningMultipleNodes = true) when the engine is initialized with a number of cpus > 1 or the engine is a child engine.
644         /// </summary>
645         private bool isRunningMultipleNodes;
646 
647         private static StringBuilder currentDirectoryBuffer = new StringBuilder(270);
648 
649         /// <summary>
650         /// In a multiproc build this is the maximum number of build requests which will be sent at a time to the parent engine
651         /// A default of 10 was an arbitrary number but turned out to be a good balance between being too small
652         /// causing the system to run out of work too quickly and being too big and flooding the system with requests.
653         /// </summary>
654         private const int defaultBatchRequestSize = 10;
655         private int batchRequestSize = defaultBatchRequestSize;
656 
657         /// <summary>
658         /// The nodeId of the node the TaskExecutionModule is running on
659         /// </summary>
660         private int nodeId = -1;
661 
662         #endregion
663 
664         #region Enums
665 
666         internal enum TaskExecutionModuleMode
667         {
668             /// <summary>
669             /// In this mode the tasks should be executed on the calling thread
670             /// </summary>
671             SingleProcMode = 0,
672             /// <summary>
673             /// In this mode the tasks should be executed on a different thread and the execute calls
674             /// should return immediately. The messages due to the task are not flushed.
675             /// </summary>
676             MultiProcFullNodeMode = 1,
677         }
678 
679         #endregion
680     }
681 }
682