Exemplo n.º 1
0
        public DurationLogger(string operationName, LogSource logSource, bool isEnabled)
        {
            Helpers.Argument.ValidateIsNotNull(logSource, nameof(logSource));
            Helpers.Argument.ValidateIsNotNullOrWhitespace(operationName, nameof(operationName));

            _operationName = operationName;
            _logSource     = logSource;

            // We enable the duration logging feature to be easily switched off via flag.
            if (!isEnabled)
            {
                return;
            }

            _stopwatch = new Stopwatch();
            _stopwatch.Start();

            _logSource.Debug($"{_operationName} - started.");
        }
Exemplo n.º 2
0
            internal void Start()
            {
                if (Process != null)
                {
                    throw new InvalidOperationException("The instance has already been started.");
                }

                _log.Debug($"Executing: {ExecutablePath} {CensoredArguments}");

                StreamWriter outputFileWriter = null;

                if (!string.IsNullOrWhiteSpace(OutputFilePath))
                {
                    // Make sure the file can be created - parent directory exists.
                    var parent = Path.GetDirectoryName(OutputFilePath);

                    // No need to create it if it is a relative path with no parent.
                    if (!string.IsNullOrWhiteSpace(parent))
                    {
                        Directory.CreateDirectory(parent);
                    }

                    // Create the file.
                    outputFileWriter = File.CreateText(OutputFilePath);
                }

                try
                {
                    var startInfo = new ProcessStartInfo
                    {
                        Arguments              = Arguments,
                        ErrorDialog            = false,
                        FileName               = ExecutablePath,
                        RedirectStandardError  = true,
                        RedirectStandardOutput = true,
                        UseShellExecute        = false,
                        WorkingDirectory       = WorkingDirectory,
                        CreateNoWindow         = true
                    };

                    if (EnvironmentVariables != null)
                    {
                        foreach (var pair in EnvironmentVariables)
                        {
                            startInfo.EnvironmentVariables[pair.Key] = pair.Value;
                        }
                    }

                    if (_standardInputProvider != null)
                    {
                        startInfo.RedirectStandardInput = true;
                    }

                    string standardError  = null;
                    string standardOutput = null;

                    var runtime = Stopwatch.StartNew();

                    using (new CrashDialogSuppressionBlock())
                        Process = Process.Start(startInfo);

                    // We default all external tools to below normal because they are, as a rule, less
                    // important than fast responsive UX, so the system should not be bogged down by them.
                    try
                    {
                        Process.PriorityClass = ProcessPriorityClass.BelowNormal;
                    }
                    catch (InvalidOperationException)
                    {
                        // If the process has already exited, we get this on Windows. This is fine.
                    }
                    catch (Win32Exception ex) when(ex.NativeErrorCode == 3)  // "No such process"
                    {
                        // If the process has already exited, we get this on Linux. This is fine.
                    }

                    // These are only set if they are created by ExternalTool - we don't care about user threads.
                    Thread standardErrorReader  = null;
                    Thread standardOutputReader = null;

                    if (_standardErrorConsumer != null)
                    {
                        // Caller wants to have it. Okay, fine.
                        Helpers.Async.BackgroundThreadInvoke(delegate { _standardErrorConsumer(Process.StandardError.BaseStream); });
                    }
                    else
                    {
                        // We'll store it ourselves.
                        standardErrorReader = new Thread((ThreadStart) delegate
                        {
                            // This should be safe if the process we are starting is well-behaved (i.e. not ADB).
                            standardError = Process.StandardError.ReadToEnd();
                        });

                        standardErrorReader.Start();
                    }

                    if (_standardOutputConsumer != null)
                    {
                        // Caller wants to have it. Okay, fine. We do not need to track this thread.
                        Helpers.Async.BackgroundThreadInvoke(delegate { _standardOutputConsumer(Process.StandardOutput.BaseStream); });
                    }
                    else
                    {
                        // We'll store it ourselves.
                        standardOutputReader = new Thread((ThreadStart) delegate
                        {
                            // This should be safe if the process we are starting is well-behaved (i.e. not ADB).
                            standardOutput = Process.StandardOutput.ReadToEnd();
                        });

                        standardOutputReader.Start();
                    }


                    if (_standardInputProvider != null)
                    {
                        // We don't care about monitoring this later, since ExternalTool does not need to touch stdin.
                        Helpers.Async.BackgroundThreadInvoke(delegate
                        {
                            // Closing stdin after providing input is critical or the app may just hang forever.
                            using (var stdin = Process.StandardInput.BaseStream)
                                _standardInputProvider(stdin);
                        });
                    }

                    var resultThread = new Thread((ThreadStart) delegate
                    {
                        Process.WaitForExit();
                        runtime.Stop();

                        // NB! Streams may stay open and blocked after process exits.
                        // This happens e.g. if you go cmd.exe -> start.exe.
                        // Even if you kill cmd.exe, start.exe remains and keeps the pipes open.
                        standardErrorReader?.Join();
                        standardOutputReader?.Join();

                        if (outputFileWriter != null)
                        {
                            if (standardOutput != null)
                            {
                                outputFileWriter.WriteLine(standardOutput);
                            }

                            if (standardError != null)
                            {
                                outputFileWriter.WriteLine(standardError);
                            }

                            outputFileWriter.Dispose();
                        }

                        _result.TrySetResult(new ExternalToolResult(this, standardOutput, standardError, Process.ExitCode, runtime.Elapsed));
                    });

                    // All the rest happens in the result thread, which waits for the process to exit.
                    resultThread.Start();
                }
                catch (Exception)
                {
                    // Don't leave this lingering if starting the process fails.
                    outputFileWriter?.Dispose();

                    throw;
                }
            }