Skip to content

Azure Blob payload interceptor skips continue-as-new carryover payloads #819

Open

Description

@YunchuWang

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.

  • ExternalizeOrchestratorResponseAsync handles a completion's Result and Details, and traverses rewind NewHistory, but never traverses completion CarryoverEvents.
  • The native worker uses BehaviorOnContinueAsNew.Carryover.
  • ProtoUtils serializes 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions