// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management;
namespace ProcessWatchdog
{
///
/// Keeps track of a process and all its descendants.
///
internal class ProcessTracker : IDisposable
{
private readonly Process _parentProcess;
private List _trackedProcesses;
private readonly ProcDump _procDump;
///
/// Initializes a new instance of the class from the
/// specified process id.
///
///
/// The process whose descendants are to be tracked.
///
///
/// Object responsible for producing memory dumps of any tracked processes that
/// fail or are terminated.
///
internal ProcessTracker(Process parentProcess, ProcDump procDump)
{
_parentProcess = parentProcess;
_trackedProcesses = new List();
_procDump = procDump;
TrackProcess(parentProcess);
}
internal bool AllFinished => !_trackedProcesses.Any();
internal void Update()
{
// Clear out any processes which have ended.
_trackedProcesses = _trackedProcesses.Where(tp => !tp.HasExited).ToList();
// CAUTION: This code is subject to a race condition where between one
// call to update and the next, all the processes in the list ended, but new
// processes (which we are not yet tracking) were created. In that case,
// _trackedProcesses would now be empty, and the ProcessWatchdog would exit,
// even though there are still processes we care about.
//
// This should not happen for the scenarios we care about, since the parent
// process should outlive all its descendants.
if (_trackedProcesses.Any())
{
// Add any new descendants of the remaining processes (that is, any
// descendants that we're not already tracking).
UniqueProcess[] existingProcesses = _trackedProcesses.Select(tp => tp.Process).ToArray();
foreach (Process descendant in GetDescendants(_parentProcess.Id))
{
UniqueProcess uniqueDescendant;
if (UniqueProcess.TryCreate(descendant, out uniqueDescendant))
{
if (!existingProcesses.Contains(uniqueDescendant))
{
TrackProcess(descendant);
}
}
}
}
}
internal void TerminateAll()
{
foreach (TrackedProcess trackedProcess in _trackedProcesses)
{
// Launch another procdump process, distinct from the one that has been
// monitoring the tracked process. This procdump process will take an
// immediate dump of the tracked process.
Process immediateDumpProcess = _procDump.DumpProcessNow(
trackedProcess.Process.Id,
trackedProcess.Description);
immediateDumpProcess.WaitForExit();
// Terminate the procdump process that has been monitoring the target process
// Since this procdump is acting as a debugger, terminating it will
// terminate the target process as well.
trackedProcess.ProcDumpProcess.Kill();
}
}
private void TrackProcess(Process process)
{
string description = MakeProcessDescription(process);
Process procDumpProcess = _procDump.MonitorProcess(process.Id, description);
TrackedProcess trackedProcess;
if (TrackedProcess.TryCreate(process, procDumpProcess, description, out trackedProcess))
{
_trackedProcesses.Add(trackedProcess);
}
}
private static string MakeProcessDescription(Process process)
{
return $"{process.ProcessName}-{process.Id}";
}
private IList GetDescendants(int processId)
{
var descendants = new List();
// Don't include procdump itself in the descendants, because
// we don't want to kill procdump.
string query = string.Format(
CultureInfo.InvariantCulture,
"SELECT * FROM Win32_Process WHERE ParentProcessId = {0} AND Name <> \"procdump.exe\"",
processId);
var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject process in searcher.Get())
{
object descendantIdProperty = process["ProcessId"];
int descendantId = Convert.ToInt32(descendantIdProperty);
try
{
Process descendant = Process.GetProcessById(descendantId);
descendants.Add(descendant);
// Recurse to find descendants of descendants.
descendants.AddRange(GetDescendants(descendantId));
}
catch (ArgumentException)
{
// Don't worry if the process stopped running between the time we got
// its id and the time we tried to get a Process object from the id.
// Just don't add it to the list.
}
}
return descendants;
}
#region IDisposable Support
private bool _isDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
foreach (TrackedProcess trackedProcess in _trackedProcesses)
{
// Killing the procdump process will also kill the tracked
// process to which it had attached itself as a debugger.
trackedProcess.ProcDumpProcess.Kill();
trackedProcess.Process.Process.Dispose();
trackedProcess.ProcDumpProcess.Process.Dispose();
}
_trackedProcesses = null;
}
_isDisposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
///
/// Information about a single process being tracked by a .
///
private class TrackedProcess
{
internal static bool TryCreate(
Process process,
Process procDumpProcess,
string description,
out TrackedProcess trackedProcess)
{
bool result = false;
trackedProcess = null;
UniqueProcess uniqueProcess;
UniqueProcess uniqueProcDumpProcess;
if (UniqueProcess.TryCreate(process, out uniqueProcess)
&& UniqueProcess.TryCreate(procDumpProcess, out uniqueProcDumpProcess))
{
trackedProcess = new TrackedProcess(uniqueProcess, uniqueProcDumpProcess, description);
result = true;
}
return result;
}
///
/// Initializes a new instance of the class from
/// the specified process information.
///
///
/// The process being tracked.
///
///
/// The procdump process attached to , and responsible
/// for producing a memory dump if that process should fail.
///
///
/// A string that describes the process, of the form "processName-processId".
///
private TrackedProcess(UniqueProcess process, UniqueProcess procDumpProcess, string description)
{
Process = process;
ProcDumpProcess = procDumpProcess;
Description = description;
}
///
/// Gets a value indicating whether the tracked process has exited.
///
internal bool HasExited => Process.HasExited;
///
/// Gets the process being tracked.
///
internal UniqueProcess Process { get; }
///
/// Gets the procdump process attached to , and responsible
/// for producing a memory dump if that process should fail..
///
internal UniqueProcess ProcDumpProcess { get; }
///
/// Gets a string that describes the process, of the form "processName-processId".
///
internal string Description { get; }
}
}
}