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 // </copyright>
5 // <summary>Class implementing an in-proc node.</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Collections.Concurrent;
12 using System.Diagnostics;
13 using System.Linq;
14 using System.Text;
15 using System.Globalization;
16 using System.Threading;
17 using Microsoft.Build.Execution;
18 using Microsoft.Build.Evaluation;
19 using Microsoft.Build.Framework;
20 using Microsoft.Build.Internal;
21 using Microsoft.Build.Shared;
22 
23 using BuildEventArgTransportSink = Microsoft.Build.BackEnd.Logging.BuildEventArgTransportSink;
24 using LoggingService = Microsoft.Build.BackEnd.Logging.LoggingService;
25 using LoggingServiceFactory = Microsoft.Build.BackEnd.Logging.LoggingServiceFactory;
26 using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
27 using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext;
28 using LoggingExceptionDelegate = Microsoft.Build.BackEnd.Logging.LoggingExceptionDelegate;
29 using Microsoft.Build.BackEnd.Components.Caching;
30 
31 namespace Microsoft.Build.BackEnd
32 {
33     /// <summary>
34     /// This class represents an implementation of INode for out-of-proc nodes.
35     /// </summary>
36     internal class InProcNode : INode, INodePacketFactory
37     {
38         /// <summary>
39         /// The build component host.
40         /// </summary>
41         private IBuildComponentHost _componentHost;
42 
43         /// <summary>
44         /// The environment at the time the build is started.
45         /// </summary>
46         private IDictionary<string, string> _savedEnvironment;
47 
48         /// <summary>
49         /// The current directory at the time the build is started.
50         /// </summary>
51         private string _savedCurrentDirectory;
52 
53         /// <summary>
54         /// The node logging context.
55         /// </summary>
56         private NodeLoggingContext _loggingContext;
57 
58         /// <summary>
59         /// The build request engine.
60         /// </summary>
61         private IBuildRequestEngine _buildRequestEngine;
62 
63         /// <summary>
64         /// The current node configuration
65         /// </summary>
66         private NodeConfiguration _currentConfiguration;
67 
68         /// <summary>
69         /// The queue of packets we have received but which have not yet been processed.
70         /// </summary>
71         private ConcurrentQueue<INodePacket> _receivedPackets;
72 
73         /// <summary>
74         /// The event which is set when we receive packets.
75         /// </summary>
76         private AutoResetEvent _packetReceivedEvent;
77 
78         /// <summary>
79         /// The event which is set when we should shut down.
80         /// </summary>
81         private AutoResetEvent _shutdownEvent;
82 
83         /// <summary>
84         /// The reason we are shutting down.
85         /// </summary>
86         private NodeEngineShutdownReason _shutdownReason;
87 
88         /// <summary>
89         /// The exception, if any, which caused shutdown.
90         /// </summary>
91         private Exception _shutdownException;
92 
93         /// <summary>
94         /// The set of configurations which have had projects loaded.
95         /// </summary>
96         private HashSet<NGen<int>> _configurationProjectsLoaded;
97 
98         /// <summary>
99         /// The node endpoint
100         /// </summary>
101         private INodeEndpoint _nodeEndpoint;
102 
103         /// <summary>
104         /// Handler for engine exceptions.
105         /// </summary>
106         private EngineExceptionDelegate _engineExceptionEventHandler;
107 
108         /// <summary>
109         /// Handler for new configuration requests.
110         /// </summary>
111         private NewConfigurationRequestDelegate _newConfigurationRequestEventHandler;
112 
113         /// <summary>
114         /// Handler for blocked request events.
115         /// </summary>
116         private RequestBlockedDelegate _requestBlockedEventHandler;
117 
118         /// <summary>
119         /// Handler for request completed events.
120         /// </summary>
121         private RequestCompleteDelegate _requestCompleteEventHandler;
122 
123         /// <summary>
124         /// Constructor.
125         /// </summary>
InProcNode(IBuildComponentHost componentHost, INodeEndpoint inProcNodeEndpoint)126         public InProcNode(IBuildComponentHost componentHost, INodeEndpoint inProcNodeEndpoint)
127         {
128             _componentHost = componentHost;
129             _nodeEndpoint = inProcNodeEndpoint;
130             _receivedPackets = new ConcurrentQueue<INodePacket>();
131             _packetReceivedEvent = new AutoResetEvent(false);
132             _shutdownEvent = new AutoResetEvent(false);
133 
134             _configurationProjectsLoaded = new HashSet<NGen<int>>();
135 
136             _buildRequestEngine = componentHost.GetComponent(BuildComponentType.RequestEngine) as IBuildRequestEngine;
137 
138             _engineExceptionEventHandler = new EngineExceptionDelegate(OnEngineException);
139             _newConfigurationRequestEventHandler = new NewConfigurationRequestDelegate(OnNewConfigurationRequest);
140             _requestBlockedEventHandler = new RequestBlockedDelegate(OnNewRequest);
141             _requestCompleteEventHandler = new RequestCompleteDelegate(OnRequestComplete);
142         }
143 
144         #region INode Members
145 
146         /// <summary>
147         /// Starts up the node and processes messages until the node is requested to shut down.
148         /// </summary>
149         /// <param name="shutdownException">The exception which caused shutdown, if any.</param>
150         /// <returns>The reason for shutting down.</returns>
Run(out Exception shutdownException)151         public NodeEngineShutdownReason Run(out Exception shutdownException)
152         {
153             try
154             {
155                 _nodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(OnLinkStatusChanged);
156                 _nodeEndpoint.Listen(this);
157 
158                 WaitHandle[] waitHandles = new WaitHandle[] { _shutdownEvent, _packetReceivedEvent };
159 
160                 // Get the current directory before doing work. We need this so we can restore the directory when the node shuts down.
161                 _savedCurrentDirectory = NativeMethodsShared.GetCurrentDirectory();
162                 while (true)
163                 {
164                     int index = WaitHandle.WaitAny(waitHandles);
165                     switch (index)
166                     {
167                         case 0:
168                             {
169                                 NodeEngineShutdownReason shutdownReason = HandleShutdown(out shutdownException);
170                                 if (_componentHost.BuildParameters.ShutdownInProcNodeOnBuildFinish)
171                                 {
172                                     return shutdownReason;
173                                 }
174 
175                                 break;
176                             }
177 
178                         case 1:
179                             INodePacket packet = null;
180 
181                             while (_receivedPackets.TryDequeue(out packet))
182                             {
183                                 if (packet != null)
184                                 {
185                                     HandlePacket(packet);
186                                 }
187                             }
188 
189                             break;
190                     }
191                 }
192             }
193 #if FEATURE_VARIOUS_EXCEPTIONS
194             catch (ThreadAbortException)
195             {
196                 // Do nothing.  This will happen when the thread is forcibly terminated because we are shutting down, for example
197                 // when the unit test framework terminates.
198                 throw;
199             }
200 #endif
201             catch (Exception e)
202             {
203                 // Dump all engine exceptions to a temp file
204                 // so that we have something to go on in the
205                 // event of a failure
206                 ExceptionHandling.DumpExceptionToFile(e);
207 
208                 // This is fatal: process will terminate: make sure the
209                 // debugger launches
210                 ErrorUtilities.ThrowInternalError(e.Message, e);
211                 throw;
212             }
213 
214             // UNREACHABLE
215         }
216 
217         #endregion
218 
219         #region INodePacketFactory Members
220 
221         /// <summary>
222         /// Not necessary for in-proc node - we don't serialize.
223         /// </summary>
RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)224         public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)
225         {
226             // The in-proc node doesn't need to do this.
227         }
228 
229         /// <summary>
230         /// Not necessary for in-proc node - we don't serialize.
231         /// </summary>
UnregisterPacketHandler(NodePacketType packetType)232         public void UnregisterPacketHandler(NodePacketType packetType)
233         {
234             // The in-proc node doesn't need to do this.
235         }
236 
237         /// <summary>
238         /// Not necessary for in-proc node - we don't serialize.
239         /// </summary>
DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, INodePacketTranslator translator)240         public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, INodePacketTranslator translator)
241         {
242             // The in-proc endpoint shouldn't be serializing, just routing.
243             ErrorUtilities.ThrowInternalError("Unexpected call to DeserializeAndRoutePacket on the in-proc node.");
244         }
245 
246         /// <summary>
247         /// Routes the packet to the appropriate handler.
248         /// </summary>
249         /// <param name="nodeId">The node id.</param>
250         /// <param name="packet">The packet.</param>
RoutePacket(int nodeId, INodePacket packet)251         public void RoutePacket(int nodeId, INodePacket packet)
252         {
253             _receivedPackets.Enqueue(packet);
254             _packetReceivedEvent.Set();
255         }
256 
257         #endregion
258 
259         /// <summary>
260         /// Event handler for the BuildEngine's OnRequestComplete event.
261         /// </summary>
OnRequestComplete(BuildRequest request, BuildResult result)262         private void OnRequestComplete(BuildRequest request, BuildResult result)
263         {
264             if (_nodeEndpoint.LinkStatus == LinkStatus.Active)
265             {
266                 _nodeEndpoint.SendData(result);
267             }
268         }
269 
270         /// <summary>
271         /// Event handler for the BuildEngine's OnNewRequest event.
272         /// </summary>
OnNewRequest(BuildRequestBlocker blocker)273         private void OnNewRequest(BuildRequestBlocker blocker)
274         {
275             if (_nodeEndpoint.LinkStatus == LinkStatus.Active)
276             {
277                 _nodeEndpoint.SendData(blocker);
278             }
279         }
280 
281         /// <summary>
282         /// Event handler for the BuildEngine's OnNewConfigurationRequest event.
283         /// </summary>
OnNewConfigurationRequest(BuildRequestConfiguration config)284         private void OnNewConfigurationRequest(BuildRequestConfiguration config)
285         {
286             if (_nodeEndpoint.LinkStatus == LinkStatus.Active)
287             {
288                 _nodeEndpoint.SendData(config);
289             }
290         }
291 
292         /// <summary>
293         /// Event handler for the LoggingService's OnLoggingThreadException event.
294         /// </summary>
OnLoggingThreadException(Exception e)295         private void OnLoggingThreadException(Exception e)
296         {
297             OnEngineException(e);
298         }
299 
300         /// <summary>
301         /// Event handler for the BuildEngine's OnEngineException event.
302         /// </summary>
OnEngineException(Exception e)303         private void OnEngineException(Exception e)
304         {
305             _shutdownException = e;
306             _shutdownReason = NodeEngineShutdownReason.Error;
307             _shutdownEvent.Set();
308         }
309 
310         /// <summary>
311         /// Perform necessary actions to shut down the node.
312         /// </summary>
HandleShutdown(out Exception exception)313         private NodeEngineShutdownReason HandleShutdown(out Exception exception)
314         {
315             // Console.WriteLine("Node shutting down with reason {0} and exception: {1}", shutdownReason, shutdownException);
316             try
317             {
318                 // Clean up the engine
319                 if (null != _buildRequestEngine && _buildRequestEngine.Status != BuildRequestEngineStatus.Uninitialized)
320                 {
321                     _buildRequestEngine.CleanupForBuild();
322                 }
323             }
324             catch (Exception ex)
325             {
326                 if (ExceptionHandling.IsCriticalException(ex))
327                 {
328                     throw;
329                 }
330 
331                 // If we had some issue shutting down, don't reuse the node because we may be in some weird state.
332                 if (_shutdownReason == NodeEngineShutdownReason.BuildCompleteReuse)
333                 {
334                     _shutdownReason = NodeEngineShutdownReason.BuildComplete;
335                 }
336             }
337 
338             // Dispose of any build registered objects
339             IRegisteredTaskObjectCache objectCache = (IRegisteredTaskObjectCache)(_componentHost.GetComponent(BuildComponentType.RegisteredTaskObjectCache));
340             objectCache.DisposeCacheObjects(RegisteredTaskObjectLifetime.Build);
341 
342             if (_shutdownReason != NodeEngineShutdownReason.BuildCompleteReuse)
343             {
344                 // Dispose of any node registered objects.
345                 ((IBuildComponent)objectCache).ShutdownComponent();
346             }
347 
348             if (_componentHost.BuildParameters.SaveOperatingEnvironment)
349             {
350                 // Restore the original current directory.
351                 NativeMethodsShared.SetCurrentDirectory(_savedCurrentDirectory);
352 
353                 // Restore the original environment.
354                 foreach (KeyValuePair<string, string> entry in CommunicationsUtilities.GetEnvironmentVariables())
355                 {
356                     if (!_savedEnvironment.ContainsKey(entry.Key))
357                     {
358                         Environment.SetEnvironmentVariable(entry.Key, null);
359                     }
360                 }
361 
362                 foreach (KeyValuePair<string, string> entry in _savedEnvironment)
363                 {
364                     Environment.SetEnvironmentVariable(entry.Key, entry.Value);
365                 }
366             }
367 
368             exception = _shutdownException;
369 
370             if (null != _loggingContext)
371             {
372                 _loggingContext.LoggingService.OnLoggingThreadException -= new LoggingExceptionDelegate(OnLoggingThreadException);
373                 _loggingContext = null;
374             }
375 
376             // Notify the BuildManager that we are done.
377             if (_nodeEndpoint.LinkStatus == LinkStatus.Active)
378             {
379                 _nodeEndpoint.SendData(new NodeShutdown(_shutdownReason == NodeEngineShutdownReason.Error ? NodeShutdownReason.Error : NodeShutdownReason.Requested, exception));
380             }
381 
382             _buildRequestEngine.OnEngineException -= _engineExceptionEventHandler;
383             _buildRequestEngine.OnNewConfigurationRequest -= _newConfigurationRequestEventHandler;
384             _buildRequestEngine.OnRequestBlocked -= _requestBlockedEventHandler;
385             _buildRequestEngine.OnRequestComplete -= _requestCompleteEventHandler;
386 
387             return _shutdownReason;
388         }
389 
390         /// <summary>
391         /// Dispatches the packet to the correct handler.
392         /// </summary>
HandlePacket(INodePacket packet)393         private void HandlePacket(INodePacket packet)
394         {
395             switch (packet.Type)
396             {
397                 case NodePacketType.BuildRequest:
398                     HandleBuildRequest(packet as BuildRequest);
399                     break;
400 
401                 case NodePacketType.BuildRequestConfiguration:
402                     HandleBuildRequestConfiguration(packet as BuildRequestConfiguration);
403                     break;
404 
405                 case NodePacketType.BuildRequestConfigurationResponse:
406                     HandleBuildRequestConfigurationResponse(packet as BuildRequestConfigurationResponse);
407                     break;
408 
409                 case NodePacketType.BuildRequestUnblocker:
410                     HandleBuildResult(packet as BuildRequestUnblocker);
411                     break;
412 
413                 case NodePacketType.NodeConfiguration:
414                     HandleNodeConfiguration(packet as NodeConfiguration);
415                     break;
416 
417                 case NodePacketType.NodeBuildComplete:
418                     HandleNodeBuildComplete(packet as NodeBuildComplete);
419                     break;
420             }
421         }
422 
423         /// <summary>
424         /// Event handler for the node endpoint's LinkStatusChanged event.
425         /// </summary>
OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status)426         private void OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status)
427         {
428             switch (status)
429             {
430                 case LinkStatus.ConnectionFailed:
431                 case LinkStatus.Failed:
432                     _shutdownReason = NodeEngineShutdownReason.ConnectionFailed;
433                     _shutdownEvent.Set();
434                     break;
435 
436                 case LinkStatus.Inactive:
437                     break;
438 
439                 default:
440                     break;
441             }
442         }
443 
444         /// <summary>
445         /// Handles the BuildRequest packet.
446         /// </summary>
HandleBuildRequest(BuildRequest request)447         private void HandleBuildRequest(BuildRequest request)
448         {
449             _buildRequestEngine.SubmitBuildRequest(request);
450         }
451 
452         /// <summary>
453         /// Handles the BuildRequestConfiguration packet.
454         /// </summary>
HandleBuildRequestConfiguration(BuildRequestConfiguration configuration)455         private void HandleBuildRequestConfiguration(BuildRequestConfiguration configuration)
456         {
457             // Configurations are already in the cache, which we share with the BuildManager.
458         }
459 
460         /// <summary>
461         /// Handles the BuildRequestConfigurationResponse packet.
462         /// </summary>
HandleBuildRequestConfigurationResponse(BuildRequestConfigurationResponse response)463         private void HandleBuildRequestConfigurationResponse(BuildRequestConfigurationResponse response)
464         {
465             _buildRequestEngine.ReportConfigurationResponse(response);
466         }
467 
468         /// <summary>
469         /// Handles the BuildResult packet.
470         /// </summary>
HandleBuildResult(BuildRequestUnblocker unblocker)471         private void HandleBuildResult(BuildRequestUnblocker unblocker)
472         {
473             _buildRequestEngine.UnblockBuildRequest(unblocker);
474         }
475 
476         /// <summary>
477         /// Handles the NodeConfiguration packet.
478         /// </summary>
HandleNodeConfiguration(NodeConfiguration configuration)479         private void HandleNodeConfiguration(NodeConfiguration configuration)
480         {
481             // Set the culture.
482             CultureInfo.CurrentCulture = configuration.BuildParameters.Culture;
483             CultureInfo.CurrentUICulture = configuration.BuildParameters.UICulture;
484 
485             // Snapshot the initial environment.
486             _savedEnvironment = CommunicationsUtilities.GetEnvironmentVariables();
487 
488             // Save the current directory.
489             _savedCurrentDirectory = NativeMethodsShared.GetCurrentDirectory();
490 
491             // Set the node id.
492             _componentHost.BuildParameters.NodeId = configuration.NodeId;
493             _shutdownException = null;
494 
495 #if FEATURE_APPDOMAIN
496             // And the AppDomainSetup
497             _componentHost.BuildParameters.AppDomainSetup = configuration.AppDomainSetup;
498 #endif
499 
500             // Declare in-proc
501             _componentHost.BuildParameters.IsOutOfProc = false;
502 
503             // Set the logging exception handler
504             ILoggingService loggingService = _componentHost.LoggingService;
505             loggingService.OnLoggingThreadException += new LoggingExceptionDelegate(OnLoggingThreadException);
506 
507             // Now prep the buildRequestEngine for the build.
508             _loggingContext = new NodeLoggingContext(loggingService, configuration.NodeId, true /* inProcNode */);
509 
510             _buildRequestEngine.OnEngineException += _engineExceptionEventHandler;
511             _buildRequestEngine.OnNewConfigurationRequest += _newConfigurationRequestEventHandler;
512             _buildRequestEngine.OnRequestBlocked += _requestBlockedEventHandler;
513             _buildRequestEngine.OnRequestComplete += _requestCompleteEventHandler;
514 
515             if (_shutdownException != null)
516             {
517                 Exception exception;
518                 HandleShutdown(out exception);
519                 throw exception;
520             }
521 
522             _buildRequestEngine.InitializeForBuild(_loggingContext);
523 
524             // Finally store off this configuration packet.
525             _currentConfiguration = configuration;
526         }
527 
528         /// <summary>
529         /// Handles the NodeBuildComplete packet.
530         /// </summary>
HandleNodeBuildComplete(NodeBuildComplete buildComplete)531         private void HandleNodeBuildComplete(NodeBuildComplete buildComplete)
532         {
533             _shutdownReason = buildComplete.PrepareForReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete;
534             _shutdownEvent.Set();
535         }
536     }
537 }
538