Skip to content

Add DURABLE0011: ContinueAsNew warning for unbounded orchestration loops#660

Merged
torosent merged 13 commits intomainfrom
analyzers/continue-as-new-warning
Mar 10, 2026
Merged

Add DURABLE0011: ContinueAsNew warning for unbounded orchestration loops#660
torosent merged 13 commits intomainfrom
analyzers/continue-as-new-warning

Conversation

@torosent
Copy link
Member

@torosent torosent commented Mar 10, 2026

Summary

Adds a new Roslyn analyzer DURABLE0011 that warns when an orchestration contains a while(true) loop that calls any TaskOrchestrationContext method but has no ContinueAsNew call within that loop.

Problem this addresses

Long-lived orchestrations that run in unbounded loops accumulate history without limit. Every TaskOrchestrationContext method call (e.g. CallActivityAsync, WaitForExternalEvent, CallSubOrchestratorAsync, CreateTimer) adds to the replay history. This leads to increasingly expensive replays and can cause backend persistence failures. This was identified as the root cause of a production crash loop in the Durable Task Scheduler.

What is included

  • ContinueAsNewOrchestrationAnalyzer.cs -- Roslyn analyzer (ID: DURABLE0011)
  • Resource strings in Resources.resx
  • Release tracking entry in AnalyzerReleases.Unshipped.md
  • Test class with 9 test cases (all passing)

Detection rules

Flagged Not flagged
while(true) + any context method + no ContinueAsNew while(true) + ContinueAsNew present
while(true) + CallActivityAsync + no ContinueAsNew No while(true) loop
while(true) + WaitForExternalEvent + no ContinueAsNew while(true) with no context method calls
while(true) + CallSubOrchestratorAsync + no ContinueAsNew

Implementation

  • Uses semantic IOperation analysis to verify methods are on TaskOrchestrationContext (no false positives from identically-named methods on other types)
  • Detects any TaskOrchestrationContext method call (except ContinueAsNew itself) as history-growing
  • Initialize() validates TaskOrchestrationContext symbol is available before running
  • Analyzer class is sealed per repo conventions

Testing

  • 152 total tests pass, 0 skipped, 0 regressions
  • 9 new tests: 4 positive-detection + 5 no-diagnostic cases

Verification

DURABLE0011 Analyzer Verification

Adds a new Roslyn analyzer (DURABLE0011) that warns when an
orchestration contains a while(true) loop with WaitForExternalEvent
or CallSubOrchestratorAsync but no ContinueAsNew call.

This pattern leads to unbounded history growth that can cause
increasingly expensive replays, persistence timeouts, and
service-wide instability.

The analyzer detects:
- while(true) with WaitForExternalEvent and no ContinueAsNew
- while(true) with CallSubOrchestratorAsync and no ContinueAsNew

Does not flag:
- while(true) with only CallActivityAsync (bounded by design)
- while(true) that includes ContinueAsNew (correctly bounded)
- Non-loop orchestrations

Test status: 5 tests pass, 3 positive-detection tests skipped
pending test infrastructure update for TaskOrchestrationContext
symbol resolution in CommonAssemblies test references.
Copilot AI review requested due to automatic review settings March 10, 2026 19:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Roslyn analyzer rule (DURABLE0011) to warn about orchestrations that use unbounded while(true) loops with history-growing operations (WaitForExternalEvent / CallSubOrchestratorAsync) but do not call ContinueAsNew, helping prevent unbounded history growth and replay degradation in Durable Task orchestrations.

Changes:

  • Introduces ContinueAsNewOrchestrationAnalyzer (DURABLE0011) to detect problematic unbounded orchestration loops.
  • Adds localized resource strings (title + message) for the new analyzer.
  • Adds release tracking metadata and a new test class (with several diagnostic tests currently skipped).

Reviewed changes

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

File Description
test/Analyzers.Tests/Orchestration/ContinueAsNewOrchestrationAnalyzerTests.cs Adds test coverage for DURABLE0011 scenarios (positive cases currently skipped).
src/Analyzers/Resources.resx Adds title/message resource strings for DURABLE0011.
src/Analyzers/Orchestration/ContinueAsNewOrchestrationAnalyzer.cs Implements the DURABLE0011 analyzer logic.
src/Analyzers/AnalyzerReleases.Unshipped.md Registers DURABLE0011 in analyzer release tracking.

torosent and others added 2 commits March 10, 2026 12:56
…e, enable tests

- Replace syntax-based IdentifierNameSyntax string matching with semantic
  IOperation analysis using IsEqualTo to verify methods are actually on
  TaskOrchestrationContext (fixes false positives/negatives)
- Add sealed modifier to ContinueAsNewOrchestrationAnalyzer class
- Add Initialize() override to validate TaskOrchestrationContext symbol
- Enable all 3 previously-skipped positive detection tests with proper
  diagnostic location markup
- All 8 tests pass (previously only 5 passed, 3 were skipped)
- Full test suite: 151 passed, 0 skipped, 0 failed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ability

Address Copilot review comment: the analyzer checks for the presence
of a ContinueAsNew call within the while loop, not reachability via
control-flow analysis. Updated the XML summary to match actual behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

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 4 out of 4 changed files in this pull request and generated 4 comments.

torosent and others added 2 commits March 10, 2026 13:22
- Update diagnostic message to say 'within that loop' for accuracy
- Document helper-method limitation in visitor XML doc
- Cache invocations array and add early break when both flags are set
- Use VerifyCS.Diagnostic() instead of manual DiagnosticResult ctor

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 20:58
Copy link
Contributor

Copilot AI left a comment

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 4 out of 5 changed files in this pull request and generated 1 comment.

torosent and others added 2 commits March 10, 2026 14:04
…ust WaitForExternalEvent

Any TaskOrchestrationContext method call (CallActivityAsync, CreateTimer,
WaitForExternalEvent, CallSubOrchestratorAsync, etc.) in a while(true) loop
without ContinueAsNew causes unbounded history growth. The analyzer now
detects any context method invocation, not just a subset.

Changes:
- Check ContainingType == TaskOrchestrationContext for any method (except
  ContinueAsNew itself) instead of matching specific method names
- Updated resource message to reflect broader detection
- Added test for activity-only loop without ContinueAsNew (now reports)
- Added test for while(true) with no context calls (no diagnostic)
- 152 total tests pass, 0 skipped

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 21:05
…lEvent

Address review comment: the SubOrchestrator test also called
WaitForExternalEvent, masking potential regressions. Now tests
CallSubOrchestratorAsync in isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

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 4 out of 5 changed files in this pull request and generated 4 comments.

torosent and others added 2 commits March 10, 2026 14:13
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 21:16
torosent and others added 2 commits March 10, 2026 14:18
- Rename test to TaskOrchestratorWhileTrueWithContextCallsNoContinueAsNew
  since the loop calls both CallActivityAsync and CreateTimer, not just
  activities
- Document the span containment limitation for lambdas/local functions
  in the visitor XML doc

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace TextSpan.Contains check with IWhileLoopOperation.Body.Descendants()
to walk the semantic operation tree. This correctly excludes invocations
inside lambdas or local functions declared within the loop body, which
have their own operation subtrees and are not directly executed by the loop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

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 4 out of 5 changed files in this pull request and generated 2 comments.

Cover the third orchestration type (OrchestratorFunc via AddOrchestratorFunc)
with both positive-detection and no-diagnostic tests, ensuring DURABLE0011
works across all supported orchestration patterns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@YunchuWang
Copy link
Member

helpLinkUri inconsistent with all other analyzers
Severity: Medium
Priority: P1
Confidence: High
Category: API and Contract Safety
Location: ContinueAsNewOrchestrationAnalyzer.cs — helpLinkUri: "https://aka.ms/durabletask-analyzers"
Why this matters: All 15 existing analyzer rules use "https://go.microsoft.com/fwlink/?linkid=2346202". The aka.ms link may resolve differently and creates an inconsistency visible to users in the IDE warning tooltip. If this was a deliberate decision to start migrating to a new URL, it should be documented; otherwise it should match existing rules.
Evidence: Every other helpLinkUri in src/Analyzers/**/*.cs uses the fwlink URL.
Recommendation: Change to helpLinkUri: "https://go.microsoft.com/fwlink/?linkid=2346202" unless the team intentionally wants to move all analyzers to the new link (in which case do it in a separate PR for all rules).
Publish target: inline

@torosent
Copy link
Member Author

helpLinkUri inconsistent with all other analyzers Severity: Medium Priority: P1 Confidence: High Category: API and Contract Safety Location: ContinueAsNewOrchestrationAnalyzer.cs — helpLinkUri: "https://aka.ms/durabletask-analyzers" Why this matters: All 15 existing analyzer rules use "https://go.microsoft.com/fwlink/?linkid=2346202". The aka.ms link may resolve differently and creates an inconsistency visible to users in the IDE warning tooltip. If this was a deliberate decision to start migrating to a new URL, it should be documented; otherwise it should match existing rules. Evidence: Every other helpLinkUri in src/Analyzers/**/*.cs uses the fwlink URL. Recommendation: Change to helpLinkUri: "https://go.microsoft.com/fwlink/?linkid=2346202" unless the team intentionally wants to move all analyzers to the new link (in which case do it in a separate PR for all rules). Publish target: inline

Need to update the other analyzers. This is the wrong link

@torosent torosent merged commit 837fe96 into main Mar 10, 2026
8 checks passed
@torosent torosent deleted the analyzers/continue-as-new-warning branch March 10, 2026 22:32
@YunchuWang YunchuWang mentioned this pull request Mar 17, 2026
Sign up for free to 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.

3 participants