Skip to content

Preflight all externalized payload sizes before starting uploads #820

Open

Description

@YunchuWang

Externalized payload sizes are currently validated inside individual upload operations. A multi-field message can therefore upload valid earlier fields before a later field is rejected for a size that was already known before any I/O. If the response is then replaced by a failed completion, the uploaded references can be discarded, leaving blobs the backend never recorded.

Current source evidence

Inspected main at 9fc9b6c5ec9ca8317c31294e831c4e8533ff46d3.

This is a storage-side-effect issue, not a claim that the original business request partially commits. When a successful upload's reference is never submitted to or recorded by the backend, tombstone-based auto-purge has no recorded reference from which to discover that blob.

Focused regression recipe

In the existing AzureBlobPayloadsSideCarInterceptorTests harness:

TrackingPayloadStore store = new();
LargePayloadStorageOptions options = new()
{
    ThresholdBytes = 1024,
    MaxPayloadBytes = 2048,
};
AzureBlobPayloadsSideCarInterceptor interceptor = new(store, options);
P.OrchestratorResponse response = new()
{
    InstanceId = "size-preflight-repro",
    CustomStatus = new string('s', 1024),
};
response.Actions.Add(new P.OrchestratorAction
{
    Id = 1,
    ScheduleTask = new P.ScheduleTaskAction
    {
        Name = "activity",
        Input = new string('x', 2049),
    },
});

await ExternalizeAsync(interceptor, response, CancellationToken.None);

store.UploadCount.Should().Be(0);
response.Actions.Should().ContainSingle();
response.Actions[0].CompleteOrchestration.OrchestrationStatus
    .Should().Be(P.OrchestrationStatus.Failed);

A store that completes the valid first upload immediately demonstrates the current sequencing: custom status is uploaded, the later action fails its local size check, and failure conversion removes the uploaded token from the returned response. Cover an entity batch with a valid state followed by an oversized operation as well: it should reject without uploading or mutating any payload field.

Evidence boundary: this is source-confirmed missing preflight in the current .NET implementation. The .NET recipe above has not been run for this report. The equivalent JavaScript scenarios were actually reproduced with an in-memory store, then fixed with zero-upload regression assertions.

Acceptance Criteria

  • Snapshot eligible nonempty fields and their UTF-8 byte sizes, then validate every eligible size before starting any upload.
  • On a deterministic maximum-size violation, perform zero store uploads and no payload-token substitutions for the entire message.
  • Keep the maximum scoped to fields selected for externalization; preserve inclusive thresholds, defaults, and inline/empty/absent behavior.
  • Keep validation within the existing failure-conversion boundary: client/entity requests propagate the size error, and activity/orchestrator responses retain the appropriate non-retriable failure representation.
  • For eligible work, honor precancellation before deterministic size validation; preserve canceled no-op behavior.
  • Once size validation passes, retain bounded concurrency, drainage, and message-order selection of actual I/O failures.
  • Add regressions for valid-first/oversized-later orchestration and entity messages, cancellation, and all-valid messages.
  • Document that deterministic size validation now precedes hypothetical earlier upload failures because no I/O starts.

Non-goals

This does not provide a cross-blob transaction, recover existing orphan blobs, or guarantee zero orphan uploads after real network failures, cancellation during I/O, or uncertain backend submission. Do not add unconditional catch-and-delete rollback: an uncertain submission is not proof the backend never accepted a reference.

Related

  • Equivalent JavaScript fix: preflight payload sizes before uploads.
  • Existing .NET payload-failure work in PR 816 has related response preparation, but its inspected head f9baad3 still lacks a message-wide zero-upload size-preflight pass.
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