Repository navigation
Azure Blob payload interceptor skips continue-as-new carryover payloads #819
Description
The Azure Blob payload interceptor does not externalize payloads nested in CompleteOrchestrationAction.CarryoverEvents. These fields can therefore bypass both the configured externalization threshold and MaxPayloadBytes, leaving a continue-as-new response too large for the configured transport even when external payload storage is enabled.
Current source evidence
Inspected main at 9fc9b6c5ec9ca8317c31294e831c4e8533ff46d3.
ExternalizeOrchestratorResponseAsynchandles a completion'sResultandDetails, and traverses rewindNewHistory, but never traverses completionCarryoverEvents.- The native worker uses
BehaviorOnContinueAsNew.Carryover. ProtoUtilsserializes the underlying completion action's carryover events into the outbound protobuf response.
Not every preserved-event path is affected: TaskOrchestrationContextWrapper forwards some buffered events using self-targeted SendEvent actions, which are already covered. This issue concerns native CarryoverEvents, including events carried forward by the underlying executor after continue-as-new has been scheduled.
Focused regression recipe
In the existing AzureBlobPayloadsSideCarInterceptorTests harness, use its TrackingPayloadStore and ExternalizeAsync helper:
TrackingPayloadStore store = new();
AzureBlobPayloadsSideCarInterceptor interceptor =
new(store, new LargePayloadStorageOptions());
P.OrchestratorResponse response = new() { InstanceId = "carryover-repro" };
P.CompleteOrchestrationAction complete = new()
{
OrchestrationStatus = P.OrchestrationStatus.ContinuedAsNew,
};
for (int i = 0; i < 5; i++)
{
complete.CarryoverEvents.Add(new P.HistoryEvent
{
EventId = i,
EventRaised = new P.EventRaisedEvent
{
Name = $"buffered-{i}",
Input = "\"" + new string('x', 1024 * 1024 - 2) + "\"",
},
});
}
response.Actions.Add(new P.OrchestratorAction { Id = 1, CompleteOrchestration = complete });
await ExternalizeAsync(interceptor, response, CancellationToken.None);
store.UploadCount.Should().Be(5);
response.CalculateSize().Should().BeLessThan(4 * 1024 * 1024);Each serialized event input is exactly 1 MiB: above the default 256 KiB threshold and below the default 10 MiB maximum. The current collector does not schedule any of these five uploads. Also cover a carryover input above MaxPayloadBytes: it should use the existing non-retriable orchestration-failure conversion rather than remain inline.
Evidence boundary: the .NET conclusion is based on the current implementation and its real worker/serialization path. The .NET regression recipe above has not been executed for this report. The corresponding JavaScript runtime/protobuf regressions were reproduced and fixed; no live .NET/DTS execution is claimed here.
Acceptance Criteria
- Traverse completion carryover history through the existing payload-bearing history-event mapping and the bounded upload mechanism.
- Apply the same UTF-8 threshold and maximum-size rules used for other outbound business payloads.
- Preserve event identity, order, names, completion metadata, and continued-as-new status for valid payloads.
- Verify payload hydration restores the original event data after a protobuf round trip.
- Add threshold/maximum boundary tests and a regression with multiple large events whose original response exceeds the chosen transport test bound.
- Retain existing cancellation, bounded-concurrency, drainage, and worker-failure semantics.
Related
- Equivalent JavaScript fix: externalize continue-as-new carryover payloads.
- Existing .NET payload-failure work in PR 816 addresses retry idempotency and entity cleanup; its inspected head
f9baad3does not add this carryover traversal.
Activity