Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,41 @@ public void Shutdown_WhenWorkerWasNeverStarted_ShouldRemainIdempotent()
Assert.DoesNotThrow(() => session.Shutdown(unusedWorkerDirectoryPath));
}

/// <summary>
/// Verifies replacing the cached worker releases the previously owned process handle.
/// </summary>
[Test]
public void StartProcessLocked_WhenReplacingCachedProcess_ShouldDisposePreviousHandle()
{
SharedRoslynCompilerWorkerSession session = new();
Process previousProcess = new();
bool previousProcessDisposed = false;
int startAttempt = 0;
previousProcess.Disposed += (sender, args) => previousProcessDisposed = true;
session.SwapProcessStarterForTests(startInfo =>
{
startAttempt++;
return startAttempt == 1 ? previousProcess : null;
});
ProcessStartInfo ignoredStartInfo = new();

try
{
bool firstStartSucceeded = session.ExecuteLocked(
() => session.StartProcessLocked(ignoredStartInfo));
bool secondStartSucceeded = session.ExecuteLocked(
() => session.StartProcessLocked(ignoredStartInfo));

Assert.That(firstStartSucceeded, Is.True);
Assert.That(secondStartSucceeded, Is.False);
Assert.That(previousProcessDisposed, Is.True);
}
finally
{
previousProcess.Dispose();
}
}

/// <summary>
/// Verifies a pending compiler stream does not keep the bounded drain waiting.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal sealed class SharedRoslynCompilerWorkerSession
{
private readonly object _syncRoot = new();
private Action<string> _deleteWorkerDirectory = path => Directory.Delete(path, true);
private Func<ProcessStartInfo, Process> _startProcess = ProcessStartHelper.TryStart;
private Action<Process, string> _sendCompileRequest = SendCompileRequestCore;
private Func<ExternalCompilerPaths, string, string, string, CompilerMessage[]>
_compileWorkerAssemblyForTests;
Expand All @@ -41,7 +42,13 @@ internal bool HasLiveProcessLocked()
internal bool StartProcessLocked(ProcessStartInfo startInfo)
{
AssertLockHeld();
_workerProcess = ProcessStartHelper.TryStart(startInfo);
// EnsureWorkerReady reaches this method only after the same lock observed no live process,
// so replacement releases the stale handle without retrying graceful shutdown.
Process previousProcess = _workerProcess;
_workerProcess = null;
previousProcess?.Dispose();

_workerProcess = _startProcess(startInfo);
return _workerProcess != null;
}

Expand Down Expand Up @@ -149,6 +156,16 @@ internal Action<string> SwapWorkerDirectoryDeleterForTests(Action<string> delete
return previous;
}

internal Func<ProcessStartInfo, Process> SwapProcessStarterForTests(
Func<ProcessStartInfo, Process> starter)
{
Debug.Assert(starter != null, "starter must not be null");

Func<ProcessStartInfo, Process> previous = _startProcess;
_startProcess = starter;
return previous;
}

internal Action<Process, string> SwapCompileRequestSenderForTests(Action<Process, string> sender)
{
Debug.Assert(sender != null, "sender must not be null");
Expand Down
Loading