diff --git a/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs
index 0eef762da5..e8355b815e 100644
--- a/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs
+++ b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs
@@ -44,6 +44,41 @@ public void Shutdown_WhenWorkerWasNeverStarted_ShouldRemainIdempotent()
Assert.DoesNotThrow(() => session.Shutdown(unusedWorkerDirectoryPath));
}
+ ///
+ /// Verifies replacing the cached worker releases the previously owned process handle.
+ ///
+ [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();
+ }
+ }
+
///
/// Verifies a pending compiler stream does not keep the bounded drain waiting.
///
diff --git a/Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/DynamicCompilation/SharedRoslynCompilerWorkerSession.cs b/Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/DynamicCompilation/SharedRoslynCompilerWorkerSession.cs
index 0d9e4e6966..88a28b942e 100644
--- a/Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/DynamicCompilation/SharedRoslynCompilerWorkerSession.cs
+++ b/Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/DynamicCompilation/SharedRoslynCompilerWorkerSession.cs
@@ -16,6 +16,7 @@ internal sealed class SharedRoslynCompilerWorkerSession
{
private readonly object _syncRoot = new();
private Action _deleteWorkerDirectory = path => Directory.Delete(path, true);
+ private Func _startProcess = ProcessStartHelper.TryStart;
private Action _sendCompileRequest = SendCompileRequestCore;
private Func
_compileWorkerAssemblyForTests;
@@ -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;
}
@@ -149,6 +156,16 @@ internal Action SwapWorkerDirectoryDeleterForTests(Action delete
return previous;
}
+ internal Func SwapProcessStarterForTests(
+ Func starter)
+ {
+ Debug.Assert(starter != null, "starter must not be null");
+
+ Func previous = _startProcess;
+ _startProcess = starter;
+ return previous;
+ }
+
internal Action SwapCompileRequestSenderForTests(Action sender)
{
Debug.Assert(sender != null, "sender must not be null");