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