DtfDeterminismAnalyzer 1.0.0
dotnet add package DtfDeterminismAnalyzer --version 1.0.0
NuGet\Install-Package DtfDeterminismAnalyzer -Version 1.0.0
<PackageReference Include="DtfDeterminismAnalyzer" Version="1.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="DtfDeterminismAnalyzer" Version="1.0.0" />
<PackageReference Include="DtfDeterminismAnalyzer"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add DtfDeterminismAnalyzer --version 1.0.0
#r "nuget: DtfDeterminismAnalyzer, 1.0.0"
#:package DtfDeterminismAnalyzer@1.0.0
#addin nuget:?package=DtfDeterminismAnalyzer&version=1.0.0
#tool nuget:?package=DtfDeterminismAnalyzer&version=1.0.0
DTF Determinism Analyzer
A production-ready Roslyn analyzer and code fixes package that validates Durable Task Framework (DTF) orchestration code for determinism constraints. Ensures your orchestrator functions follow replay-safe patterns required by Azure Durable Functions and Durable Task Framework.
Installation
Install via NuGet Package Manager:
<PackageReference Include="DtfDeterminismAnalyzer" Version="1.0.0" PrivateAssets="all" />
Or via .NET CLI:
dotnet add package DtfDeterminismAnalyzer --version 1.0.0
Quick Start
1. Write Your Orchestrator
Azure Durable Functions:
[FunctionName("MyOrchestrator")]
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
// The analyzer will automatically detect determinism issues
var result = await context.CallActivityAsync<string>("MyActivity", "input");
return result;
}
Durable Task Framework:
public class MyOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
// The analyzer will automatically detect determinism issues
var result = await context.ScheduleTask<string>(typeof(MyActivity), input);
return result;
}
}
2. Fix Issues Automatically
When you write problematic code, the analyzer will:
- 🔍 Detect non-deterministic patterns
- ⚠️ Report diagnostics with clear messages
- 🔧 Suggest automatic fixes via code actions
3. Configure Rules (Optional)
Customize rule severities in .editorconfig
:
[*.cs]
# DTF Determinism Rules
dotnet_diagnostic.DFA0001.severity = error
dotnet_diagnostic.DFA0002.severity = warning
dotnet_diagnostic.DFA0003.severity = suggestion
Overview
This package helps ensure your DTF orchestrator functions follow determinism rules required for proper replay behavior. It provides:
- Static analysis - Detects non-deterministic API usage in orchestrator functions
- Code fixes - Automatic fixes to replace problematic code with durable alternatives
- IDE integration - Works in Visual Studio, VS Code, and Rider
Supported Frameworks
The analyzer supports both major Durable Task implementations:
🔵 Azure Durable Functions
- Uses
[OrchestrationTrigger]
parameters - Functions-based programming model
- Built on top of Durable Task Framework
- Serverless Azure Functions runtime
🟢 Durable Task Framework (DTF)
- Pure .NET framework for durable orchestrations
- Class-based
TaskOrchestration<TResult, TInput>
model - Self-hosted or custom runtime
- Direct usage of DTF primitives
Both frameworks share the same determinism constraints and replay behavior, making this analyzer valuable for any durable orchestration code.
Rules
DFA0001: DateTime and Stopwatch APIs
Don't use DateTime.Now, DateTime.UtcNow, or Stopwatch in orchestrators
❌ Problem (Azure Functions):
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var now = DateTime.Now; // Non-deterministic!
var utcNow = DateTime.UtcNow; // Non-deterministic!
var stopwatch = Stopwatch.StartNew(); // Non-deterministic!
}
❌ Problem (DTF):
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
var now = DateTime.Now; // Non-deterministic!
var utcNow = DateTime.UtcNow; // Non-deterministic!
var stopwatch = Stopwatch.StartNew(); // Non-deterministic!
}
✅ Solution (Azure Functions):
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var now = context.CurrentUtcDateTime.ToLocalTime(); // Deterministic
var utcNow = context.CurrentUtcDateTime; // Deterministic
var timer = await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(10), CancellationToken.None);
}
✅ Solution (DTF):
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
var now = context.CurrentUtcDateTime.ToLocalTime(); // Deterministic
var utcNow = context.CurrentUtcDateTime; // Deterministic
var timer = await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(10));
}
DFA0002: Guid.NewGuid() calls
Don't use Guid.NewGuid() in orchestrators
❌ Problem (Azure Functions):
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var id = Guid.NewGuid(); // Non-deterministic!
}
❌ Problem (DTF):
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
var id = Guid.NewGuid(); // Non-deterministic!
}
✅ Solution (Azure Functions):
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var id = context.NewGuid(); // Deterministic
}
✅ Solution (DTF):
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
var id = context.NewGuid(); // Deterministic
}
DFA0003: Random without seed
Don't use Random without a deterministic seed in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var random = new Random(); // Non-deterministic seed!
var value = random.Next();
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var seed = context.CurrentUtcDateTime.GetHashCode();
var random = new Random(seed); // Deterministic seed
var value = random.Next();
}
DFA0004: I/O operations
Don't perform I/O operations directly in orchestrators
❌ Problem (both frameworks):
// This applies to both Azure Functions and DTF orchestrators
var content = File.ReadAllText("file.txt"); // I/O operation!
var httpResult = await httpClient.GetAsync("https://api.example.com"); // I/O operation!
✅ Solution (Azure Functions):
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var content = await context.CallActivityAsync<string>("ReadFileActivity", "file.txt");
var httpResult = await context.CallActivityAsync<string>("HttpCallActivity", "https://api.example.com");
}
✅ Solution (DTF):
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
var content = await context.ScheduleTask<string>(typeof(ReadFileActivity), "file.txt");
var httpResult = await context.ScheduleTask<string>(typeof(HttpCallActivity), "https://api.example.com");
}
DFA0005: Environment variables
Don't access environment variables in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var path = Environment.GetEnvironmentVariable("PATH"); // Non-deterministic!
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var path = await context.CallActivityAsync<string>("GetEnvironmentVariableActivity", "PATH"); // Use activity
}
DFA0006: Static mutable state
Don't access static mutable state in orchestrators
❌ Problem:
public static int Counter = 0;
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
Counter++; // Static mutable state!
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
await context.CallActivityAsync("IncrementCounterActivity", null); // Use activity for state changes
}
DFA0007: Thread blocking operations
Don't use Thread.Sleep or other blocking operations in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
Thread.Sleep(1000); // Blocking operation!
Task.Delay(1000).Wait(); // Blocking operation!
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(1), CancellationToken.None); // Durable timer
}
DFA0008: Non-durable async operations
Don't use non-durable async operations in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
await Task.Delay(1000); // Non-durable async!
await Task.Run(() => DoWork()); // Non-durable async!
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(1), CancellationToken.None); // Durable timer
await context.CallActivityAsync("DoWorkActivity", null); // Durable activity call
}
DFA0009: Threading APIs
Don't use threading APIs in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var thread = new Thread(() => DoWork()); // Threading API!
thread.Start();
await Task.Run(() => DoWork()); // Threading API!
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
await context.CallActivityAsync("DoWorkActivity", null); // Use activity for work
}
DFA0010: Non-durable bindings
Don't use non-durable bindings in orchestrators
❌ Problem:
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
[Blob("container/blob")] CloudBlockBlob blob) // Non-durable binding!
{
var content = await blob.DownloadTextAsync();
}
✅ Solution:
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var content = await context.CallActivityAsync<string>("ReadBlobActivity", "container/blob"); // Use activity
}
Rule Summary Table
Rule ID | Category | Severity | Title | Auto-Fix |
---|---|---|---|---|
DFA0001 | Time APIs | Warning | Don't use DateTime.Now, DateTime.UtcNow, or Stopwatch in orchestrators | ✅ |
DFA0002 | Random Generation | Warning | Don't use Guid.NewGuid() in orchestrators | ✅ |
DFA0003 | Random Generation | Warning | Don't use Random class without a deterministic seed in orchestrators | ❌ |
DFA0004 | I/O Operations | Warning | Don't use I/O operations directly in orchestrators | ❌ |
DFA0005 | Environment | Warning | Don't access environment variables in orchestrators | ❌ |
DFA0006 | Static State | Warning | Don't access static mutable state in orchestrators | ❌ |
DFA0007 | Threading | Warning | Don't use Thread.Sleep or other blocking operations in orchestrators | ✅ |
DFA0008 | Async Operations | Warning | Don't use non-durable async operations in orchestrators | ❌ |
DFA0009 | Threading APIs | Warning | Don't use threading APIs in orchestrators | ❌ |
DFA0010 | Bindings | Warning | Don't use non-durable input bindings in orchestrators | ❌ |
Configuration
Rule Severity Customization
Configure rule severities in .editorconfig
:
[*.cs]
# DTF Determinism Rules - Set severity levels
dotnet_diagnostic.DFA0001.severity = error # DateTime/Stopwatch APIs
dotnet_diagnostic.DFA0002.severity = warning # Guid.NewGuid()
dotnet_diagnostic.DFA0003.severity = suggestion # Random without seed
dotnet_diagnostic.DFA0004.severity = warning # I/O operations
dotnet_diagnostic.DFA0005.severity = warning # Environment variables
dotnet_diagnostic.DFA0006.severity = error # Static mutable state
dotnet_diagnostic.DFA0007.severity = warning # Thread blocking
dotnet_diagnostic.DFA0008.severity = suggestion # Non-durable async
dotnet_diagnostic.DFA0009.severity = warning # Threading APIs
dotnet_diagnostic.DFA0010.severity = error # Non-durable bindings
Global Suppression
To disable all DTF rules globally, add to GlobalSuppressions.cs
:
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("DTF", "DFA0001", Justification = "Reviewed")]
// Add more suppressions as needed
Code Fixes
The package includes automatic code fixes for common violations:
- DFA0001:
DateTime.Now
→context.CurrentUtcDateTime
,Stopwatch
→context.CreateTimer()
- DFA0002:
Guid.NewGuid()
→context.NewGuid()
- DFA0007:
Thread.Sleep()
→await context.CreateTimer()
Using Code Fixes
- Visual Studio: Click the lightbulb 💡 icon or press
Ctrl+.
- VS Code: Click the lightbulb 💡 icon or press
Ctrl+.
(Windows) /Cmd+.
(macOS) - Rider: Press
Alt+Enter
Troubleshooting
Common Issues
Q: The analyzer doesn't detect issues in my orchestrator A: Make sure your method has one of these attributes:
[FunctionName]
with[OrchestrationTrigger]
parameter[Function]
with[OrchestrationTrigger]
parameter
Q: I'm getting false positives outside of orchestrators A: The analyzer only triggers in orchestrator functions. If you're seeing issues in regular methods, check that your orchestrator detection is working correctly.
Q: Code fixes aren't showing up
A: Ensure the DtfDeterminismAnalyzer
package is installed with PrivateAssets="all"
and restart your IDE.
Q: How do I disable a specific rule?
A: Add to your .editorconfig
:
[*.cs]
dotnet_diagnostic.DFA0001.severity = none # Disable DateTime rule
Q: Can I suppress rules for a specific method?
A: Yes, use #pragma
directives or [SuppressMessage]
attributes:
#pragma warning disable DFA0001
var time = DateTime.Now; // This won't trigger the analyzer
#pragma warning restore DFA0001
Getting Help
- 📖 Documentation: View detailed docs
- 🐛 Report Issues: Create an issue
- 💬 Discussions: Join the conversation
Complete Examples
Basic Orchestrator
Here's a complete example showing the analyzer in action:
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;
public static class MyOrchestrator
{
[FunctionName("MyOrchestrator")]
public static async Task<string> RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
// ❌ This will trigger DFA0001
// var timestamp = DateTime.Now;
// ✅ Use this instead - auto-fixable!
var timestamp = context.CurrentUtcDateTime;
// ❌ This will trigger DFA0002
// var id = Guid.NewGuid();
// ✅ Use this instead - auto-fixable!
var id = context.NewGuid();
// ❌ This will trigger DFA0007
// Thread.Sleep(1000);
// ✅ Use this instead - auto-fixable!
await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(1), CancellationToken.None);
// Call activities for any I/O or external operations
var result = await context.CallActivityAsync<string>("ProcessDataActivity", new { timestamp, id });
return result;
}
[FunctionName("ProcessDataActivity")]
public static async Task<string> ProcessDataActivity([ActivityTrigger] object input)
{
// Activities can use any APIs - no restrictions
var httpResult = await new HttpClient().GetStringAsync("https://api.example.com");
var currentTime = DateTime.Now; // This is fine in activities
var newId = Guid.NewGuid(); // This is fine in activities
return httpResult;
}
}
Before and After Comparison
❌ Problematic Code:
[FunctionName("BadOrchestrator")]
public static async Task<string> BadOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var timestamp = DateTime.UtcNow; // DFA0001: Non-deterministic time
var id = Guid.NewGuid(); // DFA0002: Non-deterministic GUID
Thread.Sleep(5000); // DFA0007: Blocking operation
var random = new Random(); // DFA0003: Non-deterministic seed
var value = random.Next();
var config = Environment.GetEnvironmentVariable("CONFIG"); // DFA0005: Environment access
var file = File.ReadAllText("data.txt"); // DFA0004: I/O operation
await Task.Delay(1000); // DFA0008: Non-durable async
return $"{timestamp}-{id}-{value}-{config}-{file}";
}
✅ Fixed Code:
[FunctionName("GoodOrchestrator")]
public static async Task<string> GoodOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var timestamp = context.CurrentUtcDateTime; // Uses replay-safe time
var id = context.NewGuid(); // Uses deterministic GUID generation
// Use durable timer instead of Thread.Sleep
await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(5), CancellationToken.None);
// Use deterministic seed based on orchestration context
var seed = context.CurrentUtcDateTime.GetHashCode();
var random = new Random(seed);
var value = random.Next();
// Move environment and I/O operations to activities
var config = await context.CallActivityAsync<string>("GetConfigActivity", "CONFIG");
var file = await context.CallActivityAsync<string>("ReadFileActivity", "data.txt");
// Use durable timer instead of Task.Delay
await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(1), CancellationToken.None);
return $"{timestamp}-{id}-{value}-{config}-{file}";
}
[FunctionName("GetConfigActivity")]
public static string GetConfigActivity([ActivityTrigger] string key)
{
return Environment.GetEnvironmentVariable(key); // Activities can access environment
}
[FunctionName("ReadFileActivity")]
public static async Task<string> ReadFileActivity([ActivityTrigger] string filePath)
{
return await File.ReadAllTextAsync(filePath); // Activities can perform I/O
}
Sample Projects
The repository includes comprehensive sample projects demonstrating both supported frameworks:
🔵 Azure Durable Functions Sample (samples/DurableFunctionsSample/
)
- Complete Azure Functions project with Durable Functions
- Examples of problematic patterns and their corrections
- HTTP triggers for testing orchestrations
- Activity functions demonstrating proper I/O handling
- 40+ analyzer violations to learn from
🟢 Durable Task Framework Sample (samples/DurableTaskSample/
)
- Pure DTF implementation without Azure Functions
TaskOrchestration<TResult, TInput>
class examples- Direct DTF orchestration and activity patterns
- Console application demonstrating orchestration startup
- 6+ analyzer violations demonstrating core DTF patterns
Both samples are designed to show violations on purpose - they're educational tools for understanding what the analyzer catches and how to fix determinism issues.
Additional Resources
Documentation
For more information about DTF determinism constraints and best practices:
- Durable Functions Code Constraints - Official Microsoft documentation
- Durable Functions Overview - Introduction to Durable Functions
- Orchestrator Function Constraints - Understanding replay behavior
Related Tools
- Microsoft.Azure.WebJobs.Extensions.DurableTask - The Durable Functions runtime
- Azure Functions Core Tools - Local development tools
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Development Setup
- Clone the repository
- Open in Visual Studio 2022+ or VS Code
- Build the solution:
dotnet build
- Run tests:
dotnet test
Project Status
- ✅ CI/CD Pipeline: Automated builds and tests across multiple platforms
- ✅ Unit Tests: 170+ comprehensive test cases covering all analyzer rules
- ⏳ Code Coverage: Codecov integration planned for detailed coverage reporting
- ✅ Security: Dependabot and CodeQL scanning enabled
Note: Code coverage reporting via Codecov will be enabled soon. Currently configured as non-blocking to ensure smooth CI/CD operations.
Adding New Rules
To add a new determinism rule:
- Create a new analyzer in
src/DtfDeterminismAnalyzer/Analyzers/
- Add diagnostic descriptor in
DiagnosticDescriptors.cs
- Create corresponding tests in
tests/DtfDeterminismAnalyzer.Tests/
- Optionally add a code fix provider
Changelog
v1.0.0
- Initial release with 10 determinism rules
- Code fixes for DateTime, GUID, and Thread.Sleep violations
- Support for Azure Functions v3 and v4
License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ for the DTF community
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.0 | 160 | 9/30/2025 |
Initial release with 10 determinism rules and automatic code fixes for DateTime, GUID, and Thread.Sleep violations.