Skip to content

[duplicate-code] Duplicate IPC Message Serialization/Read Logic in NamedPipeClient and NamedPipeServer #8723

@Evangelink

Description

@Evangelink

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

  1. 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.
  2. 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
  3. 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

  • Review duplication findings
  • Move buffer fields to NamedPipeBase
  • Extract WriteMessageAsync into NamedPipeBase
  • Extract ReadNextMessageAsync into NamedPipeBase
  • Update NamedPipeClient.RequestReplyAsync to use shared helpers
  • Update NamedPipeServer.InternalLoopAsync to use shared helpers
  • Run unit + integration tests to verify no regression
  • Verify no functionality broken

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
  • expires on Jun 3, 2026, 5:57 AM UTC

Metadata

Metadata

Labels

type/automationCreated or maintained by an agentic workflow.type/tech-debtCode health, refactoring, simplification.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions