Skip to content
Closed
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
83 changes: 83 additions & 0 deletions src/Build/BackEnd/Node/OutOfProcServerNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ public sealed class OutOfProcServerNode : INode, INodePacketFactory, INodePacket
private bool _cancelRequested = false;
private string _serverBusyMutexName = default!;

// Snapshot of process-global Console state captured once at server startup.
// Restored at the end of each request to prevent loggers (e.g. BaseConsoleLogger
// which sets ForegroundColor in SetColor() but only sometimes restores via ResetColor)
// or per-request ConsoleConfiguration application from leaking visible state into the
// next request or into the idle server's state.
private ConsoleColor _originalForegroundColor;
private ConsoleColor _originalBackgroundColor;
private bool _originalConsoleColorsCaptured;
private System.Text.Encoding _originalOutputEncoding = default!;
private bool _originalOutputEncodingCaptured;

public OutOfProcServerNode(BuildCallback buildFunction)
{
_buildFunction = buildFunction;
Expand All @@ -93,6 +104,31 @@ public OutOfProcServerNode(BuildCallback buildFunction)
/// <returns>The reason for shutting down.</returns>
public NodeEngineShutdownReason Run(out Exception? shutdownException)
{
// Capture process-global Console state once. Restoring per-request keeps reused
// server requests from observing each other's color/encoding mutations.
// Wrapped in try/catch because Console.ForegroundColor/BackgroundColor throw on
// some redirected/headless setups (e.g. CI without a TTY).
try
{
_originalForegroundColor = Console.ForegroundColor;
_originalBackgroundColor = Console.BackgroundColor;
_originalConsoleColorsCaptured = true;
}
catch
{
_originalConsoleColorsCaptured = false;
}

try
{
_originalOutputEncoding = Console.OutputEncoding;
_originalOutputEncodingCaptured = true;
}
catch
{
_originalOutputEncodingCaptured = false;
}

ServerNodeHandshake handshake = new(
CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture()));

Expand Down Expand Up @@ -363,6 +399,53 @@ private void HandleServerNodeBuildCommandAsync(ServerNodeBuildCommand command)
}

private void HandleServerNodeBuildCommand(ServerNodeBuildCommand command)
{
try
{
HandleServerNodeBuildCommandCore(command);
}
finally
{
RestoreConsoleStateAfterRequest();
}
}

/// <summary>
/// Restore process-global Console state to the snapshot captured at server startup.
/// Called from a finally block in <see cref="HandleServerNodeBuildCommand"/> so that
/// per-request mutations (loggers writing to Console.ForegroundColor, the per-request
/// application of ConsoleConfiguration.BackgroundColor, etc.) do not leak across
/// requests in a reused server. See investigation #9379 (LOG-2 / W1c).
/// </summary>
private void RestoreConsoleStateAfterRequest()
{
if (_originalConsoleColorsCaptured)
{
try
{
Console.ForegroundColor = _originalForegroundColor;
Console.BackgroundColor = _originalBackgroundColor;
}
catch
{
// Console color mutation can throw on redirected stdout in CI; safe to ignore.
}
}

if (_originalOutputEncodingCaptured)
{
try
{
Console.OutputEncoding = _originalOutputEncoding;
}
catch
{
// Some hosts disallow OutputEncoding mutation after Console initialization.
}
}
}

private void HandleServerNodeBuildCommandCore(ServerNodeBuildCommand command)
{
CommunicationsUtilities.Trace($"Building with MSBuild server with command line {command.CommandLine}");
using var serverBusyMutex = ServerNamedMutex.OpenOrCreateMutex(name: _serverBusyMutexName, createdNew: out var holdsMutex);
Expand Down
Loading