fix: ensure thread-safe initialization of console line buffers#4551
fix: ensure thread-safe initialization of console line buffers#4551
Conversation
Addresses post-merge feedback from PR #4549 regarding thread-safety concerns in console buffer lazy initialization. Replaced nullable StringBuilder fields with Lazy<StringBuilder> to guarantee thread-safe initialization when multiple threads access GetConsoleStdOutLineBuffer() or GetConsoleStdErrLineBuffer() concurrently. The previous implementation using ??= was not thread-safe and could result in multiple StringBuilder instances being created before assignment completes, undermining per-context buffer isolation. Benefits: - Thread-safe by default with Lazy<T> - No manual double-checked locking needed - Cleaner, more idiomatic C# code - AOT-compatible Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
SummaryReplaces nullable StringBuilder fields with Lazy for thread-safe initialization of console line buffers. Critical IssuesNone found ✅ AnalysisThe PR correctly addresses a legitimate thread-safety concern in the null-coalescing initialization pattern. However, I have one observation about the implementation: Observation: Lock objects may be redundant The change replaces var (buffer, bufferLock) = GetLineBuffer();
lock (bufferLock)
{
if (buffer.Length > 0)
{
RouteToSinks(buffer.ToString());
buffer.Clear();
}
}The locks are clearly necessary for protecting buffer operations. The Correctness: Performance consideration: Verdict✅ APPROVE - Clean fix that properly addresses the thread-safety issue without introducing new problems. 🤖 Generated with Claude Code |
Problem
PR #4549 introduced per-context console line buffers to prevent output mixing between parallel tests. However, a post-merge review comment identified a thread-safety issue with the lazy initialization in
Context.cs:Race condition: Multiple threads calling
GetConsoleStdOutLineBuffer()simultaneously could each create their ownStringBuilderbefore the null-coalescing assignment completes, undermining the per-context buffer isolation that #4549 was designed to provide.Solution
Replace nullable
StringBuilder?fields withLazy<StringBuilder>for guaranteed thread-safe initialization:TUnit.Core/Context.cs:
StringBuilder? _consoleStdOutLineBuffer→Lazy<StringBuilder> _consoleStdOutLineBuffer = new(() => new StringBuilder())StringBuilder? _consoleStdErrLineBuffer→Lazy<StringBuilder> _consoleStdErrLineBuffer = new(() => new StringBuilder()).Valueinstead of??=operatorBenefits
✅ Thread-safe by default -
Lazy<T>guarantees single initialization even under concurrent access✅ No manual locking - No need for double-checked locking pattern
✅ Idiomatic C# - Standard approach for lazy initialization
✅ AOT-compatible - Works with Native AOT compilation
✅ Zero behavior change - Transparent fix maintaining all existing functionality
Testing
ParallelConsoleOutputTestsfrom fix: prevent console output mixing between parallel tests #4549 continue to validate correct behaviorAddresses feedback from: #4549 (comment)
🤖 Generated with Claude Code