Stream Mediator Framework message serialization - #924
Conversation
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Introduces streaming, bounded message serialization/deserialization for MediatorFramework messaging to reduce repeated buffering across codec, compression, DataBus offload, and transport encoding.
Changes:
- Reworks
IMessagingCodecand dispatch to asyncPipeReader/PipeWriterstreaming APIs. - Adds transactional DataBus write sessions with committed integrity metadata (length + SHA-256) and cleanup on failure.
- Updates transports and envelope codecs to better account for native overhead and reduce payload copying.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Ark.Tools.MediatorFramework.Tests/MessagingStreamingTestExtensions.cs | Adds test helpers to adapt streaming codecs to existing buffered test patterns. |
| tests/Ark.Tools.MediatorFramework.Tests/MessagingRuntimeTests.cs | Updates dispatcher tests to await async payload deserialization. |
| tests/Ark.Tools.MediatorFramework.Tests/MessagingCompressionAndDataBusTests.cs | Expands coverage for claim-check cleanup + async stream payload reader behavior. |
| tests/Ark.Tools.MediatorFramework.Tests/GeneratorSnapshotTests.cs | Updates generator snapshot expectations to async payload deserialization calls. |
| src/mediator-framework/Ark.Tools.MediatorFramework/MessagingPipelineContracts.cs | Removes buffered payload from incoming/outgoing contexts to support streaming ownership. |
| src/mediator-framework/Ark.Tools.MediatorFramework/IMessagingDataBus.cs | Introduces transactional write sessions + delete API; adjusts DataBus contract docs. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/StorageQueueMessagingTransport.cs | Computes inline payload budget accounting for Storage Queue envelope overhead. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/StorageQueueEnvelopeCodec.cs | Switches canonical encoding to a pooled fixed buffer writer and direct Base64 string generation. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ServiceBusMessagingTransport.cs | Adds inline payload budget accounting and reduces redundant payload copies for single-segment payloads. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ProtobufMessagingCodec.cs | Updates protobuf codec to streaming PipeReader/PipeWriter contract. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ProtobufContractRegistry.cs | Changes protobuf parser registry to stream-based parsing. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingStreamPayloadReader.cs | Implements async streaming deserialization by opening/disposing payload streams per read. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingPayloadSender.cs | Implements bounded streaming pipeline: serialize → optional compress → inline/DataBus destination, with pooled inline ownership. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingPayloadReceiver.cs | Returns replayable stream factory-backed payload reader rather than buffering upfront. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingDispatcher.cs | Stops passing buffered payload into MessagingIncomingContext. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingBus.cs | Adopts MessagingOutgoingPayload ownership/disposal and updates transport/outbox writes. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagePackMessagingCodec.cs | Updates MessagePack codec to async stream-based serialization/deserialization. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/JsonMessagingCodec.cs | Updates JSON codec to async stream-based serialization/deserialization. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/InMemoryMessagingTransport.cs | Implements inline payload budget API (unbounded). |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/InMemoryMessagingDataBus.cs | Implements transactional write sessions and delete for in-memory DataBus with integrity validation. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/IMessagingTransport.cs | Adds GetMaximumInlinePayloadBytes API for transports to report header-adjusted payload budget. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/IMessagingPayloadReader.cs | Replaces buffered payload reads with async typed deserialization. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/IMessagingCodec.cs | Replaces buffered codec APIs with streaming PipeReader/PipeWriter contracts. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/HashingWriteStream.cs | Adds streaming SHA-256 hashing writer used by DataBus write sessions. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/CompressionSwitchingBufferWriter.cs | Removes buffered compression-switching writer in favor of streaming pipeline. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/AzureBlobMessagingDataBus.cs | Implements transactional streamed writes with post-write metadata commit and abort cleanup. |
| src/mediator-framework/Ark.Tools.MediatorFramework.Generators/MessagingNetworkGenerator.cs | Updates generated dispatch to await payload deserialization. |
| samples/Ark.MediatorFramework.Sample/test/Ark.MediatorFramework.Sample.Tests/MessagingBusSampleTests.cs | Updates sample tests to deserialize via streaming codec APIs. |
| docs/mediator-framework/guide/serialization.md | Updates documentation to describe streaming codec APIs and bounded send/receive behavior. |
Suppressed comments (2)
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingPayloadSender.cs:25
- Public constructor XML docs lost the
<param>descriptions in this change. This repo requires XML docs for public APIs; restore parameter documentation so generated docs and analyzers remain consistent.
/// <summary>Creates a payload sender.</summary>
public MessagingPayloadSender(
IMessagingDataBus dataBus,
MessagingNetworkOptions network,
CompressionAlgorithm algorithm,
int compressionMinimumSizeBytes)
{
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingPayloadSender.cs:46
BuildOutgoingPayloadAsyncnow returns an ownedMessagingOutgoingPayloadthat must be disposed to return pooled buffers, but the method no longer documents parameters/return semantics. Restore XML docs (including disposal requirement) to keep the public API contract clear.
/// <summary>Serializes, optionally compresses, and claim-checks a message.</summary>
public async Task<MessagingOutgoingPayload> BuildOutgoingPayloadAsync<T>(
T message,
IMessagingCodec codec,
IMessagingTransport transport,
IDictionary<string, string> headers,
CancellationToken ctk)
where T : class
{
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Code Coverage OverviewLanguages: C# C# / Ark.ToolsThe overall line coverage in commit 0ee0f53 in the Show a line coverage summary of the most impacted files.
Updated |
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Reviewed. Overall line coverage remains unchanged at 47%; focused Mediator Framework validation passes all 259 tests. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/HashingWriteStream.cs:58
- After _completeHash() is called, HashingWriteStream still allows further writes, which can mutate the underlying stream after the committed hash/length were captured. Guard Write/WriteAsync to throw once hashing has completed.
public override void Write(ReadOnlySpan<byte> buffer)
{
_inner.Write(buffer);
_hash.AppendData(buffer);
_bytesWritten += buffer.Length;
}
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Reviewed the suppressed finding and confirmed it. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.
Suppressed comments (6)
Previously missed (5) — in code that hasn't changed since the last review.
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ServiceBusMessagingTransport.cs:45
GetMaximumInlinePayloadBytescan return a negative payload budget when headers alone exceed the Service Bus ceiling. That negative value then propagates into sender inline buffering decisions; clamp the budget at 0 to match the interface default behavior.
/// <inheritdoc />
public long? GetMaximumInlinePayloadBytes(IReadOnlyDictionary<string, string> headers)
{
return _maximumMessageBytes - MeasureNative(headers, ReadOnlySequence<byte>.Empty);
}
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/StorageQueueMessagingTransport.cs:90
GetMaximumInlinePayloadBytescan go negative when the canonical header footprint exceeds the Storage Queue canonical limit. A negative payload budget is not meaningful and causes downstream inline-limit calculations to underflow; clamp to 0.
/// <inheritdoc />
public long? GetMaximumInlinePayloadBytes(IReadOnlyDictionary<string, string> headers)
{
return StorageQueueLimits.MaximumNormalCanonicalBytes
- StorageQueueEnvelopeCodec._measureCanonical(headers, ReadOnlySequence<byte>.Empty);
}
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ProtobufMessagingCodec.cs:49
DeserializeAsyncusesawait Task.CompletedTasksolely to satisfy the compiler, which adds an unnecessary async state machine. Since parsing is synchronous, make this method non-asyncand returnTask.FromResult(...)instead.
var result = parse(reader.AsStream(leaveOpen: true));
await Task.CompletedTask.ConfigureAwait(false);
return result;
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/InMemoryMessagingDataBus.cs:57
OpenWriteAsyncis implemented withawait Task.CompletedTaskeven though it performs no async work. This creates an unnecessary async state machine; return a completed task instead.
This issue also appears on line 113 of the same file.
_removeExpired();
var id = Guid.NewGuid().ToString("N");
await Task.CompletedTask.ConfigureAwait(false);
return new WriteSession(this, id);
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/MessagingPayloadReceiver.cs:124
PreparePayloadReaderAsynccurrently awaitsTask.CompletedTaskbut does no async work. Removingasync/awaitavoids an unnecessary state machine while keeping the same API shape.
public async Task<MessagingStreamPayloadReader> PreparePayloadReaderAsync(
IReadOnlyDictionary<string, string> headers,
ReadOnlySequence<byte> transportPayload,
IMessagingCodec codec,
CancellationToken ctk)
{
ArgumentNullException.ThrowIfNull(codec);
await Task.CompletedTask.ConfigureAwait(false);
return new MessagingStreamPayloadReader(
token => PreparePayloadAsync(headers, transportPayload, token),
codec);
}
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/InMemoryMessagingDataBus.cs:119
DeleteAsyncusesawait Task.CompletedTaskeven though it has no asynchronous operations. ReturnTask.CompletedTaskdirectly to avoid the async state machine.
public async Task DeleteAsync(string attachmentId, CancellationToken ctk)
{
ArgumentException.ThrowIfNullOrEmpty(attachmentId);
ctk.ThrowIfCancellationRequested();
_attachments.TryRemove(attachmentId, out _);
await Task.CompletedTask.ConfigureAwait(false);
}
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
Co-authored-by: AndreaCuneo <5227688+AndreaCuneo@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/InMemoryMessagingDataBus.cs:169
CompleteAsyncstoresMemoryStream.GetBuffer()into the attachment, which retains the stream's capacity (often >Length) and can significantly inflate memory usage for large attachments. Store a right-sized array so in-memory DataBus doesn't keep extra unused bytes alive.
var hash = _stream._completeHash();
var content = _buffer.GetBuffer();
var length = checked((int)_buffer.Length);
_owner._attachments[_id] = new Attachment(
content,
src/mediator-framework/Ark.Tools.MediatorFramework.Messaging/ProtobufMessagingCodec.cs:49
DeserializeAsyncusesawait Task.CompletedTasksolely to satisfy the async signature, which is a no-op and obscures intent. Prefer an awaitedTask.FromResult(...)return so the method remains async without dummy work.
var result = parse(reader.AsStream(leaveOpen: true));
await Task.CompletedTask.ConfigureAwait(false);
return result;
| } | ||
| throw; | ||
| } | ||
| var result = await destinationTask.ConfigureAwait(false); |
Mediator messaging buffered payloads repeatedly across serialization, compression, DataBus offload, and transport encoding. This change introduces bounded streaming pipelines while retaining only threshold-sized pooled buffers.
Changes
Streaming codecs
PipeReader/PipeWritercontracts.Bounded outgoing pipeline
Transactional DataBus
Payload ownership
MessagingOutgoingContext.Transport efficiency