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); }
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}'"); }
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; } }
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"); } }