using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEditor; using UnityEditor.Compilation; using Debug = UnityEngine.Debug; namespace io.github.hatayama.uLoopMCP { internal static class SharedRoslynCompilerWorkerHost { private const int SharedCompilerWorkerMaxAttempts = 2; private const string SharedCompilerWorkerResultPrefix = "__ULOOP_RESULT__"; private const string SharedCompilerWorkerEndMarker = "__ULOOP_END__"; private const string SharedCompilerWorkerQuitCommand = "__QUIT__"; internal const string CompileRequestPathPrefix = "path-base64:"; private const string RoslynWorkerSourceFileName = "RoslynCompilerWorker.cs"; private const string RoslynWorkerAssemblyFileName = "RoslynCompilerWorker.dll"; private const string RoslynWorkerCompileResponseFileName = "RoslynCompilerWorker.rsp"; private const string RoslynWorkerProgramTemplateRelativePath = "Editor/Compilation/Templates/SharedRoslynCompilerWorkerProgram.cs.template"; private const string SharedCompilerWorkerResultPrefixToken = "{{SHARED_COMPILER_WORKER_RESULT_PREFIX}}"; private const string SharedCompilerWorkerEndMarkerToken = "{{SHARED_COMPILER_WORKER_END_MARKER}}"; private const string SharedCompilerWorkerQuitCommandToken = "{{SHARED_COMPILER_WORKER_QUIT_COMMAND}}"; private const string CompileRequestPathPrefixToken = "{{COMPILE_REQUEST_PATH_PREFIX}}"; private const int SharedCompilerWorkerResponseTimeoutMilliseconds = 30000; private const int WorkerAssemblyBuildTimeoutMilliseconds = 30000; internal const string DotnetMultilevelLookupEnvironmentVariableName = "DOTNET_MULTILEVEL_LOOKUP"; internal const string DotnetMultilevelLookupDisabledValue = "0"; private static readonly object SharedCompilerWorkerLock = new(); private static Action s_deleteWorkerDirectory = path => Directory.Delete(path, true); private static Action s_sendCompileRequest = SendCompileRequestCore; private static Func s_compileWorkerAssemblyForTests; private static Process _sharedCompilerWorkerProcess; private static string _workerDirectoryPath; private sealed class WorkerAttemptResult { public CompilerMessage[] Messages { get; } public bool ShouldRetry { get; } public string FailureReason { get; } public object FailureContext { get; } private WorkerAttemptResult( CompilerMessage[] messages, bool shouldRetry, string failureReason, object failureContext) { Messages = messages; ShouldRetry = shouldRetry; FailureReason = failureReason; FailureContext = failureContext; } public bool Succeeded => Messages != null; public static WorkerAttemptResult Successful(CompilerMessage[] messages) { return new WorkerAttemptResult(messages, false, null, null); } public static WorkerAttemptResult RetryableFailure(string failureReason, object failureContext) { return new WorkerAttemptResult(null, true, failureReason, failureContext); } } private sealed class WorkerStartupResult { public bool IsReady { get; } public string FailureReason { get; } public object FailureContext { get; } private WorkerStartupResult(bool isReady, string failureReason, object failureContext) { IsReady = isReady; FailureReason = failureReason; FailureContext = failureContext; } public static WorkerStartupResult Ready() { return new WorkerStartupResult(true, null, null); } public static WorkerStartupResult Failure(string failureReason, object failureContext) { return new WorkerStartupResult(false, failureReason, failureContext); } } private sealed class WorkerAssemblyBuildResult { public bool StartedSuccessfully { get; } public CompilerMessage[] Messages { get; } public string FailureReason { get; } public object FailureContext { get; } private WorkerAssemblyBuildResult( bool startedSuccessfully, CompilerMessage[] messages, string failureReason, object failureContext) { StartedSuccessfully = startedSuccessfully; Messages = messages; FailureReason = failureReason; FailureContext = failureContext; } public static WorkerAssemblyBuildResult Started(CompilerMessage[] messages) { return new WorkerAssemblyBuildResult(true, messages, null, null); } public static WorkerAssemblyBuildResult StartFailure(string failureReason, object failureContext) { return new WorkerAssemblyBuildResult(false, null, failureReason, failureContext); } } private sealed class WorkerPaths { public string DirectoryPath { get; } public string SourcePath { get; } public string AssemblyPath { get; } public string CompileResponseFilePath { get; } public WorkerPaths( string directoryPath, string sourcePath, string assemblyPath, string compileResponseFilePath) { DirectoryPath = directoryPath; SourcePath = sourcePath; AssemblyPath = assemblyPath; CompileResponseFilePath = compileResponseFilePath; } } [InitializeOnLoadMethod] private static void RegisterLifecycle() { AssemblyReloadEvents.beforeAssemblyReload -= ShutdownForReload; AssemblyReloadEvents.beforeAssemblyReload += ShutdownForReload; EditorApplication.quitting -= ShutdownForQuit; EditorApplication.quitting += ShutdownForQuit; } public static CompilerMessage[] TryCompile( string requestFilePath, ExternalCompilerPaths externalCompilerPaths, CancellationToken ct, Action markBuildStarted, Action markBuildFinished, Action incrementBuildCount) { lock (SharedCompilerWorkerLock) { return TryCompileWithRetries( requestFilePath, externalCompilerPaths, ct, markBuildStarted, markBuildFinished, incrementBuildCount); } } private static CompilerMessage[] TryCompileWithRetries( string requestFilePath, ExternalCompilerPaths externalCompilerPaths, CancellationToken ct, Action markBuildStarted, Action markBuildFinished, Action incrementBuildCount) { for (int attempt = 1; attempt <= SharedCompilerWorkerMaxAttempts; attempt++) { WorkerAttemptResult attemptResult = TryCompileOnce( requestFilePath, externalCompilerPaths, ct, markBuildStarted, markBuildFinished, incrementBuildCount); if (attemptResult.Succeeded) { return attemptResult.Messages; } ShutdownWorkerProcessLocked(); if (attemptResult.ShouldRetry && attempt < SharedCompilerWorkerMaxAttempts) { continue; } DynamicCompilationHealthMonitor.ReportSharedWorkerFailure( attemptResult.FailureReason, AppendAttempt(attemptResult.FailureContext, attempt)); return null; } return null; } private static WorkerAttemptResult TryCompileOnce( string requestFilePath, ExternalCompilerPaths externalCompilerPaths, CancellationToken ct, Action markBuildStarted, Action markBuildFinished, Action incrementBuildCount) { WorkerStartupResult startupResult = EnsureWorkerReady(externalCompilerPaths); if (!startupResult.IsReady) { return WorkerAttemptResult.RetryableFailure( startupResult.FailureReason, startupResult.FailureContext); } return InvokeWorkerOnce( requestFilePath, ct, markBuildStarted, markBuildFinished, incrementBuildCount); } private static WorkerStartupResult EnsureWorkerReady(ExternalCompilerPaths externalCompilerPaths) { if (HasLiveWorkerProcess()) { return WorkerStartupResult.Ready(); } WorkerPaths workerPaths = CreateWorkerPaths(); SynchronizeWorkerSource(workerPaths); WorkerStartupResult workerAssemblyResult = EnsureWorkerAssemblyBuilt( externalCompilerPaths, workerPaths); if (!workerAssemblyResult.IsReady) { return workerAssemblyResult; } return StartWorkerProcess(externalCompilerPaths, workerPaths); } private static WorkerStartupResult EnsureWorkerAssemblyBuilt( ExternalCompilerPaths externalCompilerPaths, WorkerPaths workerPaths) { if (File.Exists(workerPaths.AssemblyPath)) { return WorkerStartupResult.Ready(); } WorkerAssemblyBuildResult buildResult = CompileWorkerAssembly( externalCompilerPaths, workerPaths.SourcePath, workerPaths.AssemblyPath, workerPaths.CompileResponseFilePath); if (!buildResult.StartedSuccessfully) { return WorkerStartupResult.Failure( buildResult.FailureReason, buildResult.FailureContext); } if (!HasErrors(buildResult.Messages)) { return WorkerStartupResult.Ready(); } DeleteWorkerAssemblyIfPresent(workerPaths.AssemblyPath); return WorkerStartupResult.Failure( "worker_build_failed", new { first_error = FindFirstErrorMessage(buildResult.Messages), worker_source_path = workerPaths.SourcePath }); } private static WorkerStartupResult StartWorkerProcess( ExternalCompilerPaths externalCompilerPaths, WorkerPaths workerPaths) { ProcessStartInfo startInfo = CreateWorkerStartInfo(externalCompilerPaths, workerPaths); _sharedCompilerWorkerProcess = ProcessStartHelper.TryStart(startInfo); if (_sharedCompilerWorkerProcess == null) { return WorkerStartupResult.Failure( "worker_start_failed", new { dotnet_host_path = externalCompilerPaths.DotnetHostPath, worker_assembly_path = workerPaths.AssemblyPath }); } return WorkerStartupResult.Ready(); } private static WorkerAttemptResult InvokeWorkerOnce( string requestFilePath, CancellationToken ct, Action markBuildStarted, Action markBuildFinished, Action incrementBuildCount) { if (!HasLiveWorkerProcess()) { return WorkerAttemptResult.RetryableFailure( "worker_process_missing", new { request_file_path = requestFilePath }); } ct.ThrowIfCancellationRequested(); incrementBuildCount(); markBuildStarted(); try { SendCompileRequest(requestFilePath); return ReadWorkerResponse(requestFilePath, ct); } catch (IOException ex) { return CreateRetryableWorkerCommunicationFailure(requestFilePath, ex); } catch (ObjectDisposedException ex) { return CreateRetryableWorkerCommunicationFailure(requestFilePath, ex); } catch (OperationCanceledException) { ShutdownWorkerProcessLocked(); throw; } finally { markBuildFinished(); } } private static void SendCompileRequest(string requestFilePath) { string absoluteRequestFilePath = Path.GetFullPath(requestFilePath); s_sendCompileRequest(_sharedCompilerWorkerProcess, absoluteRequestFilePath); } private static void SendCompileRequestCore(Process workerProcess, string requestFilePath) { string requestCommand = CreateCompileRequestCommand(requestFilePath); workerProcess.StandardInput.WriteLine(requestCommand); workerProcess.StandardInput.Flush(); } private static string CreateCompileRequestCommand(string requestFilePath) { Debug.Assert(!string.IsNullOrEmpty(requestFilePath), "requestFilePath must not be empty"); string fullRequestFilePath = Path.GetFullPath(requestFilePath); byte[] requestPathBytes = Encoding.UTF8.GetBytes(fullRequestFilePath); return CompileRequestPathPrefix + Convert.ToBase64String(requestPathBytes); } private static WorkerAttemptResult ReadWorkerResponse( string requestFilePath, CancellationToken ct) { string responseHeader = ReadProtocolLine( _sharedCompilerWorkerProcess.StandardOutput, ct); if (string.IsNullOrEmpty(responseHeader)) { return WorkerAttemptResult.RetryableFailure( "worker_empty_header", new { request_file_path = requestFilePath }); } if (!TryParseResponseHeader(responseHeader, out int exitCode)) { return WorkerAttemptResult.RetryableFailure( GetResponseHeaderFailureReason(responseHeader), new { header = responseHeader }); } List outputLines = ReadDiagnosticLines(ct); if (outputLines == null) { return WorkerAttemptResult.RetryableFailure( "worker_missing_end_marker", new { request_file_path = requestFilePath }); } string combinedOutput = string.Join("\n", outputLines); CompilerMessage[] compilerMessages = ExternalCompilerMessageParser.Parse(combinedOutput, string.Empty, exitCode); return WorkerAttemptResult.Successful(compilerMessages); } private static bool TryParseResponseHeader(string responseHeader, out int exitCode) { exitCode = 0; if (!responseHeader.StartsWith(SharedCompilerWorkerResultPrefix, StringComparison.Ordinal)) { return false; } string statusText = responseHeader.Substring(SharedCompilerWorkerResultPrefix.Length).Trim(); return int.TryParse(statusText, out exitCode); } private static string GetResponseHeaderFailureReason(string responseHeader) { if (!responseHeader.StartsWith(SharedCompilerWorkerResultPrefix, StringComparison.Ordinal)) { return "worker_invalid_header"; } return "worker_invalid_exit_code"; } private static List ReadDiagnosticLines(CancellationToken ct) { List outputLines = new List(); while (true) { string outputLine = ReadProtocolLine(_sharedCompilerWorkerProcess.StandardOutput, ct); if (outputLine == null) { return null; } if (outputLine == SharedCompilerWorkerEndMarker) { return outputLines; } outputLines.Add(outputLine); } } private static WorkerPaths CreateWorkerPaths() { string workerDirectoryPath = GetWorkerDirectoryPath(); Directory.CreateDirectory(workerDirectoryPath); _workerDirectoryPath = workerDirectoryPath; return new WorkerPaths( workerDirectoryPath, Path.Combine(workerDirectoryPath, RoslynWorkerSourceFileName), Path.Combine(workerDirectoryPath, RoslynWorkerAssemblyFileName), Path.Combine(workerDirectoryPath, RoslynWorkerCompileResponseFileName)); } private static string GetWorkerDirectoryPath() { return Path.Combine( Path.GetTempPath(), "uLoopMCPCompilation", $"RoslynWorker-{Process.GetCurrentProcess().Id}"); } private static void SynchronizeWorkerSource(WorkerPaths workerPaths) { string workerSource = CreateProgramSource(); if (File.Exists(workerPaths.SourcePath) && File.ReadAllText(workerPaths.SourcePath) == workerSource) { return; } File.WriteAllText(workerPaths.SourcePath, workerSource); if (File.Exists(workerPaths.AssemblyPath)) { File.Delete(workerPaths.AssemblyPath); } } private static ProcessStartInfo CreateWorkerStartInfo( ExternalCompilerPaths externalCompilerPaths, WorkerPaths workerPaths) { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = externalCompilerPaths.DotnetHostPath, Arguments = "exec" + " --runtimeconfig " + QuoteCommandLineArgument(externalCompilerPaths.CompilerRuntimeConfigPath) + " --depsfile " + QuoteCommandLineArgument(externalCompilerPaths.CompilerDepsFilePath) + " " + QuoteCommandLineArgument(workerPaths.AssemblyPath), WorkingDirectory = workerPaths.DirectoryPath, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = false, CreateNoWindow = true }; ConfigureWorkerDotnetRuntimeEnvironment(startInfo); return startInfo; } internal static void ConfigureWorkerDotnetRuntimeEnvironment(ProcessStartInfo startInfo) { Debug.Assert(startInfo != null, "startInfo must not be null"); // Why: global probing can select a system .NET 6 runtime while Unity 6000.4 worker // references come from the bundled .NET 8 runtime, which breaks assembly binding. startInfo.EnvironmentVariables[DotnetMultilevelLookupEnvironmentVariableName] = DotnetMultilevelLookupDisabledValue; } private static WorkerAssemblyBuildResult CompileWorkerAssembly( ExternalCompilerPaths externalCompilerPaths, string workerSourcePath, string workerAssemblyPath, string workerCompileResponseFilePath) { if (s_compileWorkerAssemblyForTests != null) { return WorkerAssemblyBuildResult.Started( s_compileWorkerAssemblyForTests( externalCompilerPaths, workerSourcePath, workerAssemblyPath, workerCompileResponseFilePath)); } WriteWorkerCompilerResponseFile( workerCompileResponseFilePath, workerSourcePath, workerAssemblyPath, BuildWorkerReferenceSet(externalCompilerPaths)); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = externalCompilerPaths.DotnetHostPath, Arguments = $"{QuoteCommandLineArgument(externalCompilerPaths.CompilerDllPath)} @{QuoteCommandLineArgument(workerCompileResponseFilePath)}", WorkingDirectory = Path.GetDirectoryName(workerSourcePath), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; ConfigureWorkerDotnetRuntimeEnvironment(startInfo); using Process process = ProcessStartHelper.TryStart(startInfo); if (process == null) { return WorkerAssemblyBuildResult.StartFailure( "worker_compiler_start_failed", new { dotnet_host_path = externalCompilerPaths.DotnetHostPath, compiler_dll_path = externalCompilerPaths.CompilerDllPath }); } Task stdoutTask = process.StandardOutput.ReadToEndAsync(); Task stderrTask = process.StandardError.ReadToEndAsync(); if (!process.WaitForExit(WorkerAssemblyBuildTimeoutMilliseconds)) { if (!process.HasExited) { process.Kill(); process.WaitForExit(500); } Task.WaitAll(stdoutTask, stderrTask); return WorkerAssemblyBuildResult.StartFailure( "worker_compiler_timeout", new { timeout_ms = WorkerAssemblyBuildTimeoutMilliseconds, dotnet_host_path = externalCompilerPaths.DotnetHostPath, compiler_dll_path = externalCompilerPaths.CompilerDllPath }); } Task.WaitAll(stdoutTask, stderrTask); CompilerMessage[] compilerMessages = ExternalCompilerMessageParser.Parse( stdoutTask.GetAwaiter().GetResult(), stderrTask.GetAwaiter().GetResult(), process.ExitCode); return WorkerAssemblyBuildResult.Started(compilerMessages); } private static List BuildWorkerReferenceSet(ExternalCompilerPaths externalCompilerPaths) { string sharedRuntimeDirectoryPath = externalCompilerPaths.NetCoreRuntimeSharedDirectoryPath; List references = new List { Path.Combine(sharedRuntimeDirectoryPath, "System.Private.CoreLib.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Runtime.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Console.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Collections.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.IO.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Threading.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Threading.Tasks.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Text.Encoding.Extensions.dll"), Path.Combine(sharedRuntimeDirectoryPath, "System.Runtime.Extensions.dll"), Path.Combine(sharedRuntimeDirectoryPath, "netstandard.dll"), externalCompilerPaths.CodeAnalysisDllPath, externalCompilerPaths.CodeAnalysisCSharpDllPath }; AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Collections.Immutable.dll")); AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Reflection.Metadata.dll")); AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Runtime.CompilerServices.Unsafe.dll")); AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Memory.dll")); AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Buffers.dll")); AddIfExists(references, Path.Combine(sharedRuntimeDirectoryPath, "System.Threading.Tasks.Extensions.dll")); return references; } private static void WriteWorkerCompilerResponseFile( string responseFilePath, string sourcePath, string dllPath, IReadOnlyCollection references) { List lines = new List { "-nologo", "-nostdlib+", "-target:exe", "-optimize+", "-debug-", QuoteResponseFileArgument("-out:", dllPath) }; foreach (string reference in references) { lines.Add(QuoteResponseFileArgument("-r:", reference)); } lines.Add(QuoteResponseFilePath(sourcePath)); File.WriteAllLines(responseFilePath, lines); } private static string ReadProtocolLine(StreamReader reader, CancellationToken ct) { Debug.Assert(reader != null, "reader must not be null"); Task readTask = Task.Run(() => reader.ReadLine()); Task timeoutTask = Task.Delay(SharedCompilerWorkerResponseTimeoutMilliseconds, ct); Task completedTask = Task.WhenAny(readTask, timeoutTask).GetAwaiter().GetResult(); if (!ReferenceEquals(completedTask, readTask)) { ct.ThrowIfCancellationRequested(); return null; } return readTask.GetAwaiter().GetResult(); } private static bool HasLiveWorkerProcess() { return _sharedCompilerWorkerProcess != null && !_sharedCompilerWorkerProcess.HasExited; } private static bool HasErrors(IReadOnlyCollection messages) { foreach (CompilerMessage message in messages) { if (message.type == CompilerMessageType.Error) { return true; } } return false; } private static string FindFirstErrorMessage(IReadOnlyCollection messages) { foreach (CompilerMessage message in messages) { if (message.type == CompilerMessageType.Error) { return message.message; } } return string.Empty; } private static void DeleteWorkerAssemblyIfPresent(string assemblyPath) { if (File.Exists(assemblyPath)) { File.Delete(assemblyPath); } } private static void ShutdownForReload() { Shutdown(); } private static void ShutdownForQuit() { Shutdown(); } internal static void ShutdownForServerReset() { Shutdown(); } internal static void ShutdownForTests() { Shutdown(); } internal static Action SwapWorkerDirectoryDeleterForTests(Action deleter) { Debug.Assert(deleter != null, "deleter must not be null"); Action previous = s_deleteWorkerDirectory; s_deleteWorkerDirectory = deleter; return previous; } internal static Action SwapCompileRequestSenderForTests(Action sender) { Debug.Assert(sender != null, "sender must not be null"); Action previous = s_sendCompileRequest; s_sendCompileRequest = sender; return previous; } internal static Func SwapWorkerAssemblyCompilerForTests( Func compiler) { Func previous = s_compileWorkerAssemblyForTests; s_compileWorkerAssemblyForTests = compiler; return previous; } private static void Shutdown() { lock (SharedCompilerWorkerLock) { ShutdownWorkerProcessLocked(); CleanupWorkerDirectoryLocked(); } } private static void ShutdownWorkerProcessLocked() { Process workerProcess = _sharedCompilerWorkerProcess; _sharedCompilerWorkerProcess = null; if (workerProcess == null) { return; } try { if (!workerProcess.HasExited) { workerProcess.StandardInput.WriteLine(SharedCompilerWorkerQuitCommand); workerProcess.StandardInput.Flush(); workerProcess.WaitForExit(500); } if (!workerProcess.HasExited) { workerProcess.Kill(); workerProcess.WaitForExit(500); } } catch (IOException ex) { LogWorkerShutdownFailure(ex); } catch (ObjectDisposedException ex) { LogWorkerShutdownFailure(ex); } catch (InvalidOperationException ex) { LogWorkerShutdownFailure(ex); } finally { workerProcess.Dispose(); } } private static void CleanupWorkerDirectoryLocked() { string workerDirectoryPath = _workerDirectoryPath ?? GetWorkerDirectoryPath(); if (!Directory.Exists(workerDirectoryPath)) { _workerDirectoryPath = null; return; } TryDeleteWorkerDirectory(workerDirectoryPath); _workerDirectoryPath = null; } private static void TryDeleteWorkerDirectory(string workerDirectoryPath) { try { s_deleteWorkerDirectory(workerDirectoryPath); } catch (IOException ex) { LogWorkerDirectoryCleanupFailure(workerDirectoryPath, ex); } catch (UnauthorizedAccessException ex) { LogWorkerDirectoryCleanupFailure(workerDirectoryPath, ex); } } private static void LogWorkerDirectoryCleanupFailure(string workerDirectoryPath, Exception ex) { VibeLogger.LogWarning( "dynamic_code_shared_worker_cleanup_failed", "execute-dynamic-code shared Roslyn worker directory cleanup failed during shutdown", new { worker_directory_path = workerDirectoryPath, exception_type = ex.GetType().FullName, exception_message = ex.Message }, humanNote: "Shared Roslyn worker cleanup could not remove its temporary directory during shutdown.", aiTodo: "Investigate file locks or permission issues if temporary worker directories continue to accumulate."); Debug.LogWarning($"[{McpConstants.PROJECT_NAME}] Failed to delete shared Roslyn worker directory '{workerDirectoryPath}': {ex.Message}"); } private static WorkerAttemptResult CreateRetryableWorkerCommunicationFailure( string requestFilePath, Exception ex) { return WorkerAttemptResult.RetryableFailure( "worker_communication_failed", new { request_file_path = requestFilePath, exception_type = ex.GetType().FullName, exception_message = ex.Message }); } private static void LogWorkerShutdownFailure(Exception ex) { VibeLogger.LogWarning( "dynamic_code_shared_worker_shutdown_failed", "execute-dynamic-code shared Roslyn worker shutdown observed a communication failure", new { exception_type = ex.GetType().FullName, exception_message = ex.Message }, humanNote: "Shared Roslyn worker shutdown saw a broken communication channel while cleaning up a crashed worker.", aiTodo: "Investigate repeated worker shutdown communication failures if shared compilation stops recovering cleanly."); } private static object AppendAttempt(object failureContext, int attempt) { return new { attempt, details = failureContext }; } private static string QuoteResponseFileArgument(string prefix, string value) { return $"{prefix}{QuoteResponseFilePath(value)}"; } private static string QuoteResponseFilePath(string path) { return $"\"{path}\""; } private static string QuoteCommandLineArgument(string value) { return $"\"{value}\""; } private static void AddIfExists( List destination, string referencePath) { if (string.IsNullOrEmpty(referencePath) || !File.Exists(referencePath)) { return; } destination.Add(referencePath); } private static string CreateProgramSource() { string templatePath = GetWorkerProgramTemplatePath(); string templateSource = File.ReadAllText(templatePath, Encoding.UTF8); return templateSource .Replace(SharedCompilerWorkerResultPrefixToken, SharedCompilerWorkerResultPrefix) .Replace(SharedCompilerWorkerEndMarkerToken, SharedCompilerWorkerEndMarker) .Replace(SharedCompilerWorkerQuitCommandToken, SharedCompilerWorkerQuitCommand) .Replace(CompileRequestPathPrefixToken, CompileRequestPathPrefix); } private static string GetWorkerProgramTemplatePath() { return Path.Combine(McpConstants.PackageResolvedPath, RoslynWorkerProgramTemplateRelativePath); } } }