Skip to content

Add ContinueAsNew fresh-trace support for periodic orchestrations - #1337

Merged
Chris Gillum (cgillum) merged 15 commits into
Azure:mainfrom
chandramouleswaran:feature/continue-as-new-fresh-trace
May 20, 2026
Merged

Add ContinueAsNew fresh-trace support for periodic orchestrations#1337
Chris Gillum (cgillum) merged 15 commits into
Azure:mainfrom
chandramouleswaran:feature/continue-as-new-fresh-trace

Conversation

@chandramouleswaran

Copy link
Copy Markdown
Contributor

Summary

Long-running periodic orchestrations that use ContinueAsNew accumulate all generations into a single distributed trace. This adds an opt-in ContinueAsNewTraceBehavior.StartNewTrace option that starts the next generation in a fresh trace.

Motivation

Orchestrations that run on a schedule (e.g., every 5 hours) via ContinueAsNew end up with a single trace spanning days/weeks/months, making:

  • Individual cycle performance hard to measure
  • Trace viewers slow/unresponsive with thousands of spans
  • Anomaly detection across cycles impossible

API Changes

  • ContinueAsNewOptions class with TraceBehavior property
  • ContinueAsNewTraceBehavior enum: PreserveTraceContext (default) | StartNewTrace
  • New ContinueAsNew(string, object, ContinueAsNewOptions) overload on OrchestrationContext
// Start a fresh trace for the next generationcontext.ContinueAsNew(null,input,newContinueAsNewOptions{TraceBehavior=ContinueAsNewTraceBehavior.StartNewTrace,});

Implementation

Uses a typed bool GenerateNewTrace property on ExecutionStartedEvent (not tags) to signal fresh-trace behavior. The property is consumed once by TraceHelper (creates a fresh root producer span, stores identity in ParentTraceContext, resets to false). Subsequent replays use the persisted identity — stable span ID across replays.

Signal flow

  1. Orchestrator calls ContinueAsNew(version, input, options) → sets ContinueAsNewTraceBehavior on OrchestrationCompleteOrchestratorAction
  2. Dispatcher creates the next ExecutionStartedEvent with GenerateNewTrace = true and skips copying the old ParentTraceContext
  3. TraceHelper sees GenerateNewTrace, creates a fresh root producer span, stores its identity in ParentTraceContext, and resetsGenerateNewTrace = false
  4. Subsequent replays use the persisted identity — stable span ID across replays

Design decisions

DecisionRationale
Typed bool property on ExecutionStartedEvent instead of tagsAvoids customer tag namespace collision; avoids cross-generation tag leaking through CloneTags; typed and self-documenting
Tags are now cloned (not shared by reference)Prevents mutation of the current generation's tag dictionary during continuation
Base class throws NotSupportedExceptionExternal OrchestrationContext implementations cannot silently ignore StartNewTrace
Single 3-param overload (version + input + options)The 2-param overload (object input, ContinueAsNewOptions options) was ambiguous with (string newVersion, object input); users pass null for version to keep current
Legacy Correlation pipeline unchangedThe two trace systems (System.Diagnostics.Activity vs legacy Correlation) are independent; only ParentTraceContext controls the new pipeline

Replay safety

  • GenerateNewTrace is consumed once and reset to false
  • The result (trace identity in ParentTraceContext.Id/.SpanId) is persisted
  • Subsequent replays restore from persisted identity, not the signal
  • On crash before first persist, a new trace is created (consistent since no state from the abandoned attempt survived)

Backward Compatibility

  • Default behavior is PreserveTraceContext — zero change for existing users
  • GenerateNewTrace defaults to false — pre-upgrade serialized events deserialize correctly
  • ContinueAsNewTraceBehavior defaults to PreserveTraceContext (0) on the action
  • Existing ContinueAsNew(object) and ContinueAsNew(string, object) are unchanged

Tests

31 tests passing covering:

  • GenerateNewTrace property: default value, copy constructor, JSON serialization, backward compat
  • Tag isolation: property doesn't appear in or leak through tags
  • TraceHelper: fresh trace creation, consume-and-reset, replay with persisted identity, ambient activity isolation
  • TaskOrchestrationContext: new overloads, last-call-wins, null options rejection
  • Base class NotSupportedException
  • ContinueAsNewOptions defaults

Long-running periodic orchestrations that use ContinueAsNew accumulate all
generations into a single distributed trace, making individual cycles hard
to observe. This adds an opt-in ContinueAsNewTraceBehavior.StartNewTrace
option that starts the next generation in a fresh trace.
API changes:
- Added ContinueAsNewOptions class with TraceBehavior property
- Added ContinueAsNewTraceBehavior enum (PreserveTraceContext, StartNewTrace)
- Added ContinueAsNew(string, object, ContinueAsNewOptions) overload on
OrchestrationContext (virtual, throws NotSupportedException by default)
- TaskOrchestrationContext overrides it to set the behavior on the action
Implementation:
- Added GenerateNewTrace property on ExecutionStartedEvent (typed bool,
[DataMember], defaults to false for backward compatibility)
- Dispatcher sets GenerateNewTrace=true on the continuation event when
StartNewTrace is requested, and skips copying ParentTraceContext
- TraceHelper consumes the flag once, creates a fresh root producer span,
stores the new identity in ParentTraceContext, and resets the flag
- Subsequent replays use the persisted identity (stable span across replays)
Design decisions:
- Used a typed property instead of tags to avoid customer namespace collision
and tag-leak bugs through CloneTags
- Tags are now cloned (not shared by reference) to prevent mutation of the
current generation's tag dictionary
- Base class throws NotSupportedException instead of silently dropping options
- Only one new overload (3-param with version) to avoid overload ambiguity
between ContinueAsNew(object, ContinueAsNewOptions) and
ContinueAsNew(string, object)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 16, 2026 07:12
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) require an authorized user to comment /azp run to run.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in mechanism for ContinueAsNew to start the next orchestration generation in a fresh distributed trace (instead of accumulating all generations into one long trace), improving observability for periodic/long-running orchestrations.

Changes:

  • Introduces ContinueAsNewOptions + ContinueAsNewTraceBehavior and a new OrchestrationContext.ContinueAsNew(string, object, ContinueAsNewOptions) overload.
  • Propagates a new ExecutionStartedEvent.GenerateNewTrace signal through continuation creation and consumes it in TraceHelper to create a fresh root trace and persist stable replay identity.
  • Clones continuation tags to avoid cross-generation mutation and adds test coverage for the new behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/DurableTask.Core/Tracing/TraceHelper.csConsumes GenerateNewTrace to create a fresh root trace for the next generation.
src/DurableTask.Core/TaskOrchestrationDispatcher.csSets GenerateNewTrace on continued-as-new ExecutionStartedEvent, skips parent context copy when starting a new trace, and clones tags.
src/DurableTask.Core/TaskOrchestrationContext.csAdds the new overload and flows options.TraceBehavior into the completion action.
src/DurableTask.Core/OrchestrationContext.csAdds the new virtual overload (default throws NotSupportedException).
src/DurableTask.Core/History/ExecutionStartedEvent.csAdds the persisted GenerateNewTrace flag to history.
src/DurableTask.Core/ContinueAsNewOptions.csNew options type + enum controlling fresh-trace behavior.
src/DurableTask.Core/Command/OrchestrationCompleteOrchestratorAction.csAdds ContinueAsNewTraceBehavior to the completion action for dispatcher consumption.
Test/DurableTask.Core.Tests/ContinueAsNewTraceBehaviorTests.csAdds tests for serialization/back-compat, trace creation/consumption, overload behavior, and tag isolation.
Comments suppressed due to low confidence (1)

src/DurableTask.Core/Tracing/TraceHelper.cs:107

  • TraceHelper no longer honors the OrchestrationTags.CreateTraceForNewOrchestration ("MS_CreateTrace") tag. Since this tag is still a public constant, any existing callers that relied on setting it on ExecutionStartedEvent.Tags to force root trace creation will silently stop working. Consider keeping backward-compat by treating the tag as an alias for GenerateNewTrace (and optionally removing it from Tags after consumption), or explicitly obsoleting/removing the public tag constant as part of the change.
 // When GenerateNewTrace is set, create a fresh root trace for this orchestration.
// The flag is consumed once and reset so that subsequent replays use the
// persisted trace identity rather than creating yet another new trace.
if (startEvent.GenerateNewTrace)
{
startEvent.GenerateNewTrace = false;
// Note that if we create the trace activity for starting a new orchestration here, then its duration will be longer since its end time will be set to once we // start processing the orchestration rather than when the request for a new orchestration is committed to storage. using var activityForNewOrchestration = StartActivityForNewOrchestration(startEvent);
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/DurableTask.Core/TaskOrchestrationContext.cs Outdated
Preserve polymorphic behavior for derived TaskOrchestrationContext types
by routing ContinueAsNew(object) and ContinueAsNew(string, object) through
the virtual ContinueAsNew(string, object, ContinueAsNewOptions) overload
instead of calling the private ContinueAsNewCore directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/DurableTask.Core/Tracing/TraceHelper.cs
External clients like durabletask-dotnet's ShimDurableTaskClient set the
OrchestrationTags.CreateTraceForNewOrchestration tag on ExecutionStartedEvent
to trigger fresh root trace creation. The previous change replaced this
tag-based check with the new GenerateNewTrace property, silently breaking
those callers.
Now TraceHelper honors both mechanisms:
- GenerateNewTrace property (new ContinueAsNew path)
- Legacy CreateTraceForNewOrchestration tag (external client path)
Both signals are consumed on use to prevent double trace creation on replay.
Adds 3 tests for legacy tag backward compatibility.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 21, 2026 22:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/DurableTask.Core/Tracing/TraceHelper.cs Outdated
Comment threadsrc/DurableTask.Core/TaskOrchestrationDispatcher.cs Outdated
Comment threadsrc/DurableTask.Core/TaskOrchestrationContext.cs
Comment threadsrc/DurableTask.Core/TaskOrchestrationDispatcher.cs Outdated
- TraceHelper: Move GenerateNewTrace/tag reset after StartActivityForNewOrchestration
so signals are preserved if the call throws (retry-safe)
- Dispatcher: Fix comment to accurately describe the timestamp as dispatcher
processing time rather than 'accurate start time'
- Dispatcher: Preserve dictionary comparer when cloning tags for continuation
Skipped: ContinueAsNewOptions allocation suggestion — ContinueAsNew is called
at most once per orchestration execution, and the allocation preserves
polymorphic dispatch required by the API design.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use a static readonly instance for the default ContinueAsNewOptions
passed by the 1-param and 2-param ContinueAsNew overloads, avoiding
a heap allocation on every call while preserving polymorphic dispatch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/DurableTask.Core/Tracing/TraceHelper.cs Outdated
Comment threadsrc/DurableTask.Core/TaskOrchestrationContext.cs Outdated
Keep fresh-trace signals intact when the producer activity is suppressed, tighten the pre-upgrade compatibility test, and align the default options field declaration with local style.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadTest/DurableTask.Core.Tests/ContinueAsNewTraceBehaviorTests.cs Outdated
Comment threadsrc/DurableTask.Core/TaskOrchestrationDispatcher.cs Outdated
The 3-argument string.Replace(string, string, StringComparison) overload
is only available in .NET Core/.NET 5+. Use the 2-argument overload for
.NET Framework 4.8 compatibility.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 27, 2026 22:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/DurableTask.Core/TaskOrchestrationDispatcher.cs
CopilotAI review requested due to automatic review settings May 13, 2026 05:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment threadtest/DurableTask.Core.Tests/ContinueAsNewTraceBehaviorTests.cs Outdated
Use ExecutionStartedEvent.Timestamp for ContinueAsNew producer span timing instead of writing an internal RequestTime marker into user-visible tags. Restart the orchestration trace activity when a StartNewTrace continuation executes in the same work-item loop, and move the new trace tests under the correctly cased test project path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 18, 2026 20:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment threadsrc/DurableTask.Core/TaskOrchestrationDispatcher.cs Outdated
Remove the unused System.Globalization import left behind by the fresh trace timestamp cleanup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@cgillum

Copy link
Copy Markdown
Member

Chandramouleswaran (@chandramouleswaran) can you check and see if there's anything from this PR that should be documented under /docs/telemetry? I just want to make sure that this new feature has the right level of visibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 20, 2026 01:05
@chandramouleswaran

Copy link
Copy Markdown
ContributorAuthor

Chris Gillum (@cgillum) thanks for the review -- good call on making sure this is documented.

I added coverage for this under /docs/telemetry:

  • docs/telemetry/distributed-tracing.md now documents the default ContinueAsNew trace-context preservation behavior and the ContinueAsNewTraceBehavior.StartNewTrace opt-in for long-running/periodic orchestrations.
  • docs/telemetry/traces/semantic-conventions.md now notes the producer/server span shape for fresh-trace continue-as-new generations.

I also ran the focused ContinueAsNew trace tests after the docs update: 26/26 passed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment threadtest/DurableTask.Core.Tests/ContinueAsNewTraceBehaviorTests.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@cgillum

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@cgillum
Chris Gillum (cgillum) merged commit ba4dd7c into Azure:mainMay 20, 2026
45 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@chandramouleswaran@cgillum@fanyirobin