diff --git a/Directory.Packages.props b/Directory.Packages.props
index e5ba73485..b2b1ff2ed 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,9 +15,9 @@
-
-
-
+
+
+
@@ -29,10 +29,11 @@
-
-
+
+
+
@@ -67,7 +68,7 @@
-
+
diff --git a/src/WebJobs.Extensions.DurableTask/Bindings/OrchestrationTriggerAttributeBindingProvider.cs b/src/WebJobs.Extensions.DurableTask/Bindings/OrchestrationTriggerAttributeBindingProvider.cs
index 31ce35e89..82f45f406 100644
--- a/src/WebJobs.Extensions.DurableTask/Bindings/OrchestrationTriggerAttributeBindingProvider.cs
+++ b/src/WebJobs.Extensions.DurableTask/Bindings/OrchestrationTriggerAttributeBindingProvider.cs
@@ -168,7 +168,7 @@ public Task BindAsync(object? value, ValueBindingContext context)
var orchestratorRequest = new Microsoft.DurableTask.Protobuf.OrchestratorRequest()
{
InstanceId = remoteContext.InstanceId,
- PastEvents = { remoteContext.PastEvents.Select(ProtobufUtils.ToHistoryEventProto) },
+ PastEvents = { remoteContext.Configurations.IncludePastEvents ? remoteContext.PastEvents.Select(ProtobufUtils.ToHistoryEventProto) : Enumerable.Empty() },
NewEvents = { remoteContext.NewEvents.Select(ProtobufUtils.ToHistoryEventProto) },
EntityParameters = remoteContext.EntityParameters.ToProtobuf(),
};
diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/RemoteOrchestratorContext.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/RemoteOrchestratorContext.cs
index b7999e041..16150bdd8 100644
--- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/RemoteOrchestratorContext.cs
+++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/RemoteOrchestratorContext.cs
@@ -22,7 +22,7 @@ internal class RemoteOrchestratorContext
private Exception? failure;
- public RemoteOrchestratorContext(OrchestrationRuntimeState runtimeState, TaskOrchestrationEntityParameters? entityParameters, DurableTaskOptions options)
+ public RemoteOrchestratorContext(OrchestrationRuntimeState runtimeState, TaskOrchestrationEntityParameters? entityParameters, DurableTaskOptions options, bool isExtendedSession, bool includePastEvents)
{
this.runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState));
this.EntityParameters = entityParameters;
@@ -30,6 +30,12 @@ public RemoteOrchestratorContext(OrchestrationRuntimeState runtimeState, TaskOrc
{
HttpDefaultAsyncRequestSleepTimeMilliseconds = options.HttpSettings.DefaultAsyncRequestSleepTimeMilliseconds,
};
+ if (options.ExtendedSessionsEnabled)
+ {
+ this.Configurations.IsExtendedSession = isExtendedSession;
+ this.Configurations.IncludePastEvents = includePastEvents;
+ this.Configurations.ExtendedSessionIdleTimeoutInSeconds = options.ExtendedSessionIdleTimeoutInSeconds;
+ }
}
[JsonProperty("instanceId")]
diff --git a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto
index 3b9c4f408..df5143bc9 100644
--- a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto
+++ b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto
@@ -342,8 +342,10 @@ message OrchestratorResponse {
// The number of work item events that were processed by the orchestrator.
// This field is optional. If not set, the service should assume that the orchestrator processed all events.
google.protobuf.Int32Value numEventsProcessed = 5;
-
OrchestrationTraceContext orchestrationTraceContext = 6;
+
+ // Whether or not a history is required to complete the original OrchestratorRequest and none was provided.
+ bool requiresHistory = 7;
}
message CreateInstanceRequest {
@@ -678,6 +680,18 @@ message AbandonEntityTaskResponse {
// Empty.
}
+message SkipGracefulOrchestrationTerminationsRequest {
+ // A maximum of 500 instance IDs can be provided in this list.
+ repeated string instanceIds = 1;
+ google.protobuf.StringValue reason = 2;
+}
+
+message SkipGracefulOrchestrationTerminationsResponse {
+ // Those instances which could not be terminated because they had locked entities at the time of this termination call,
+ // are already in a terminal state (completed, failed, terminated, etc.), are not orchestrations, or do not exist (i.e. have been purged)
+ repeated string unterminatedInstanceIds = 1;
+}
+
service TaskHubSidecarService {
// Sends a hello request to the sidecar service.
rpc Hello(google.protobuf.Empty) returns (google.protobuf.Empty);
@@ -751,6 +765,10 @@ service TaskHubSidecarService {
// Abandon an entity work item
rpc AbandonTaskEntityWorkItem(AbandonEntityTaskRequest) returns (AbandonEntityTaskResponse);
+
+ // "Skip" graceful termination of orchestrations by immediately changing their status in storage to "terminated".
+ // Note that a maximum of 500 orchestrations can be terminated at a time using this method.
+ rpc SkipGracefulOrchestrationTerminations(SkipGracefulOrchestrationTerminationsRequest) returns (SkipGracefulOrchestrationTerminationsResponse);
}
message GetWorkItemsRequest {
diff --git a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt
index 1b0140b56..b4a20b9e3 100644
--- a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt
+++ b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt
@@ -1,2 +1,2 @@
-# The following files were downloaded from branch main at 2025-09-11 18:23:56 UTC
-https://raw.githubusercontent.com/microsoft/durabletask-protobuf/985035a0890575ae18be0eb2a3ac93c10824498a/protos/orchestrator_service.proto
+# The following files were downloaded from branch main at 2025-09-17 01:59:32 UTC
+https://raw.githubusercontent.com/microsoft/durabletask-protobuf/f5745e0d83f608d77871c1894d9260ceaae08967/protos/orchestrator_service.proto
diff --git a/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs b/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs
index 5c49899ed..8cd324988 100644
--- a/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs
+++ b/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs
@@ -358,10 +358,10 @@ internal void Validate(INameResolver environmentVariableResolver)
string runtimeLanguage = environmentVariableResolver.Resolve("FUNCTIONS_WORKER_RUNTIME");
if (this.ExtendedSessionsEnabled &&
runtimeLanguage != null && // If we don't know from the environment variable, don't assume customer isn't .NET
- !string.Equals(runtimeLanguage, "dotnet", StringComparison.OrdinalIgnoreCase))
+ !(string.Equals(runtimeLanguage, "dotnet", StringComparison.OrdinalIgnoreCase) || string.Equals(runtimeLanguage, "dotnet-isolated", StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException(
- "Durable Functions with extendedSessionsEnabled set to 'true' is only supported when using the in-process .NET worker. Please remove the setting or change it to 'false'." +
+ "Durable Functions with extendedSessionsEnabled set to 'true' is only supported when using the in-process or isolated .NET worker. Please remove the setting or change it to 'false'." +
"See https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-perf-and-scale#extended-sessions for more details.");
}
diff --git a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs
index f0898aa3a..0aeaa9440 100644
--- a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs
+++ b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs
@@ -110,7 +110,12 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync(
isReplay: false);
}
- var context = new RemoteOrchestratorContext(runtimeState, entityParameters, this.extension.Options);
+ WorkItemMetadata workItemMetadata = dispatchContext.GetProperty();
+ bool isExtendedSession = workItemMetadata.IsExtendedSession;
+ bool includePastEvents = workItemMetadata.IncludePastEvents;
+
+ var context = new RemoteOrchestratorContext(runtimeState, entityParameters, this.extension.Options, isExtendedSession, includePastEvents);
+ bool workerRequiresHistory = false;
var input = new TriggeredFunctionData
{
@@ -139,6 +144,8 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync(
byte[] triggerReturnValueBytes = Convert.FromBase64String(triggerReturnValue);
P.OrchestratorResponse response = P.OrchestratorResponse.Parser.ParseFrom(triggerReturnValueBytes);
+ workerRequiresHistory = response.RequiresHistory;
+
// TrySetResult may throw if a platform-level error is encountered (like an out of memory exception).
context.SetResult(
response.Actions.Select(ProtobufUtils.ToOrchestratorAction),
@@ -198,6 +205,11 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync(
OrchestratorExecutionResult orchestratorResult;
if (functionResult.Succeeded)
{
+ if (workerRequiresHistory)
+ {
+ throw new SessionAbortedException("The worker has since ended the extended session and needs an orchestration history to execute the orchestration request.");
+ }
+
orchestratorResult = context.GetResult();
if (context.OrchestratorCompleted)
diff --git a/src/WebJobs.Extensions.DurableTask/RemoteOrchestratorConfiguration.cs b/src/WebJobs.Extensions.DurableTask/RemoteOrchestratorConfiguration.cs
index 6636753a0..5b38f88ab 100644
--- a/src/WebJobs.Extensions.DurableTask/RemoteOrchestratorConfiguration.cs
+++ b/src/WebJobs.Extensions.DurableTask/RemoteOrchestratorConfiguration.cs
@@ -12,5 +12,22 @@ public class RemoteOrchestratorConfiguration
/// Gets or sets the default number of milliseconds between async HTTP status poll requests.
///
public int HttpDefaultAsyncRequestSleepTimeMilliseconds { get; set; } = 30000;
+
+ ///
+ /// Gets or sets whether or not to include the past history events in the orchestration request.
+ /// True by default.
+ ///
+ public bool IncludePastEvents { get; set; } = true;
+
+ ///
+ /// Gets or sets whether or not the orchestration request is within an extended session.
+ /// False by default.
+ ///
+ public bool IsExtendedSession { get; set; } = false;
+
+ ///
+ /// Gets or sets the amount of time in seconds before an idle extended session times out.
+ ///
+ public int ExtendedSessionIdleTimeoutInSeconds { get; set; }
}
}
diff --git a/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs b/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs
index be1919673..1ef56ff7b 100644
--- a/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs
+++ b/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Exceptions;
using Microsoft.Azure.Functions.Worker.Middleware;
+using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Grpc;
namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
@@ -13,14 +14,16 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
///
/// A middleware to handle orchestration triggers.
///
-internal class DurableTaskFunctionsMiddleware : IFunctionsWorkerMiddleware
+internal class DurableTaskFunctionsMiddleware(ExtendedSessionsCache extendedSessionsCache) : IFunctionsWorkerMiddleware
{
+ private readonly ExtendedSessionsCache extendedSessionsCache = extendedSessionsCache;
+
///
public Task Invoke(FunctionContext functionContext, FunctionExecutionDelegate next)
{
if (IsOrchestrationTrigger(functionContext, out BindingMetadata? triggerBinding))
{
- return RunOrchestrationAsync(functionContext, triggerBinding, next);
+ return this.RunOrchestrationAsync(functionContext, triggerBinding, next);
}
if (IsEntityTrigger(functionContext, out triggerBinding))
@@ -52,7 +55,7 @@ private static bool IsOrchestrationTrigger(
return false;
}
- static async Task RunOrchestrationAsync(
+ async Task RunOrchestrationAsync(
FunctionContext context, BindingMetadata triggerBinding, FunctionExecutionDelegate next)
{
InputBindingData