/// <summary> /// Indicates to the TaskHost that it is no longer needed. /// Called by TaskBuilder when the task using the EngineProxy is done. /// </summary> internal void MarkAsInactive() { lock (_callbackMonitor) { VerifyActiveProxy(); _activeProxy = false; // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. _host = null; _requestEntry = null; // Don't bother clearing the tiny task location _taskLoggingContext = null; _targetBuilderCallback = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engine proxy was never sent across an AppDomain boundary. if (_sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(_sponsor); } _sponsor.Close(); _sponsor = null; } } }
/// <summary> /// Create an instance of the wrapped ITask for a batch run of the task. /// </summary> internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, IDictionary <string, string> taskIdentityParameters, #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif bool isOutOfProc) { bool useTaskFactory = false; IDictionary <string, string> mergedParameters = null; _taskLoggingContext = taskLoggingContext; // Optimization for the common (vanilla AssemblyTaskFactory) case -- only calculate // the task factory parameters if we have any to calculate; otherwise even if we // still launch the task factory, it will be with parameters corresponding to the // current process. if ((_factoryIdentityParameters != null && _factoryIdentityParameters.Count > 0) || (taskIdentityParameters != null && taskIdentityParameters.Count > 0)) { VerifyThrowIdentityParametersValid(taskIdentityParameters, taskLocation, _taskName, "MSBuildRuntime", "MSBuildArchitecture"); mergedParameters = MergeTaskFactoryParameterSets(_factoryIdentityParameters, taskIdentityParameters); useTaskFactory = !NativeMethodsShared.IsMono && (_taskHostFactoryExplicitlyRequested || !TaskHostParametersMatchCurrentProcess(mergedParameters)); } else { // if we don't have any task host parameters specified on either the using task or the // task invocation, then we will run in-proc UNLESS "TaskHostFactory" is explicitly specified // as the task factory. useTaskFactory = _taskHostFactoryExplicitlyRequested; } if (useTaskFactory) { ErrorUtilities.VerifyThrowInternalNull(buildComponentHost, "buildComponentHost"); mergedParameters = mergedParameters ?? new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); string runtime = null; string architecture = null; if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out runtime)) { mergedParameters[XMakeAttributes.runtime] = XMakeAttributes.MSBuildRuntimeValues.clr4; } if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out architecture)) { mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture(); } TaskHostTask task = new TaskHostTask(taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _loadedType #if FEATURE_APPDOMAIN , appDomainSetup #endif ); return(task); } else { #if FEATURE_APPDOMAIN AppDomain taskAppDomain = null; #endif ITask taskInstance = TaskLoader.CreateTask(_loadedType, _taskName, taskLocation.File, taskLocation.Line, taskLocation.Column, new TaskLoader.LogError(ErrorLoggingDelegate) #if FEATURE_APPDOMAIN , appDomainSetup #endif , isOutOfProc #if FEATURE_APPDOMAIN , out taskAppDomain #endif ); #if FEATURE_APPDOMAIN if (taskAppDomain != null) { _tasksAndAppDomains[taskInstance] = taskAppDomain; } #endif return(taskInstance); } }
/// <summary> /// Create an instance of the wrapped ITask for a batch run of the task. /// </summary> internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, IDictionary<string, string> taskIdentityParameters, AppDomainSetup appDomainSetup, bool isOutOfProc) { bool useTaskFactory = false; IDictionary<string, string> mergedParameters = null; _taskLoggingContext = taskLoggingContext; // Optimization for the common (vanilla AssemblyTaskFactory) case -- only calculate // the task factory parameters if we have any to calculate; otherwise even if we // still launch the task factory, it will be with parameters corresponding to the // current process. if ((_factoryIdentityParameters != null && _factoryIdentityParameters.Count > 0) || (taskIdentityParameters != null && taskIdentityParameters.Count > 0)) { VerifyThrowIdentityParametersValid(taskIdentityParameters, taskLocation, _taskName, "MSBuildRuntime", "MSBuildArchitecture"); mergedParameters = MergeTaskFactoryParameterSets(_factoryIdentityParameters, taskIdentityParameters); useTaskFactory = _taskHostFactoryExplicitlyRequested || !TaskHostParametersMatchCurrentProcess(mergedParameters); } else { // if we don't have any task host parameters specified on either the using task or the // task invocation, then we will run in-proc UNLESS "TaskHostFactory" is explicitly specified // as the task factory. useTaskFactory = _taskHostFactoryExplicitlyRequested; } if (useTaskFactory) { ErrorUtilities.VerifyThrowInternalNull(buildComponentHost, "buildComponentHost"); mergedParameters = mergedParameters ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string runtime = null; string architecture = null; if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out runtime)) { mergedParameters[XMakeAttributes.runtime] = XMakeAttributes.MSBuildRuntimeValues.clr4; } if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out architecture)) { mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture(); } TaskHostTask task = new TaskHostTask(taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _loadedType, appDomainSetup); return task; } else { AppDomain taskAppDomain = null; ITask taskInstance = TaskLoader.CreateTask(_loadedType, _taskName, taskLocation.File, taskLocation.Line, taskLocation.Column, new TaskLoader.LogError(ErrorLoggingDelegate), appDomainSetup, isOutOfProc, out taskAppDomain); if (taskAppDomain != null) { _tasksAndAppDomains[taskInstance] = taskAppDomain; } return taskInstance; } }
/// <summary> /// Execute a task object for a given bucket. /// </summary> /// <param name="taskExecutionHost">The host used to execute the task.</param> /// <param name="taskLoggingContext">The logging context.</param> /// <param name="taskHost">The task host for the task.</param> /// <param name="bucket">The batching bucket</param> /// <param name="howToExecuteTask">The task execution mode</param> /// <returns>The result of running the task.</returns> private async Task<WorkUnitResult> ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) { UpdateContinueOnError(bucket, taskHost); bool taskResult = false; WorkUnitResultCode resultCode = WorkUnitResultCode.Success; WorkUnitActionCode actionCode = WorkUnitActionCode.Continue; if (!taskExecutionHost.SetTaskParameters(_taskNode.ParametersForBuild)) { // The task cannot be initialized. ProjectErrorUtilities.VerifyThrowInvalidProject(false, _targetChildInstance.Location, "TaskParametersError", _taskNode.Name, String.Empty); } else { bool taskReturned = false; Exception taskException = null; // If this is the MSBuild task, we need to execute it's special internal method. TaskExecutionHost host = taskExecutionHost as TaskExecutionHost; Type taskType = host.TaskInstance.GetType(); try { if (taskType == typeof(MSBuild)) { MSBuild msbuildTask = host.TaskInstance as MSBuild; ErrorUtilities.VerifyThrow(msbuildTask != null, "Unexpected MSBuild internal task."); _targetBuilderCallback.EnterMSBuildCallbackState(); try { taskResult = await msbuildTask.ExecuteInternal(); } finally { _targetBuilderCallback.ExitMSBuildCallbackState(); } } else if (taskType == typeof(CallTarget)) { CallTarget callTargetTask = host.TaskInstance as CallTarget; taskResult = await callTargetTask.ExecuteInternal(); } else { using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) { taskResult = taskExecutionHost.Execute(); } } } catch (Exception ex) { if (ExceptionHandling.IsCriticalException(ex) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { throw; } taskException = ex; } if (taskException == null) { taskReturned = true; // Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not. // The main use of this is if ContinueOnError is true -- so that the next task can consult the result. // So we want it to be "false" even if ContinueOnError is true. // The constants "true" and "false" should NOT be localized. They become property values. bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable)); } else { Type type = taskException.GetType(); if (type == typeof(LoggerException)) { // if a logger has failed, abort immediately // Polite logger failure _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new LoggerException(taskException.Message, taskException); } else if (type == typeof(InternalLoggerException)) { // Logger threw arbitrary exception _continueOnError = ContinueOnError.ErrorAndStop; InternalLoggerException ex = taskException as InternalLoggerException; // Rethrow wrapped in order to avoid losing the callstack throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException); } else if (type == typeof(ThreadAbortException)) { Thread.ResetAbort(); _continueOnError = ContinueOnError.ErrorAndStop; // Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor // Stack will be lost throw taskException; } else if (type == typeof(BuildAbortedException)) { _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new BuildAbortedException(taskException.Message, ((BuildAbortedException)taskException)); } else if (type == typeof(CircularDependencyException)) { _continueOnError = ContinueOnError.ErrorAndStop; ProjectErrorUtilities.ThrowInvalidProject(taskLoggingContext.Task.Location, "CircularDependency", taskLoggingContext.TargetLoggingContext.Target.Name); } else if (type == typeof(InvalidProjectFileException)) { // Just in case this came out of a task, make sure it's not // marked as having been logged. InvalidProjectFileException ipex = (InvalidProjectFileException)taskException; ipex.HasBeenLogged = false; if (_continueOnError != ContinueOnError.ErrorAndStop) { taskLoggingContext.LogInvalidProjectFileError(ipex); taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { // Rethrow wrapped in order to avoid losing the callstack throw new InvalidProjectFileException(ipex.Message, ipex); } } else if (type == typeof(Exception) || type.IsSubclassOf(typeof(Exception))) { // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with // a debugger attached to break on 2nd chance exceptions. // That requires that there needs to be a way to not catch here, by setting an environment variable. if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { // Wrapping in an Exception will unfortunately mean that this exception would fly through any IsCriticalException above. // However, we should not have any, also we should not have stashed such an exception anyway. throw new Exception(taskException.Message, taskException); } Exception exceptionToLog = taskException; if (exceptionToLog is TargetInvocationException) { exceptionToLog = exceptionToLog.InnerException; } // handle any exception thrown by the task during execution // NOTE: We catch ALL exceptions here, to attempt to completely isolate the Engine // from failures in the task. if (_continueOnError == ContinueOnError.WarnAndContinue) { taskLoggingContext.LogTaskWarningFromException ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); // Log a message explaining why we converted the previous error into a warning. taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { taskLoggingContext.LogFatalTaskError ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); } } else { ErrorUtilities.ThrowInternalErrorUnreachable(); } } // If the task returned attempt to gather its outputs. If gathering outputs fails set the taskResults // to false if (taskReturned) { taskResult = GatherTaskOutputs(taskExecutionHost, howToExecuteTask, bucket) && taskResult; } // If the taskResults are false look at ContinueOnError. If ContinueOnError=false (default) // mark the taskExecutedSuccessfully=false. Otherwise let the task succeed but log a normal // pri message that says this task is continuing because ContinueOnError=true resultCode = taskResult ? WorkUnitResultCode.Success : WorkUnitResultCode.Failed; actionCode = WorkUnitActionCode.Continue; if (resultCode == WorkUnitResultCode.Failed) { if (_continueOnError == ContinueOnError.ErrorAndStop) { actionCode = WorkUnitActionCode.Stop; } else { // This is the ErrorAndContinue or WarnAndContinue case... string settingString = "true"; if (_taskNode.ContinueOnErrorLocation != null) { settingString = bucket.Expander.ExpandIntoStringAndUnescape(_taskNode.ContinueOnError, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata } taskLoggingContext.LogComment ( MessageImportance.Normal, "TaskContinuedDueToContinueOnError", "ContinueOnError", _taskNode.Name, settingString ); actionCode = WorkUnitActionCode.Continue; } } } WorkUnitResult result = new WorkUnitResult(resultCode, actionCode, null); return result; }
/// <summary> /// Initializes and executes the task. /// </summary> private async Task<WorkUnitResult> InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { if (!_taskExecutionHost.InitializeForBatch(taskLoggingContext, bucket, taskIdentityParameters)) { ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskDeclarationOrUsageError", _taskNode.Name); } try { // UNDONE: Move this and the task host. taskHost.LoggingContext = taskLoggingContext; WorkUnitResult executionResult = await ExecuteInstantiatedTask(_taskExecutionHost, taskLoggingContext, taskHost, bucket, howToExecuteTask); ErrorUtilities.VerifyThrow(executionResult != null, "Unexpected null execution result"); return executionResult; } finally { _taskExecutionHost.CleanupForBatch(); } }
private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); Thread staThread = null; Exception exceptionFromExecution = null; ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); try { ThreadStart taskRunnerDelegate = delegate () { Lookup.Scope scope = bucket.Lookup.EnterScope("STA Thread for Task"); try { taskResult = InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask).Result; } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } exceptionFromExecution = e; } finally { scope.LeaveScope(); taskRunnerFinished.Set(); } }; staThread = new Thread(taskRunnerDelegate); staThread.SetApartmentState(ApartmentState.STA); staThread.Name = "MSBuild STA task runner thread"; staThread.CurrentCulture = _componentHost.BuildParameters.Culture; staThread.CurrentUICulture = _componentHost.BuildParameters.UICulture; staThread.Start(); // TODO: Why not just Join on the thread??? taskRunnerFinished.WaitOne(); } finally { taskRunnerFinished.Close(); taskRunnerFinished = null; } if (exceptionFromExecution != null) { // Unfortunately this will reset the callstack throw exceptionFromExecution; } return taskResult; }
/// <summary> /// Create an instance of the wrapped ITask for a batch run of the task. /// </summary> public ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, AppDomainSetup appDomainSetup, bool isOutOfProc) { separateAppDomain = false; separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute(); taskAppDomain = null; if (separateAppDomain) { if (!loadedType.Type.IsMarshalByRef) { taskLoggingContext.LogError ( new BuildEventFileInfo(taskLocation), "TaskNotMarshalByRef", taskName ); return null; } else { // Our task depend on this name to be precisely that, so if you change it make sure // you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it. // Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain // has done the binding redirection in code, that we will get those settings as well. AppDomainSetup appDomainInfo = new AppDomainSetup(); // Get the current app domain setup settings byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes(); // Apply the appdomain settings to the new appdomain before creating it appDomainInfo.SetConfigurationBytes(currentAppdomainBytes); taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); // Hook up last minute dumping of any exceptions taskAppDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionHandling.UnhandledExceptionHandler); } } // instantiate the task in given domain if (taskAppDomain == null || taskAppDomain == AppDomain.CurrentDomain) { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. taskInstance = (ITask)Activator.CreateInstance(loadedType.Type); return taskInstance; } if (loadedType.Assembly.AssemblyFile != null) { taskInstance = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); // this will force evaluation of the task class type and try to load the task assembly Type taskType = taskInstance.GetType(); // If the types don't match, we have a problem. It means that our AppDomain was able to load // a task assembly using Load, and loaded a different one. I don't see any other choice than // to fail here. if (taskType != loadedType.Type) { taskLoggingContext.LogError ( new BuildEventFileInfo(taskLocation), "ConflictingTaskAssembly", loadedType.Assembly.AssemblyFile, loadedType.Type.Assembly.Location ); taskInstance = null; } } else { taskInstance = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName); } return taskInstance; }
/// <summary> /// Indicates to the TaskHost that it is no longer needed. /// Called by TaskBuilder when the task using the EngineProxy is done. /// </summary> internal void MarkAsInactive() { lock (_callbackMonitor) { VerifyActiveProxy(); _activeProxy = false; // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. _host = null; _requestEntry = null; // Don't bother clearing the tiny task location _taskLoggingContext = null; _targetBuilderCallback = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engineproxy was never sent across an appdomain boundry. if (_sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(_sponsor); } _sponsor.Close(); _sponsor = null; } } }