Repository: dotnet/runtime
File: src/coreclr/tools/r2rtest/ProcessRunner.cs (C#)

Code before:
    {
        TextWriter logWriter = _logWriter;

        if (logWriter != null)
        {
            lock (logWriter)
            {
                if (_logWriter != null)
                {
                    // The logWriter was not destroyed yet
                    _logWriter.WriteLine(message);
                }
            }
        }
    }

    private void StandardOutputEventHandler(object sender, DataReceivedEventArgs eventArgs)
    {
        string data = eventArgs?.Data;
        if (!string.IsNullOrEmpty(data))
        {
            WriteLog(data);
            _outputCapture.AppendLine("  " + data);
        }
    }

    private void StandardErrorEventHandler(object sender, DataReceivedEventArgs eventArgs)
    {
        string data = eventArgs?.Data;
        if (!string.IsNullOrEmpty(data))
        {
            WriteLog(data);
            _outputCapture.AppendLine("!! " + data);
        }
    }

    public bool IsAvailable(ref int progressIndex, ref int failureCount)
    {
        if (_state != StateFinishing)
        {
            return _state == StateIdle;
        }

        string processSpec;
        if (!string.IsNullOrEmpty(_processInfo.Parameters.Arguments))
        {
            processSpec = Path.GetFileName(_processInfo.Parameters.ProcessPath) + " " + _processInfo.Parameters.Arguments;
        }
        else
        {
            processSpec = _processInfo.Parameters.ProcessPath;

Review comment (bug):
Locking around `_outputCapture.AppendLine` is a good step, but `_outputCapture` is still read without the same lock (e.g., `_outputCapture.ToString()` in `IsAvailable`) while the async stdout/stderr callbacks may still be running until output reading is canceled/unsubscribed. This can still result in concurrent `StringBuilder` mutation during `ToString()` and keep the original assertion/crash risk. Consider either taking the same `lock (_outputCapture)` around the `ToString()`/read, or canceling output/error reads and waiting for completion before reading the captured output.

Code after:
    {
        TextWriter logWriter = _logWriter;

        if (logWriter != null)
        {
            lock (logWriter)
            {
                if (_logWriter != null)
                {
                    // The logWriter was not destroyed yet
                    _logWriter.WriteLine(message);
                }
            }
        }
    }

    private void StandardOutputEventHandler(object sender, DataReceivedEventArgs eventArgs)
    {
        string data = eventArgs?.Data;
        if (!string.IsNullOrEmpty(data))
        {
            WriteLog(data);
            lock (_outputCapture)
            {
                _outputCapture.AppendLine("  " + data);
            }
        }
    }

    private void StandardErrorEventHandler(object sender, DataReceivedEventArgs eventArgs)
    {
        string data = eventArgs?.Data;
        if (!string.IsNullOrEmpty(data))
        {
            WriteLog(data);
            lock (_outputCapture)
            {
                _outputCapture.AppendLine("!! " + data);
            }
        }
    }

    public bool IsAvailable(ref int progressIndex, ref int failureCount)
    {
        if (_state != StateFinishing)
        {
            return _state == StateIdle;
        }

        string processSpec;
        if (!string.IsNullOrEmpty(_processInfo.Parameters.Arguments))