Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/DiffEngine.Tests/DiffRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,53 @@ public async Task LaunchAndKill()
await Assert.That(ProcessCleanup.IsRunning(command)).IsFalse();
}

/// <summary>
/// A tool launched after the process list was filled has to be visible without anything
/// asking for a refresh first.
/// <para>
/// The list was filled once, by the static constructor, and nothing in the library ever
/// refreshed it. So within one process a tool launched after first use was invisible for the
/// rest of that process: Kill logged "No matching commands" and left it open, a relaunch
/// never saw the running instance and opened a second window while spending another
/// MaxInstance slot, and an AutoRefresh tool was relaunched rather than reused. Every test
/// here polls through WaitForRunning, which calls Refresh itself, so none of them could see
/// it - and FakeDiffTool exits on its own after five seconds, so even a kill that found
/// nothing looked like a kill that worked.
/// </para>
/// </summary>
[Test]
public async Task AToolLaunchedAfterTheListWasFilledIsVisible()
{
await WaitForRunning(false);
// The stale snapshot: taken while the tool is not running
ProcessCleanup.Refresh();
await Assert.That(ProcessCleanup.IsRunning(command)).IsFalse();

var result = await DiffRunner.LaunchAsync(file1, file2);
await Assert.That(result).IsEqualTo(LaunchResult.StartedNewInstance);

// Nothing calls Refresh from here. Bounded well inside FakeDiffTool's five second life,
// so a pass means the query saw it rather than that the process outlived the poll
await Assert.That(await PollCached()).IsTrue();

DiffRunner.Kill(file1, file2);
await WaitForRunning(false);
}

async Task<bool> PollCached()
{
for (var attempt = 0; attempt < 12; attempt++)
{
if (ProcessCleanup.IsRunning(command))
{
return true;
}

await Task.Delay(250);
}

return false;
}
[Test]
public async Task LaunchAndKillAsync()
{
Expand Down
18 changes: 18 additions & 0 deletions src/DiffEngine/Process/ProcessCleanup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ static ProcessCleanup()
Refresh();
}

/// <summary>
/// The processes as of the last <see cref="Refresh"/>. A snapshot, so callers that need to
/// know what is running now go through <see cref="IsRunning"/> or <see cref="Kill"/>, both of
/// which take their own.
/// </summary>
public static IReadOnlyCollection<ProcessCommand> Commands => commands;

[MemberNotNull(nameof(commands))]
Expand Down Expand Up @@ -60,6 +65,14 @@ public static void Kill(string command)
command = TrimCommand(command);
}

// The list was filled once by the static constructor and nothing in the library refreshed
// it, so this matched against whatever was running the first time anything touched
// DiffEngine. In one process a Launch followed by a Kill for the same pair logged "No
// matching commands" and left the tool open. It also keeps the PID as fresh as this can
// make it: a process that has since exited may have had its id reused, and terminating
// from a stale snapshot kills whatever holds it now
Refresh();

var matchingCommands = Commands
.Where(_ => _.Command == command).ToList();
Logging.Write($"Kill: {command}. Matching count: {matchingCommands.Count}");
Expand Down Expand Up @@ -91,6 +104,11 @@ public static bool TryGetProcessInfo(string command, out ProcessCommand process)
command = TrimCommand(command);
}

// As for Kill: the question is what is running now. Against the startup snapshot a tool
// launched later was never seen again, so an AutoRefresh tool opened a second window
// instead of being reused and every relaunch spent another MaxInstance slot
Refresh();

process = commands.FirstOrDefault(_ => _.Command == command);
return !process.Equals(default(ProcessCommand));
}
Expand Down
Loading