public void DumpProcessThatHasExited()
        {
            string  cmdPath = Environment.GetEnvironmentVariable("comspec");
            Process p       = Process.Start(cmdPath, " /c");

            p.WaitForExit();
            Exception dumpException;
            bool      result = ProcessDumper.TryDumpProcessAndChildren(p.Id, TemporaryDirectory, out dumpException);

            XAssert.IsFalse(result, "Expected failure since there is no process to dump");
        }
        public void OnlyDumpUnderSameUsername()
        {
            string  cmdPath = Environment.GetEnvironmentVariable("comspec");
            Process p       = Process.Start(cmdPath, " /c");

            p.WaitForExit();
            var startTime = p.StartTime;

            Process systemProcess = Process.GetProcessesByName("System")[0];

            XAssert.IsNotNull(systemProcess);

            Exception exception;

            bool result = ProcessDumper.TryDumpProcessAndChildren(systemProcess.Id, TemporaryDirectory, out exception);
        }
Beispiel #3
0
        public void DumpProcessTreeWhenProcessDoesNotExist()
        {
            string dumpPath = Path.Combine(TemporaryDirectory, "dumps2");

            Directory.CreateDirectory(dumpPath);

            var       nonExistentProcessId = -1;
            Exception failure;
            bool      ok = ProcessDumper.TryDumpProcessAndChildren(nonExistentProcessId, dumpPath, out failure);

            XAssert.IsFalse(ok, "Expected dump to fail");
            XAssert.IsNotNull(failure);
            XAssert.AreEqual(typeof(BuildXLException), failure.GetType());
            var failureSnippet = $"ArgumentException: Process with an Id of {nonExistentProcessId} is not running";

            XAssert.IsTrue(failure.ToString().Contains(failureSnippet), $"Expected error to contain '{failureSnippet}' but it doesn't: '{failure}'");
        }
Beispiel #4
0
        public void DumpProcessTreeTest()
        {
            // This test has proven to occasionally be flakey, primarily by failing to create some of the dump files.
            // We add retries to prevent it from failing our automation.
            int MaxTries = 4;

            for (int i = 1; i <= MaxTries; i++)
            {
                try
                {
                    string dumpPath = Path.Combine(TemporaryDirectory, "dumps" + i);
                    Directory.CreateDirectory(dumpPath);
                    var       cmd = CmdHelper.CmdX64;
                    Process   p   = null;
                    bool      success;
                    Exception failure;
                    try
                    {
                        p = Process.Start(new ProcessStartInfo(cmd, string.Format(@" /K {0} /K {0} /K ping localhost -t", cmd))
                        {
                            CreateNoWindow         = true,
                            WindowStyle            = ProcessWindowStyle.Hidden,
                            RedirectStandardOutput = true,
                            UseShellExecute        = false,
                        });

                        // Make sure ping is actually running before dumping. Otherwise the process tree might not be fully created.
                        bool          found  = false;
                        Stopwatch     sw     = Stopwatch.StartNew();
                        StringBuilder output = new StringBuilder();
                        while (sw.Elapsed.TotalSeconds < 30)
                        {
                            string line = p.StandardOutput.ReadLine();
                            output.AppendLine(line);
                            if (line.Contains("Pinging"))
                            {
                                found = true;
                                break;
                            }
                        }

                        if (!found)
                        {
                            TestRetryException.Assert(false, "Didn't find result of ping in standard out. Full output:" + Environment.NewLine +
                                                      output.ToString() + Environment.NewLine + p.StandardError.ReadToEnd());
                        }

                        var processIds = ProcessDumper.GetProcessTreeIds(p.Id, 0);
                        XAssert.IsTrue(processIds.Count == 1, processIds.Count.ToString());

                        success = ProcessDumper.TryDumpProcessAndChildren(p.Id, dumpPath, out failure);
                    }
                    finally
                    {
                        Stopwatch killTimer = Stopwatch.StartNew();
                        if (p != null)
                        {
                            while (killTimer.Elapsed.TotalSeconds < 10)
                            {
                                // Make sure we kill all of the child processes before exiting the test
                                var kill = Process.Start(new ProcessStartInfo("taskkill", "/pid " + p.Id + " /T /F")
                                {
                                    CreateNoWindow = true,
                                    WindowStyle    = ProcessWindowStyle.Hidden,
                                });
                                kill.WaitForExit();

                                if (p.HasExited)
                                {
                                    break;
                                }
                                else
                                {
                                    Thread.Sleep(200);
                                }
                            }
                        }
                    }

                    // Make sure all of the dumps exist
                    TestRetryException.Assert(success, "Dumping processes failed: " + (failure == null ? "Failure was null" : failure.ToString()));

                    AssertFileExists(Path.Combine(dumpPath, "1_cmd.exe.dmp"));

#if !FEATURE_CORECLR // ConHost is not launched with the CoreCLR through the xUnit runner
                    AssertFileExists(Path.Combine(dumpPath, "1_2_cmd.exe.dmp"));
                    AssertFileExists(Path.Combine(dumpPath, "1_2_1_cmd.exe.dmp"));
                    AssertFileExists(Path.Combine(dumpPath, "1_2_1_1_PING.EXE.dmp"));
#endif
                }
                catch (TestRetryException ex)
                {
                    if (i >= MaxTries)
                    {
                        TestRetryException.Assert(false, "Test failed after exhausting retries. Retry number {0}. Failure: {1}", i, ex);
                    }

                    m_testOutputHelper.WriteLine("Test iteration failed. Pausing and retrying");
                    // Take a break before retrying the test
                    Thread.Sleep(3000);
                    continue;
                }

                // Success
                break;
            }
        }
Beispiel #5
0
        private async void CompletionCallback(object context, bool timedOut)
        {
            if (timedOut)
            {
                Volatile.Write(ref m_timedout, true);

                // Attempt to dump the timed out process before killing it
                if (!m_processHandle.IsInvalid && !m_processHandle.IsClosed && m_workingDirectory != null)
                {
                    DumpFileDirectory = m_timeoutDumpDirectory;

                    Exception dumpCreationException;

                    try
                    {
                        Directory.CreateDirectory(DumpFileDirectory);
                    }
                    catch (Exception ex)
                    {
                        DumpCreationException = ex;
                    }

                    if (DumpCreationException == null &&
                        !ProcessDumper.TryDumpProcessAndChildren(
                            parentProcessId: m_processId,
                            dumpDirectory: DumpFileDirectory,
                            primaryDumpCreationException: out dumpCreationException))
                    {
                        DumpCreationException = dumpCreationException;
                    }
                }

                Kill(ExitCodes.Timeout);

                using (await m_syncSemaphore.AcquireAsync())
                {
                    if (m_processWaitHandle != null)
                    {
                        await m_processWaitHandle;
                        m_exited = true;
                    }
                }
            }
            else
            {
                m_exited = true;
            }

            using (var semaphoreReleaser = await m_syncSemaphore.AcquireAsync())
            {
                Contract.Assume(m_waiting, "CompletionCallback should only be triggered once.");
                StopWaiting(semaphoreReleaser);
            }

            try
            {
                await Task.Run(
                    async() =>
                {
                    // Before waiting on anything, we call the processExiting callback.
                    // This callback happens to be responsible for triggering or forcing
                    // cleanup of all processes in this job. We can't finish waiting on pipe EOF
                    // (error, output, report, and process-injector pipes) until all handles
                    // to the write-sides are closed.
                    Func <Task> processExiting = m_processExitingAsync;
                    if (processExiting != null)
                    {
                        await processExiting();
                    }

                    using (var semaphoreReleaser = await m_syncSemaphore.AcquireAsync())
                    {
                        // Error and output pipes: Finish reading and then expect EOF (see above).
                        await WaitUntilErrorAndOutputEof(false, semaphoreReleaser);

                        // Stop process injection service. This finishes reading the injector control pipe (for injection requests).
                        // Since we don't get to the 'end' of the pipe until all child-processes holding on to it exit, we must
                        // perform this wait after processExiting() above.
                        if (m_processInjector != null)
                        {
                            // Stop() discards all unhandled requests. That is only safe to do since we are assuming that all processes
                            // in the job have exited (so those requests aren't relevant anymore)
                            await m_processInjector.Stop();
                            m_hasDetoursFailures = m_processInjector.HasDetoursInjectionFailures;
                            m_processInjector.Dispose();
                            m_processInjector = null;
                        }
                    }

                    // Now, callback for additional cleanup (can safely wait on extra pipes, such as the SandboxedProcess report pipe,
                    // if processExiting causes process tree teardown; see above).
                    var processExited = m_processExited;
                    if (processExited != null)
                    {
                        await processExited();
                    }
                });
            }
            catch (Exception exception)
            {
                // Something above may fail and that has to be observed. Unfortunately, throwing a normal exception in a continuation
                // just means someone has to observe the continuation. So we tug on some bootstraps by killing the process here.
                // TODO: It'd be nice if we had a FailFast equivalent that went through AppDomain.UnhandledExceptionEvent for logging.
                ExceptionHandling.OnFatalException(exception, "FailFast in DetouredProcess completion callback");
            }
        }