From b9b95fbced08e4be15ac7ce528a93b87e0ee945f Mon Sep 17 00:00:00 2001 From: "Taylor Southwick (from Dev Box)" Date: Thu, 7 Sep 2023 17:12:34 -0700 Subject: [PATCH] Expose middleware properties in activity/orchestrator The properties bag from DispatchMiddlewareContext contains information that might be useful from activities/orchestrators. However, they currently do not show up from those context options. This change plumbs the dictionary through and surfaces it for both activities and orchestrators. As part of this, a new interface IContextProperties is added that just has the properties dictionary. However, this allows for shared code to set and get properties by type or by name. --- .../PropertiesMiddlewareTests.cs | 147 +++++++++ .../RetryInterceptorTests.cs | 2 +- UpgradeLog.htm | 305 ++++++++++++++++++ .../ContextPropertiesExtensions.cs | 80 +++++ src/DurableTask.Core/IContextProperties.cs | 30 ++ .../Middleware/DispatchMiddlewareContext.cs | 13 +- src/DurableTask.Core/OrchestrationContext.cs | 5 +- src/DurableTask.Core/PropertiesDictionary.cs | 28 ++ .../TaskActivityDispatcher.cs | 4 +- src/DurableTask.Core/TaskContext.cs | 23 +- .../TaskOrchestrationContext.cs | 4 + .../TaskOrchestrationDispatcher.cs | 7 +- .../TaskOrchestrationExecutor.cs | 32 +- .../DispatcherMiddlewareTests.cs | 2 +- tools/DurableTask.props | 2 +- 15 files changed, 662 insertions(+), 22 deletions(-) create mode 100644 Test/DurableTask.Core.Tests/PropertiesMiddlewareTests.cs create mode 100644 UpgradeLog.htm create mode 100644 src/DurableTask.Core/ContextPropertiesExtensions.cs create mode 100644 src/DurableTask.Core/IContextProperties.cs create mode 100644 src/DurableTask.Core/PropertiesDictionary.cs diff --git a/Test/DurableTask.Core.Tests/PropertiesMiddlewareTests.cs b/Test/DurableTask.Core.Tests/PropertiesMiddlewareTests.cs new file mode 100644 index 000000000..a7b648250 --- /dev/null +++ b/Test/DurableTask.Core.Tests/PropertiesMiddlewareTests.cs @@ -0,0 +1,147 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- +#nullable enable +namespace DurableTask.Core.Tests +{ + using System; + using System.Diagnostics; + using System.Threading.Tasks; + using DurableTask.Emulator; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PropertiesMiddlewareTests + { + private const string PropertyKey = "Test"; + private const string PropertyValue = "Value"; + + TaskHubWorker worker = null!; + TaskHubClient client = null!; + + [TestInitialize] + public async Task Initialize() + { + var service = new LocalOrchestrationService(); + this.worker = new TaskHubWorker(service); + + await this.worker + .AddTaskOrchestrations(typeof(NoActivities), typeof(RunActivityOrchestrator)) + .AddTaskActivities(typeof(ReturnPropertyActivity)) + .StartAsync(); + + this.client = new TaskHubClient(service); + } + + [TestCleanup] + public async Task TestCleanup() + { + await this.worker.StopAsync(true); + } + + private sealed class NoActivities : TaskOrchestration + { + public override Task RunTask(OrchestrationContext context, string input) + { + return Task.FromResult(context.GetProperty(PropertyKey)!); + } + } + + private sealed class ReturnPropertyActivity : TaskActivity + { + protected override string Execute(TaskContext context, string input) + { + return context.GetProperty(PropertyKey)!; + } + } + + private sealed class RunActivityOrchestrator : TaskOrchestration + { + public override Task RunTask(OrchestrationContext context, string input) + { + return context.ScheduleTask(typeof(ReturnPropertyActivity)); + } + } + + [TestMethod] + public async Task OrchestrationGetsProperties() + { + this.worker.AddOrchestrationDispatcherMiddleware((context, next) => + { + context.SetProperty(PropertyKey, PropertyValue); + + return next(); + }); + + OrchestrationInstance instance = await this.client.CreateOrchestrationInstanceAsync(typeof(NoActivities), null); + + TimeSpan timeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 1000 : 10); + var state = await this.client.WaitForOrchestrationAsync(instance, timeout); + + Assert.AreEqual($"\"{PropertyValue}\"", state.Output); + } + + [TestMethod] + public async Task OrchestrationDoesNotGetPropertiesFromActivityMiddleware() + { + this.worker.AddActivityDispatcherMiddleware((context, next) => + { + context.SetProperty(PropertyKey, PropertyValue); + + return next(); + }); + + OrchestrationInstance instance = await this.client.CreateOrchestrationInstanceAsync(typeof(NoActivities), null); + + TimeSpan timeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 1000 : 10); + var state = await this.client.WaitForOrchestrationAsync(instance, timeout); + + Assert.IsNull(state.Output); + } + + [TestMethod] + public async Task ActivityGetsProperties() + { + this.worker.AddActivityDispatcherMiddleware((context, next) => + { + context.SetProperty(PropertyKey, PropertyValue); + + return next(); + }); + + OrchestrationInstance instance = await this.client.CreateOrchestrationInstanceAsync(typeof(RunActivityOrchestrator), null); + + TimeSpan timeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 1000 : 10); + var state = await this.client.WaitForOrchestrationAsync(instance, timeout); + + Assert.AreEqual($"\"{PropertyValue}\"", state.Output); + } + + [TestMethod] + public async Task ActivityDoesNotGetPropertiesFromOrchestratorMiddleware() + { + this.worker.AddOrchestrationDispatcherMiddleware((context, next) => + { + context.SetProperty(PropertyKey, PropertyValue); + + return next(); + }); + + OrchestrationInstance instance = await this.client.CreateOrchestrationInstanceAsync(typeof(RunActivityOrchestrator), null); + + TimeSpan timeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 1000 : 10); + var state = await this.client.WaitForOrchestrationAsync(instance, timeout); + + Assert.IsNull(state.Output); + } + } +} diff --git a/Test/DurableTask.Core.Tests/RetryInterceptorTests.cs b/Test/DurableTask.Core.Tests/RetryInterceptorTests.cs index a8a711724..1a6649817 100644 --- a/Test/DurableTask.Core.Tests/RetryInterceptorTests.cs +++ b/Test/DurableTask.Core.Tests/RetryInterceptorTests.cs @@ -89,7 +89,7 @@ sealed class MockOrchestrationContext : TaskOrchestrationContext readonly List delays = new List(); public MockOrchestrationContext(OrchestrationInstance orchestrationInstance, TaskScheduler taskScheduler) - : base(orchestrationInstance, taskScheduler) + : base(orchestrationInstance, new PropertiesDictionary(), taskScheduler) { CurrentUtcDateTime = DateTime.UtcNow; } diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 000000000..c2cf9800f --- /dev/null +++ b/UpgradeLog.htm @@ -0,0 +1,305 @@ + + + + Migration Report +

+ Migration Report -

Overview

ProjectPathErrorsWarningsMessages
TestApplicationtest\TestFabricApplication\TestApplication\TestApplication.sfproj100
.nuget.nuget000
ApplicationInsightsSamplesamples\DistributedTraceSample\ApplicationInsights\ApplicationInsightsSample.csproj000
Correlation.Samplessamples\Correlation.Samples\Correlation.Samples.csproj000
DistributedTraceSampleDistributedTraceSample000
DurableTask.ApplicationInsightssrc\DurableTask.ApplicationInsights\DurableTask.ApplicationInsights.csproj000
DurableTask.AzureServiceFabricsrc\DurableTask.AzureServiceFabric\DurableTask.AzureServiceFabric.csproj000
DurableTask.AzureServiceFabric.Integration.Teststest\DurableTask.AzureServiceFabric.Integration.Tests\DurableTask.AzureServiceFabric.Integration.Tests.csproj000
DurableTask.AzureServiceFabric.Teststest\DurableTask.AzureServiceFabric.Tests\DurableTask.AzureServiceFabric.Tests.csproj000
DurableTask.AzureStoragesrc\DurableTask.AzureStorage\DurableTask.AzureStorage.csproj000
DurableTask.AzureStorage.Teststest\DurableTask.AzureStorage.Tests\DurableTask.AzureStorage.Tests.csproj000
DurableTask.Coresrc\DurableTask.Core\DurableTask.Core.csproj000
DurableTask.Core.Teststest\DurableTask.Core.Tests\DurableTask.Core.Tests.csproj000
DurableTask.Emulatorsrc\DurableTask.Emulator\DurableTask.Emulator.csproj000
DurableTask.Emulator.Teststest\DurableTask.Emulator.Tests\DurableTask.Emulator.Tests.csproj000
DurableTask.Redissrc\DurableTask.Redis\DurableTask.Redis.csproj000
DurableTask.Redis.Teststest\DurableTask.Redis.Tests\DurableTask.Redis.Tests.csproj000
DurableTask.Samplessamples\DurableTask.Samples\DurableTask.Samples.csproj000
DurableTask.ServiceBussrc\DurableTask.ServiceBus\DurableTask.ServiceBus.csproj000
DurableTask.ServiceBus.Teststest\DurableTask.ServiceBus.Tests\DurableTask.ServiceBus.Tests.csproj000
DurableTask.SqlServersrc\DurableTask.SqlServer\DurableTask.SqlServer.csproj000
DurableTask.SqlServer.Teststest\DurableTask.SqlServer.Tests\DurableTask.SqlServer.Tests.csproj000
DurableTask.Stress.Teststest\DurableTask.Stress.Tests\DurableTask.Stress.Tests.csproj000
DurableTask.Test.Orchestrationstest\DurableTask.Test.Orchestrations\DurableTask.Test.Orchestrations.csproj000
OpenTelemetrySamplesamples\DistributedTraceSample\OpenTelemetry\OpenTelemetrySample.csproj000
samplessamples000
Solution ItemsSolution Items000
srcsrc000
testtest000
TestApplication.Commontest\TestFabricApplication\TestApplication.Common\TestApplication.Common.csproj000
TestApplication.StatefulServicetest\TestFabricApplication\TestApplication.StatefulService\TestApplication.StatefulService.csproj000
TestFabricApplicationTestFabricApplication000
toolstools000
SolutionDurableTask.sln001

Solution and projects

TestApplication

Message
test\TestFabricApplication\TestApplication\TestApplication.sfproj: + The application which this project type is based on was not found. Please try this link for further information: a07b5eb6-e848-4116-a8d0-a826331d98c6

.nuget

Message
.nuget logged no messages. +

ApplicationInsightsSample

Message
ApplicationInsightsSample logged no messages. +

Correlation.Samples

Message
Correlation.Samples logged no messages. +

DistributedTraceSample

Message
DistributedTraceSample logged no messages. +

DurableTask.ApplicationInsights

Message
DurableTask.ApplicationInsights logged no messages. +

DurableTask.AzureServiceFabric

Message
DurableTask.AzureServiceFabric logged no messages. +

DurableTask.AzureServiceFabric.Integration.Tests

Message
DurableTask.AzureServiceFabric.Integration.Tests logged no messages. +

DurableTask.AzureServiceFabric.Tests

Message
DurableTask.AzureServiceFabric.Tests logged no messages. +

DurableTask.AzureStorage

Message
DurableTask.AzureStorage logged no messages. +

DurableTask.AzureStorage.Tests

Message
DurableTask.AzureStorage.Tests logged no messages. +

DurableTask.Core

Message
DurableTask.Core logged no messages. +

DurableTask.Core.Tests

Message
DurableTask.Core.Tests logged no messages. +

DurableTask.Emulator

Message
DurableTask.Emulator logged no messages. +

DurableTask.Emulator.Tests

Message
DurableTask.Emulator.Tests logged no messages. +

DurableTask.Redis

Message
DurableTask.Redis logged no messages. +

DurableTask.Redis.Tests

Message
DurableTask.Redis.Tests logged no messages. +

DurableTask.Samples

Message
DurableTask.Samples logged no messages. +

DurableTask.ServiceBus

Message
DurableTask.ServiceBus logged no messages. +

DurableTask.ServiceBus.Tests

Message
DurableTask.ServiceBus.Tests logged no messages. +

DurableTask.SqlServer

Message
DurableTask.SqlServer logged no messages. +

DurableTask.SqlServer.Tests

Message
DurableTask.SqlServer.Tests logged no messages. +

DurableTask.Stress.Tests

Message
DurableTask.Stress.Tests logged no messages. +

DurableTask.Test.Orchestrations

Message
DurableTask.Test.Orchestrations logged no messages. +

OpenTelemetrySample

Message
OpenTelemetrySample logged no messages. +

samples

Message
samples logged no messages. +

Solution Items

Message
Solution Items logged no messages. +

src

Message
src logged no messages. +

test

Message
test logged no messages. +

TestApplication.Common

Message
TestApplication.Common logged no messages. +

TestApplication.StatefulService

Message
TestApplication.StatefulService logged no messages. +

TestFabricApplication

Message
TestFabricApplication logged no messages. +

tools

Message
tools logged no messages. +

Solution

Message
+ Show 1 additional messages +
DurableTask.sln: + The solution file does not require migration.
+ Hide 1 additional messages +
\ No newline at end of file diff --git a/src/DurableTask.Core/ContextPropertiesExtensions.cs b/src/DurableTask.Core/ContextPropertiesExtensions.cs new file mode 100644 index 000000000..f2d57da32 --- /dev/null +++ b/src/DurableTask.Core/ContextPropertiesExtensions.cs @@ -0,0 +1,80 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace DurableTask.Core +{ + /// + /// Extension methods that help get properties from . + /// + public static class ContextPropertiesExtensions + { + /// + /// Sets a property value to the context using the full name of the type as the key. + /// + /// The type of the property. + /// Properties to set property for. + /// The value of the property. + public static void SetProperty(this IContextProperties properties, T? value) => properties.SetProperty(typeof(T).FullName, value); + + /// + /// Sets a named property value to the context. + /// + /// The type of the property. + /// Properties to set property for. + /// The name of the property. + /// The value of the property. + public static void SetProperty(this IContextProperties properties, string key, T? value) + { + if (value is null) + { + properties.Properties.Remove(key); + } + else + { + properties.Properties[key] = value; + } + } + + /// + /// Gets a property value from the context using the full name of . + /// + /// The type of the property. + /// Properties to get property from. + /// The value of the property or default(T) if the property is not defined. + public static T? GetProperty(this IContextProperties properties) => properties.GetProperty(typeof(T).FullName); + + internal static T GetRequiredProperty(this IContextProperties properties) + => properties.GetProperty() ?? throw new InvalidOperationException($"Could not find property for {typeof(T).FullName}"); + + /// + /// Gets a named property value from the context. + /// + /// + /// Properties to get property from. + /// The name of the property value. + /// The value of the property or default(T) if the property is not defined. + public static T? GetProperty(this IContextProperties properties, string key) => properties.Properties.TryGetValue(key, out object value) ? (T)value : default; + + /// + /// Gets the tags from the current properties. + /// + /// + /// + public static IDictionary GetTags(this IContextProperties properties) => properties.GetRequiredProperty().OrchestrationTags; + } +} \ No newline at end of file diff --git a/src/DurableTask.Core/IContextProperties.cs b/src/DurableTask.Core/IContextProperties.cs new file mode 100644 index 000000000..69ee6c2f5 --- /dev/null +++ b/src/DurableTask.Core/IContextProperties.cs @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.Collections.Generic; + +#nullable enable + +namespace DurableTask.Core +{ + /// + /// Collection of properties for context objects to store arbitrary state. + /// + public interface IContextProperties + { + /// + /// Gets the properties of the current instance + /// + IDictionary Properties { get; } + } +} \ No newline at end of file diff --git a/src/DurableTask.Core/Middleware/DispatchMiddlewareContext.cs b/src/DurableTask.Core/Middleware/DispatchMiddlewareContext.cs index 256d3e7dc..399915175 100644 --- a/src/DurableTask.Core/Middleware/DispatchMiddlewareContext.cs +++ b/src/DurableTask.Core/Middleware/DispatchMiddlewareContext.cs @@ -13,13 +13,12 @@ namespace DurableTask.Core.Middleware { - using System; using System.Collections.Generic; /// /// Context data that can be used to share data between middleware. /// - public class DispatchMiddlewareContext + public class DispatchMiddlewareContext : IContextProperties { /// /// Sets a property value to the context using the full name of the type as the key. @@ -28,7 +27,7 @@ public class DispatchMiddlewareContext /// The value of the property. public void SetProperty(T value) { - SetProperty(typeof(T).FullName, value); + ContextPropertiesExtensions.SetProperty(this, value); } /// @@ -39,7 +38,7 @@ public void SetProperty(T value) /// The value of the property. public void SetProperty(string key, T value) { - Properties[key] = value; + ContextPropertiesExtensions.SetProperty(this, key, value); } /// @@ -49,7 +48,7 @@ public void SetProperty(string key, T value) /// The value of the property or default(T) if the property is not defined. public T GetProperty() { - return GetProperty(typeof(T).FullName); + return ContextPropertiesExtensions.GetProperty(this); } /// @@ -60,12 +59,12 @@ public T GetProperty() /// The value of the property or default(T) if the property is not defined. public T GetProperty(string key) { - return Properties.TryGetValue(key, out object value) ? (T)value : default(T); + return ContextPropertiesExtensions.GetProperty(this, key); } /// /// Gets a key/value collection that can be used to share data between middleware. /// - public IDictionary Properties { get; } = new Dictionary(StringComparer.Ordinal); + public IDictionary Properties { get; } = new PropertiesDictionary(); } } diff --git a/src/DurableTask.Core/OrchestrationContext.cs b/src/DurableTask.Core/OrchestrationContext.cs index 52238bbc2..aa7d8bbb1 100644 --- a/src/DurableTask.Core/OrchestrationContext.cs +++ b/src/DurableTask.Core/OrchestrationContext.cs @@ -23,13 +23,16 @@ namespace DurableTask.Core /// /// Context for an orchestration containing the instance, replay status, orchestration methods and proxy methods /// - public abstract class OrchestrationContext + public abstract class OrchestrationContext : IContextProperties { /// /// Used in generating proxy interfaces and classes. /// private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator(); + /// + public virtual IDictionary Properties { get; } = new Dictionary(); + /// /// Thread-static variable used to signal whether the calling thread is the orchestrator thread. /// The primary use case is for detecting illegal async usage in orchestration code. diff --git a/src/DurableTask.Core/PropertiesDictionary.cs b/src/DurableTask.Core/PropertiesDictionary.cs new file mode 100644 index 000000000..1b13792b6 --- /dev/null +++ b/src/DurableTask.Core/PropertiesDictionary.cs @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace DurableTask.Core +{ + using System; + using System.Collections.Generic; + + internal sealed class PropertiesDictionary : Dictionary, IContextProperties + { + public PropertiesDictionary() + : base(StringComparer.Ordinal) + { + } + + IDictionary IContextProperties.Properties => this; + } +} diff --git a/src/DurableTask.Core/TaskActivityDispatcher.cs b/src/DurableTask.Core/TaskActivityDispatcher.cs index 1c0b8464f..f31b0f243 100644 --- a/src/DurableTask.Core/TaskActivityDispatcher.cs +++ b/src/DurableTask.Core/TaskActivityDispatcher.cs @@ -174,7 +174,7 @@ async Task OnProcessWorkItemAsync(TaskActivityWorkItem workItem) ActivityExecutionResult? result; try { - await this.dispatchPipeline.RunAsync(dispatchContext, async _ => + await this.dispatchPipeline.RunAsync(dispatchContext, async dispatchContext => { if (taskActivity == null) { @@ -185,7 +185,7 @@ await this.dispatchPipeline.RunAsync(dispatchContext, async _ => throw new TypeMissingException($"TaskActivity {scheduledEvent.Name} version {scheduledEvent.Version} was not found"); } - var context = new TaskContext(taskMessage.OrchestrationInstance); + var context = new TaskContext(taskMessage.OrchestrationInstance, dispatchContext.Properties); context.ErrorPropagationMode = this.errorPropagationMode; HistoryEvent? responseEvent; diff --git a/src/DurableTask.Core/TaskContext.cs b/src/DurableTask.Core/TaskContext.cs index f959ef12c..2fd09016f 100644 --- a/src/DurableTask.Core/TaskContext.cs +++ b/src/DurableTask.Core/TaskContext.cs @@ -11,20 +11,34 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using System.Collections.Generic; + namespace DurableTask.Core { /// /// Task context /// - public class TaskContext + public class TaskContext : IContextProperties { /// - /// Creates a new TaskContext with the supplied OrchestrationInstance + /// Creates a new with the supplied /// /// public TaskContext(OrchestrationInstance orchestrationInstance) { OrchestrationInstance = orchestrationInstance; + Properties = new PropertiesDictionary(); + } + + /// + /// Creates a new with the supplied and properties + /// + /// + /// + public TaskContext(OrchestrationInstance orchestrationInstance, IDictionary properties) + { + OrchestrationInstance = orchestrationInstance; + Properties = properties; } /// @@ -32,6 +46,11 @@ public TaskContext(OrchestrationInstance orchestrationInstance) /// public OrchestrationInstance OrchestrationInstance { get; private set; } + /// + /// Gets the properties of the current instance + /// + public IDictionary Properties { get; } + /// /// Gets or sets a value indicating how to propagate unhandled exception metadata. /// diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index a9831ff47..d4a6d101a 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -44,8 +44,11 @@ public void AddEventToNextIteration(HistoryEvent he) continueAsNew.CarryoverEvents.Add(he); } + public override IDictionary Properties { get; } + public TaskOrchestrationContext( OrchestrationInstance orchestrationInstance, + IContextProperties properties, TaskScheduler taskScheduler, ErrorPropagationMode errorPropagationMode = ErrorPropagationMode.SerializeExceptions) { @@ -60,6 +63,7 @@ public TaskOrchestrationContext( IsReplaying = false; ErrorPropagationMode = errorPropagationMode; this.eventsWhileSuspended = new Queue(); + Properties = properties.Properties; } public IEnumerable OrchestratorActions => this.orchestratorActionsMap.Values; diff --git a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs index 6ebeaf89b..7e52fd3b2 100644 --- a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs +++ b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs @@ -669,7 +669,7 @@ async Task ExecuteOrchestrationAsync(Orchestration TaskOrchestrationExecutor? executor = null; - await this.dispatchPipeline.RunAsync(dispatchContext, _ => + await this.dispatchPipeline.RunAsync(dispatchContext, dispatchContext => { // Check to see if the custom middleware intercepted and substituted the orchestration execution // with its own execution behavior, providing us with the end results. If so, we can terminate @@ -680,7 +680,7 @@ await this.dispatchPipeline.RunAsync(dispatchContext, _ => return CompletedTask; } - if (taskOrchestration == null) + if (dispatchContext.GetProperty() is null) { throw TraceHelper.TraceExceptionInstance( TraceEventType.Error, @@ -690,8 +690,7 @@ await this.dispatchPipeline.RunAsync(dispatchContext, _ => } executor = new TaskOrchestrationExecutor( - runtimeState, - taskOrchestration, + dispatchContext, this.orchestrationService.EventBehaviourForContinueAsNew, this.errorPropagationMode); OrchestratorExecutionResult resultFromOrchestrator = executor.Execute(); diff --git a/src/DurableTask.Core/TaskOrchestrationExecutor.cs b/src/DurableTask.Core/TaskOrchestrationExecutor.cs index b0ca99976..3cff3ea49 100644 --- a/src/DurableTask.Core/TaskOrchestrationExecutor.cs +++ b/src/DurableTask.Core/TaskOrchestrationExecutor.cs @@ -24,6 +24,7 @@ namespace DurableTask.Core using DurableTask.Core.Common; using DurableTask.Core.Exceptions; using DurableTask.Core.History; + using DurableTask.Core.Middleware; /// /// Utility for executing task orchestrators. @@ -49,14 +50,39 @@ public TaskOrchestrationExecutor( TaskOrchestration taskOrchestration, BehaviorOnContinueAsNew eventBehaviourForContinueAsNew, ErrorPropagationMode errorPropagationMode = ErrorPropagationMode.SerializeExceptions) + : this(CreateProperties(orchestrationRuntimeState, taskOrchestration), eventBehaviourForContinueAsNew, errorPropagationMode) { + } + + private static IContextProperties CreateProperties(OrchestrationRuntimeState orchestrationRuntimeState, TaskOrchestration taskOrchestration) + { + var properties = new PropertiesDictionary(); + + properties.SetProperty(orchestrationRuntimeState); + properties.SetProperty(taskOrchestration); + + return properties; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TaskOrchestrationExecutor( + IContextProperties properties, + BehaviorOnContinueAsNew eventBehaviourForContinueAsNew, + ErrorPropagationMode errorPropagationMode = ErrorPropagationMode.SerializeExceptions) + { + this.orchestrationRuntimeState = properties.GetRequiredProperty(); + this.taskOrchestration = properties.GetRequiredProperty(); this.decisionScheduler = new SynchronousTaskScheduler(); this.context = new TaskOrchestrationContext( orchestrationRuntimeState.OrchestrationInstance, + properties, this.decisionScheduler, errorPropagationMode); - this.orchestrationRuntimeState = orchestrationRuntimeState; - this.taskOrchestration = taskOrchestration; this.skipCarryOverEvents = eventBehaviourForContinueAsNew == BehaviorOnContinueAsNew.Ignore; } @@ -144,7 +170,7 @@ void ProcessEvents(IEnumerable events) // Let this exception propagate out to be handled by the dispatcher ExceptionDispatchInfo.Capture(exception).Throw(); } - + this.context.FailOrchestration(exception); } else diff --git a/test/DurableTask.Core.Tests/DispatcherMiddlewareTests.cs b/test/DurableTask.Core.Tests/DispatcherMiddlewareTests.cs index ad89efc92..5f44ca599 100644 --- a/test/DurableTask.Core.Tests/DispatcherMiddlewareTests.cs +++ b/test/DurableTask.Core.Tests/DispatcherMiddlewareTests.cs @@ -250,7 +250,7 @@ public void EnsureOrchestrationExecutionContextSupportsDataContractSerialization [TestMethod] public async Task EnsureSubOrchestrationDispatcherMiddlewareHasAccessToRuntimeState() { - ConcurrentBag capturedContexts = new ConcurrentBag(); + ConcurrentBag capturedContexts = new ConcurrentBag(); for (var i = 0; i < 10; i++) { diff --git a/tools/DurableTask.props b/tools/DurableTask.props index 000586444..90883d5d4 100644 --- a/tools/DurableTask.props +++ b/tools/DurableTask.props @@ -8,7 +8,7 @@ 9.0 NU5125,NU5048 - True + false true true