private async Task <ResultStatus> StartCommand(IExecuteContext executeContext, ListStore <CommandResultEntry> commandResultEntries, BuilderContext builderContext) { var logger = executeContext.Logger; //await Scheduler.Yield(); ResultStatus status; using (commandResultEntries) { logger.Debug($"Starting command {Command}..."); // Creating the CommandResult object var commandContext = new LocalCommandContext(executeContext, this, builderContext); // Actually processing the command if (Command.ShouldSpawnNewProcess() && builderContext.MaxParallelProcesses > 0 && builderContext.SlaveBuilderPath != null) { while (!builderContext.CanSpawnParallelProcess()) { await Task.Delay(1, Command.CancellationToken); } var address = "net.pipe://localhost/" + Guid.NewGuid(); var arguments = $"--slave=\"{address}\" --build-path=\"{builderContext.BuildPath}\" --profile=\"{builderContext.BuildProfile}\""; using (var debugger = VisualStudioDebugger.GetAttached()) { if (debugger != null) { arguments += $" --reattach-debugger={debugger.ProcessId}"; } } var startInfo = new ProcessStartInfo { FileName = builderContext.SlaveBuilderPath, Arguments = arguments, WorkingDirectory = Environment.CurrentDirectory, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, }; // Start WCF pipe for communication with process var processBuilderRemote = new ProcessBuilderRemote(commandContext, Command); var host = new ServiceHost(processBuilderRemote); host.AddServiceEndpoint(typeof(IProcessBuilderRemote), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { MaxReceivedMessageSize = int.MaxValue }, address); host.Open(); var output = new List <string>(); var process = new Process { StartInfo = startInfo }; process.Start(); process.OutputDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.ErrorDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.BeginOutputReadLine(); process.BeginErrorReadLine(); // Attach debugger to newly created process // Add a reference to EnvDTE80 in the csproj and uncomment this (and also the Thread.Sleep in BuildEngineCmmands), then start the master process without debugger to attach to a slave. //var dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.11.0"); //foreach (EnvDTE.Process dteProcess in dte.Debugger.LocalProcesses) //{ // if (dteProcess.ProcessID == process.Id) // { // dteProcess.Attach(); // dte.Debugger.CurrentProcess = dteProcess; // } //} // Note: we don't want the thread to schedule another job since the CPU core will be in use by the process, so we do a blocking WaitForExit. process.WaitForExit(); host.Close(); builderContext.NotifyParallelProcessEnded(); if (process.ExitCode != 0) { logger.Error($"Remote command crashed with output:{Environment.NewLine}{string.Join(Environment.NewLine, output)}"); } if (processBuilderRemote.Result != null) { // Register results back locally foreach (var outputObject in processBuilderRemote.Result.OutputObjects) { commandContext.RegisterOutput(outputObject.Key, outputObject.Value); } // Register log messages foreach (var logMessage in processBuilderRemote.Result.LogMessages) { commandContext.Logger.Log(logMessage); } // Register tags foreach (var tag in processBuilderRemote.Result.TagSymbols) { commandContext.AddTag(tag.Key, tag.Value); } } status = Command.CancellationToken.IsCancellationRequested ? ResultStatus.Cancelled : (process.ExitCode == 0 ? ResultStatus.Successful : ResultStatus.Failed); } else { Command.PreCommand(commandContext); if (!Command.BasePreCommandCalled) { throw new InvalidOperationException("base.PreCommand not called in command " + Command); } try { // Merge results from prerequisites // TODO: This will prevent us from overwriting this asset with different content as it will result in a write conflict // At some point we _might_ want to get rid of WaitBuildStep/ListBuildStep system and write a fully stateless input/output-based system; probably need further discussions var fileProvider = ContentManager.FileProvider; if (fileProvider != null) { var assetIndexMap = fileProvider.ContentIndexMap; foreach (var prerequisiteStep in PrerequisiteSteps) { foreach (var output in prerequisiteStep.OutputObjectIds) { assetIndexMap[output.Key.Path] = output.Value; } } } status = await Command.DoCommand(commandContext); } catch (Exception ex) { executeContext.Logger.Error("Exception in command " + this + ": " + ex); status = ResultStatus.Failed; } Command.PostCommand(commandContext, status); if (!Command.BasePostCommandCalled) { throw new InvalidOperationException("base.PostCommand not called in command " + Command); } } // Ensure the command set at least the result status if (status == ResultStatus.NotProcessed) { throw new InvalidDataException("The command " + Command + " returned ResultStatus.NotProcessed after completion."); } // Registering the result to the build cache RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status); } return(status); }
public static BuildResultCode BuildSlave(BuilderOptions options) { // Mount build path ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(options.BuildDirectory); PrepareDatabases(options); try { VirtualFileSystem.CreateDirectory("/data/"); VirtualFileSystem.CreateDirectory("/data/db/"); } catch (Exception) { throw new OptionException("Invalid Build database path", "database"); } // Open WCF channel with master builder var namedPipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { SendTimeout = TimeSpan.FromSeconds(300.0) }; var processBuilderRemote = ChannelFactory <IProcessBuilderRemote> .CreateChannel(namedPipeBinding, new EndpointAddress(options.SlavePipe)); try { RegisterRemoteLogger(processBuilderRemote); // Create scheduler var scheduler = new Scheduler(); var status = ResultStatus.NotProcessed; // Schedule command string buildPath = options.BuildDirectory; Logger logger = options.Logger; MicroThread microthread = scheduler.Add(async() => { // Deserialize command and parameters Command command = processBuilderRemote.GetCommandToExecute(); BuildParameterCollection parameters = processBuilderRemote.GetBuildParameters(); // Run command var inputHashes = new DictionaryStore <InputVersionKey, ObjectId>(VirtualFileSystem.OpenStream("/data/db/InputHashes", VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite)); var builderContext = new BuilderContext(buildPath, inputHashes, parameters, 0, null); var commandContext = new RemoteCommandContext(processBuilderRemote, command, builderContext, logger); command.PreCommand(commandContext); status = await command.DoCommand(commandContext); command.PostCommand(commandContext, status); // Returns result to master builder processBuilderRemote.RegisterResult(commandContext.ResultEntry); }); while (true) { scheduler.Run(); // Exit loop if no more micro threads lock (scheduler.MicroThreads) { if (!scheduler.MicroThreads.Any()) { break; } } Thread.Sleep(0); } // Rethrow any exception that happened in microthread if (microthread.Exception != null) { options.Logger.Fatal(microthread.Exception.ToString()); return(BuildResultCode.BuildError); } if (status == ResultStatus.Successful || status == ResultStatus.NotTriggeredWasSuccessful) { return(BuildResultCode.Successful); } return(BuildResultCode.BuildError); } finally { // Close WCF channel // ReSharper disable SuspiciousTypeConversion.Global ((IClientChannel)processBuilderRemote).Close(); // ReSharper restore SuspiciousTypeConversion.Global } }