Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Build stateful apps with Durable Functions. It's an extension of Azure Functions. Use an orchestrator function to coordinate other Durable Functions in your function app. Orchestrator functions are stateful, reliable, and they're built to run for a long time.
Build stateful, fault-tolerant workflows with the Durable Task SDKs in .NET, Python, and Java. Use an orchestrator to coordinate activities and sub-orchestrations. Orchestrators are stateful, reliable, and they're built to run for a long time.
Orchestrator code constraints
Orchestrator functions use event sourcing to ensure reliable execution and to maintain local variable state. The replay behavior of orchestrator code creates constraints on the type of code you can write in an orchestrator function. For example, orchestrator functions must be deterministic: an orchestrator function replays multiple times, and it must produce the same result each time.
Orchestrators use event sourcing to ensure reliable execution and to maintain local variable state. The replay behavior of orchestrator code creates constraints on the type of code you can write in an orchestrator. For example, orchestrators must be deterministic: an orchestrator replays multiple times, and it must produce the same result each time.
Use deterministic APIs
Here are some simple guidelines to help ensure your code is deterministic.
Call APIs from your target languages in orchestrator functions, but use only deterministic APIs. A deterministic API always returns the same value for the same input, no matter when or how often it's called.
The following sections provide guidance on APIs and patterns you should avoid because they're not deterministic. These restrictions apply only to orchestrator functions. Other function types don't have such restrictions.
Here are some simple guidelines to help ensure your code is deterministic.
Call APIs from your target languages in orchestrators, but use only deterministic APIs. A deterministic API always returns the same value for the same input, no matter when or how often it's called.
The following sections provide guidance on APIs and patterns you should avoid because they're not deterministic. These restrictions apply only to orchestrators. Activities don't have such restrictions.
Note
This article covers common orchestrator code constraints, but it isn't comprehensive. Focus on whether an API is deterministic. With that mindset, you can usually tell which APIs are safe to use without referring to this list.
Dates and times
Time-based APIs are nondeterministic and should never be used in orchestrator functions. Each orchestrator function replay produces a different value. Instead, use the Durable Functions equivalent API for getting the current date or time, which remains consistent across replays.
Don't use DateTime.Now, DateTime.UtcNow, or equivalent APIs for getting the current time. Classes such as Stopwatch should also be avoided. For .NET in-process orchestrator functions, use the IDurableOrchestrationContext.CurrentUtcDateTime property to get the current time. For .NET isolated orchestrator functions, use the TaskOrchestrationContext.CurrentDateTimeUtc property to get the current time.
DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);
Time-based APIs are nondeterministic and should never be used in orchestrators. Each orchestrator replay produces a different value. Instead, use the Durable Task SDK equivalent API for getting the current date or time, which remains consistent across replays.
Don't use DateTime.Now, DateTime.UtcNow, or equivalent APIs for getting the current time. Classes such as Stopwatch should also be avoided. Use the TaskOrchestrationContext.CurrentUtcDateTime property to get the current time.
using Microsoft.DurableTask;
public class TimerExample : TaskOrchestrator<object?, TimeSpan>
{
public override async Task<TimeSpan> RunAsync(TaskOrchestrationContext context, object? input)
{
// Use context.CurrentUtcDateTime instead of DateTime.Now or DateTime.UtcNow
DateTime startTime = context.CurrentUtcDateTime;
// do some work
await context.CallActivityAsync("DoWork", null);
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);
return totalTime;
}
}
GUIDs and UUIDs
APIs that return a random GUID or UUID are nondeterministic because the generated value is different for each replay. Depending on your language, a built-in API for generating deterministic GUIDs or UUIDs might be available. Otherwise, use an activity function to return a randomly generated GUID or UUID.
Instead of APIs like Guid.NewGuid(), use the context object's NewGuid() API to generate a random GUID that's safe for orchestrator replay.
Guid randomGuid = context.NewGuid();
Note
GUIDs generated with orchestration context APIs are Type 5 UUIDs.
APIs that return a random GUID or UUID are nondeterministic because the generated value is different for each replay. Depending on your language, a built-in API for generating deterministic GUIDs or UUIDs might be available. Otherwise, use an activity to return a randomly generated GUID or UUID.
Instead of APIs like Guid.NewGuid(), use the context object's NewGuid() API to generate a random GUID that's safe for orchestrator replay.
using Microsoft.DurableTask;
public class GuidExample : TaskOrchestrator<object?, Guid>
{
public override async Task<Guid> RunAsync(TaskOrchestrationContext context, object? input)
{
// Use context.NewGuid() instead of Guid.NewGuid()
Guid randomGuid = context.NewGuid();
return randomGuid;
}
}
Note
GUIDs generated with orchestration context APIs are Type 5 UUIDs.
Random numbers
Use an activity function to return random numbers to an orchestrator function. The return values of activity functions are always safe for replay because they're saved into the orchestration history.
Alternatively, you can use a random number generator with a fixed seed value directly in an orchestrator function. This approach is safe as long as the same sequence of numbers is generated for each orchestration replay.
Use an activity to return random numbers to an orchestrator. The return values of activities are always safe for replay because they're saved into the orchestration history.
Alternatively, you can use a random number generator with a fixed seed value directly in an orchestrator. This approach is safe as long as the same sequence of numbers is generated for each orchestration replay.
Bindings
Don't use bindings in an orchestrator function, including the orchestration client and entity client bindings. Use input and output bindings only in a client or activity function. Orchestrator functions can replay multiple times, causing nondeterministic and duplicate I/O with external systems.
Orchestrators shouldn't perform direct I/O operations with external systems. Move I/O operations to activities. Orchestrators can replay multiple times, causing nondeterministic and duplicate I/O with external systems.
Static variables
Static variables can change over time, making them unsafe for orchestrator functions. Avoid using static variables in orchestrator functions because their values can change over time, resulting in nondeterministic runtime behavior. Instead, use constants, or limit the use of static variables to activity functions.
Static variables can change over time, making them unsafe for orchestrators. Avoid using static variables in orchestrators because their values can change over time, resulting in nondeterministic runtime behavior. Instead, use constants, or limit the use of static variables to activities.
Note
Even outside of orchestrator functions, using static variables in Azure Functions can be problematic for various reasons since there's no guarantee that static state persists across multiple function executions. Avoid static variables except in specific use cases, like best effort in-memory caching in activity or entity functions.
Environment variables
Environment variables in orchestrator functions can change over time, resulting in nondeterministic runtime behavior. If an orchestrator function needs configuration defined in an environment variable, you must pass the configuration value into the orchestrator function as an input or as the return value of an activity function.
Environment variables in orchestrators can change over time, resulting in nondeterministic runtime behavior. If an orchestrator needs configuration defined in an environment variable, you must pass the configuration value into the orchestrator as an input or as the return value of an activity.
Network and HTTP
Use activity functions to make outbound network calls. If you need to make an HTTP call from your orchestrator function, you can also use the durable HTTP APIs.
Use activities to make outbound network calls. Orchestrators should never make direct HTTP calls or other network requests because these operations are nondeterministic.
Thread-blocking APIs
Blocking APIs like sleep can cause performance and scale problems for orchestrator functions and can result in unnecessary execution time charges in the Azure Functions Consumption plan. Use alternatives when they're available. For example, use Durable timers to create delays that are safe for replay and don't count toward orchestrator execution time.
Blocking APIs like "sleep" can cause performance and scale problems for orchestrators and should be avoided. Use durable timers to create delays that are safe for replay.
Use context.CreateTimer() instead of Task.Delay() or Thread.Sleep().
// Don't use Task.Delay() or Thread.Sleep()
// Use context.CreateTimer() instead
await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(5), CancellationToken.None);
Async APIs
Orchestrator code must never start any async operation, except operations defined by the orchestration trigger's context object. For example, never use Task.Run, Task.Delay, and HttpClient.SendAsync in .NET or setTimeout and setInterval in JavaScript. An orchestrator function should only schedule async work using Durable SDK APIs, like scheduling activity functions. Any other type of async invocations should be done inside activity functions.
Orchestrator code must never start any async operation, except operations defined by the orchestration context object. For example, never use Task.Run, Task.Delay, and HttpClient.SendAsync in .NET. An orchestrator should only schedule async work using Durable Task SDK APIs, like scheduling activities. Any other type of async invocations should be done inside activities.
Async JavaScript functions
Declare JavaScript orchestrator functions as synchronous generator functions. Don't declare JavaScript orchestrator functions as async because the Node.js runtime doesn't guarantee deterministic behavior for async functions.
Python coroutines
Don't declare Python orchestrator functions as coroutines. Don't use the async keyword because coroutine semantics don't align with the Durable Functions replay model. Declare Python orchestrator functions as generators, and use yield instead of await with the context API.
You must not declare Python orchestrators as coroutines. In other words, never declare Python orchestrators with the async keyword because coroutine semantics don't align with the Durable Task replay model. You must always declare Python orchestrators as generators, meaning that you should use yield instead of await when calling context APIs.
from durabletask import task
# CORRECT - use yield (generator function)
def my_orchestrator(ctx: task.OrchestrationContext, input: str):
result = yield ctx.call_activity(my_activity, input=input)
return result
# WRONG - don't use async/await
async def bad_orchestrator(ctx: task.OrchestrationContext, input: str):
result = await ctx.call_activity(my_activity, input=input) # This won't work!
return result
.NET threading APIs
The Durable Task Framework runs orchestrator code on a single thread and can't interact with any other threads. Running async continuations on a worker pool thread in an orchestration's execution can result in nondeterministic execution or deadlocks. For this reason, your orchestrator functions should almost never use threading APIs. For example, never use ConfigureAwait(continueOnCapturedContext: false) in an orchestrator function to ensure task continuations run on the orchestrator function's original SynchronizationContext.
Note
The Durable Task Framework attempts to detect accidental use of nonorchestrator threads in orchestrator functions. If it finds a violation, the framework throws a NonDeterministicOrchestrationException exception. However, this detection behavior won't catch all violations, and you shouldn't depend on it.
The Durable Task Framework runs orchestrator code on a single thread and can't interact with any other threads. Running async continuations on a worker pool thread in an orchestration's execution can result in nondeterministic execution or deadlocks. For this reason, your orchestrators should almost never use threading APIs. For example, never use ConfigureAwait(continueOnCapturedContext: false) in an orchestrator to ensure task continuations run on the orchestrator's original SynchronizationContext.
Note
The Durable Task Framework attempts to detect accidental use of nonorchestrator threads in orchestrators. If it finds a violation, the framework throws a NonDeterministicOrchestrationException exception. However, this detection behavior won't catch all violations, and you shouldn't depend on it.
Versioning
A durable orchestration can run for days, months, years, or even as an eternal orchestration. Code changes that affect running orchestrations can break replay behavior, so plan carefully before you update your app. For more information, see Versioning.
A durable orchestration can run for days, months, years, or even indefinitely. Code changes that affect running orchestrations can break replay behavior, so plan carefully before you update your app. Common versioning strategies include side by side deployment and using version specific task hub names.
Durable tasks
Note
This section describes internal implementation details of the Durable Task Framework. You don't need to know this information to use Durable Functions, but it helps explain the replay behavior.
Tasks that can safely wait in orchestrator functions are sometimes called durable tasks. The Durable Task Framework creates and manages these tasks. Examples include the tasks returned by CallActivityAsync, WaitForExternalEvent, and CreateTimer in .NET orchestrator functions.
A list of TaskCompletionSource objects in .NET manages these durable tasks internally. During replay, orchestrator code creates these tasks. The dispatcher completes them as it enumerates the corresponding history events.
The runtime executes the tasks synchronously on a single thread until it replays the history. If a durable task doesn't finish by the end of history replay, the runtime takes the appropriate actions. For example, the runtime can enqueue a message to call an activity function.
This runtime behavior explains why your orchestrator function can't use await or yield in a nondurable task. The dispatcher thread can't wait for the task to finish, and callbacks from that task can corrupt the orchestrator function's tracking state. The runtime includes checks to help detect these violations.
To learn more about how the Durable Task Framework executes orchestrator functions, see the Durable Task source code on GitHub. In particular, see TaskOrchestrationExecutor.cs and TaskOrchestrationContext.cs.
Tasks that can safely wait in orchestrators are sometimes called durable tasks. The Durable Task Framework creates and manages these tasks. Examples include the tasks returned by CallActivityAsync, WaitForExternalEvent, and CreateTimer in .NET orchestrators.
A list of TaskCompletionSource objects in .NET manages these durable tasks internally. During replay, orchestrator code creates these tasks. The dispatcher completes them as it enumerates the corresponding history events.
The runtime executes the tasks synchronously on a single thread until it replays the history. If a durable task doesn't finish by the end of history replay, the runtime takes the appropriate actions. For example, the runtime can enqueue a message to call an activity.
This runtime behavior explains why your orchestrator can't use await or yield in a nondurable task. The dispatcher thread can't wait for the task to finish, and callbacks from that task can corrupt the orchestrator's tracking state. The runtime includes checks to help detect these violations.
To learn more about how the Durable Task Framework executes orchestrators, see the Durable Task source code on GitHub. In particular, see TaskOrchestrationExecutor.cs and TaskOrchestrationContext.cs.