Analysis of commit a22ece5
Assignee: @copilot
Summary
NamedPipeClient and NamedPipeServer share a large block of nearly identical message framing code — write the message size header, write the serializer ID, write the payload, flush the pipe — as well as the chunked read loop. Both classes also duplicate the same three buffer fields. The NamedPipeBase base class already exists and is the natural home for this shared logic.
Duplication Details
Pattern: IPC Message Write (serialise + frame + flush)
- Severity: High
- Occurrences: 2 (client write in
RequestReplyAsync, server write in InternalLoopAsync)
- Locations:
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs (lines 68–155)
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs (lines 197–270)
- Code Sample (client side; server side is structurally identical):
// Write the message size
#if NETCOREAPP
byte[] bytes = ArrayPool<byte>.Shared.Rent(sizeof(int));
try
{
if (!BitConverter.TryWriteBytes(bytes, sizeOfTheWholeMessage))
throw ApplicationStateGuard.Unreachable();
await _messageBuffer.WriteAsync(bytes.AsMemory(0, sizeof(int)), cancellationToken).ConfigureAwait(false);
}
finally { ArrayPool<byte>.Shared.Return(bytes); }
#else
await _messageBuffer.WriteAsync(BitConverter.GetBytes(sizeOfTheWholeMessage), 0, sizeof(int), cancellationToken).ConfigureAwait(false);
#endif
// (repeat for serializer ID and payload body) ...
// Flush
await _namedPipeClientStream.WriteAsync(_messageBuffer.GetBuffer().AsMemory(0, (int)_messageBuffer.Position), cancellationToken).ConfigureAwait(false);
await _namedPipeClientStream.FlushAsync(cancellationToken).ConfigureAwait(false);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
_namedPipeClientStream.WaitForPipeDrain();
Pattern: IPC Message Read (chunked framing loop)
- Severity: High
- Occurrences: 2
- Locations:
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs (lines 163–260)
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs (lines 129–195)
- Code Sample (server side):
int currentMessageSize = 0;
int missingBytesToReadOfWholeMessage = 0;
while (true)
{
int currentReadIndex = 0;
#if NET
int currentReadBytes = await _namedPipeServerStream.ReadAsync(_readBuffer.AsMemory(...), cancellationToken)...;
#else
int currentReadBytes = await _namedPipeServerStream.ReadAsync(_readBuffer, ..., cancellationToken)...;
#endif
if (currentMessageSize == 0)
{
currentMessageSize = BitConverter.ToInt32(_readBuffer, 0);
missingBytesToReadOfCurrentChunk = currentReadBytes - sizeof(int);
missingBytesToReadOfWholeMessage = currentMessageSize;
currentReadIndex = sizeof(int);
}
if (missingBytesToReadOfCurrentChunk > 0)
await _messageBuffer.WriteAsync(...);
missingBytesToReadOfWholeMessage -= missingBytesToReadOfCurrentChunk;
if (missingBytesToReadOfWholeMessage == 0) { /* deserialize */ }
}
Pattern: Duplicated buffer fields
- Severity: Medium
- Occurrences: 2 (identical declarations in both classes)
- Locations:
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs (lines 30–32)
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs (lines 34–36)
- Code Sample:
private readonly MemoryStream _serializationBuffer = new();
private readonly MemoryStream _messageBuffer = new();
private readonly byte[] _readBuffer = new byte[250000];
Impact Analysis
- Maintainability: Any bug fix or optimization to the message framing protocol (e.g., buffer size, platform guards,
WaitForPipeDrain logic) must be applied in two places. Recent history shows these already diverged slightly (client uses ArrayPool<byte>.Shared.Rent; server reuses _sizeOfIntArray).
- Bug Risk: Inconsistent fixes — a bug caught in the client write path may be left unfixed in the server write path (or vice versa), leading to subtle protocol asymmetry.
- Code Bloat: ~120+ lines of near-identical logic spread across two files.
Refactoring Recommendations
-
Move shared buffer fields to NamedPipeBase
- File:
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeBase.cs
- Move
_serializationBuffer, _messageBuffer, _readBuffer (and _sizeOfIntArray / ArrayPool approach, unified) to the base class.
- Both subclasses already extend
NamedPipeBase, so no inheritance changes are needed.
-
Extract WriteMessageAsync helper into NamedPipeBase
- Signature:
protected async Task WriteMessageAsync(PipeStream stream, INamedPipeSerializer serializer, object message, CancellationToken cancellationToken)
- Encapsulates: compute size header → write header bytes (unified ArrayPool path) → write serializer ID → write serialized payload → write to pipe stream → flush → WaitForPipeDrain on Windows.
- Estimated effort: ~2 hours
-
Extract ReadNextMessageAsync helper into NamedPipeBase
- Signature:
protected async Task<(int serializerId, MemoryStream payload)> ReadNextMessageAsync(PipeStream stream, CancellationToken cancellationToken)
- Encapsulates the chunked read loop and returns the deserializable payload.
- Estimated effort: ~2 hours
Implementation Checklist
Analysis Metadata
- Analyzed Files:
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs, src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs, src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeBase.cs
- Detection Method: Semantic code analysis
- Commit: a22ece5
- Analysis Date: 2026-06-01
Generated by Duplicate Code Detector · sonnet46 2.5M · ◷
Add this agentic workflows to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main
Analysis of commit a22ece5
Assignee:
@copilotSummary
NamedPipeClientandNamedPipeServershare a large block of nearly identical message framing code — write the message size header, write the serializer ID, write the payload, flush the pipe — as well as the chunked read loop. Both classes also duplicate the same three buffer fields. TheNamedPipeBasebase class already exists and is the natural home for this shared logic.Duplication Details
Pattern: IPC Message Write (serialise + frame + flush)
RequestReplyAsync, server write inInternalLoopAsync)src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs(lines 68–155)src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs(lines 197–270)Pattern: IPC Message Read (chunked framing loop)
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs(lines 163–260)src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs(lines 129–195)Pattern: Duplicated buffer fields
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs(lines 30–32)src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs(lines 34–36)Impact Analysis
WaitForPipeDrainlogic) must be applied in two places. Recent history shows these already diverged slightly (client usesArrayPool<byte>.Shared.Rent; server reuses_sizeOfIntArray).Refactoring Recommendations
Move shared buffer fields to
NamedPipeBasesrc/Platform/Microsoft.Testing.Platform/IPC/NamedPipeBase.cs_serializationBuffer,_messageBuffer,_readBuffer(and_sizeOfIntArray/ ArrayPool approach, unified) to the base class.NamedPipeBase, so no inheritance changes are needed.Extract
WriteMessageAsynchelper intoNamedPipeBaseprotected async Task WriteMessageAsync(PipeStream stream, INamedPipeSerializer serializer, object message, CancellationToken cancellationToken)Extract
ReadNextMessageAsynchelper intoNamedPipeBaseprotected async Task<(int serializerId, MemoryStream payload)> ReadNextMessageAsync(PipeStream stream, CancellationToken cancellationToken)Implementation Checklist
NamedPipeBaseWriteMessageAsyncintoNamedPipeBaseReadNextMessageAsyncintoNamedPipeBaseNamedPipeClient.RequestReplyAsyncto use shared helpersNamedPipeServer.InternalLoopAsyncto use shared helpersAnalysis Metadata
src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs,src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs,src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeBase.csAdd this agentic workflows to your repo
To install this agentic workflow, run