From 7fb7d033e103cd4dc7c484faa345c2212d1053fe Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Tue, 15 Jan 2019 13:38:57 -0800 Subject: [PATCH 01/12] * Add DurableTask.ServiceFabric project. * Add Integration Tests. * Integrate with DTFx controllers. --- .gitignore | 1 + DurableTask.sln | 114 ++++ .../App.config | 11 + .../AssemblySetup.cs | 62 ++ .../Constants.cs | 20 + .../DeploymentUtil/ApplicationInfoReader.cs | 108 ++++ .../DeploymentUtil/DeploymentHelper.cs | 106 ++++ ...ask.ServiceFabric.Integration.Tests.csproj | 34 ++ .../FunctionalTests.cs | 385 +++++++++++++ .../SimplePartitionProvider.cs | 37 ++ .../StressTests.cs | 256 +++++++++ .../Utilities.cs | 67 +++ .../WaitForOrchestrationTests.cs | 151 +++++ .../CountBasedFixedDelayRetryPolicyTests.cs | 67 +++ .../DurableTask.ServiceFabric.Tests.csproj | 25 + .../Measure.cs | 53 ++ .../OrchestrationInstanceComparerTests.cs | 85 +++ .../PersistentSessionTests.cs | 56 ++ .../SerializationPerformanceExperiments.cs | 65 +++ .../TaskMessageItemTests.cs | 42 ++ .../TimerFiredEventComparerTests.cs | 137 +++++ .../Performance/DriverOrchestration.cs | 39 ++ .../Performance/DriverOrchestrationData.cs | 28 + .../Performance/ExecutionCountingActivity.cs | 35 ++ .../ExecutionCountingOrchestration.cs | 34 ++ .../Performance/RandomTimeWaitingTask.cs | 47 ++ .../Performance/RandomTimeWaitingTaskInput.cs | 28 + .../Performance/TestOrchestration.cs | 93 +++ .../Performance/TestOrchestrationData.cs | 44 ++ .../Orchestrations/CounterException.cs | 42 ++ .../Orchestrations/ExceptionThrowingTask.cs | 32 ++ .../GenerationBasicOrchestration.cs | 50 ++ .../Orchestrations/GetUserTask.cs | 25 + .../Orchestrations/GreetUserTask.cs | 25 + .../OrchestrationRunningIntoRetry.cs | 63 +++ ...SimpleOrchestrationWithSubOrchestration.cs | 30 + .../SimpleOrchestrationWithTasks.cs | 30 + .../SimpleOrchestrationWithTimer.cs | 28 + .../TestApplication.Common.csproj | 18 + .../DefaultDependencyResolver.cs | 56 ++ .../IOwinAppBuilder.cs | 22 + .../OwinCommunicationListener.cs | 136 +++++ .../PackageRoot/Config/Settings.xml | 12 + .../PackageRoot/ServiceManifest.xml | 29 + .../Program.cs | 50 ++ .../ServiceEventSource.cs | 189 +++++++ .../Startup.cs | 58 ++ .../TestApplication.StatefulService.csproj | 27 + .../TestStatefulService.cs | 171 ++++++ .../ApplicationManifest.xml | 19 + .../ApplicationParameters/Cloud.xml | 8 + .../ApplicationParameters/Local.1Node.xml | 8 + .../ApplicationParameters/Local.5Node.xml | 8 + .../TestApplication/PublishProfiles/Cloud.xml | 25 + .../PublishProfiles/Local.1Node.xml | 11 + .../PublishProfiles/Local.5Node.xml | 11 + .../Scripts/Deploy-FabricApplication.ps1 | 258 +++++++++ .../TestApplication/TestApplication.sfproj | 47 ++ .../TestApplication/packages.config | 4 + .../History/ExecutionStartedEvent.cs | 7 + src/DurableTask.Core/History/HistoryEvent.cs | 12 +- src/DurableTask.Core/TaskMessage.cs | 6 +- src/DurableTask.ServiceFabric/AssemblyInfo.cs | 20 + .../AsyncManualResetEvent.cs | 59 ++ src/DurableTask.ServiceFabric/Constants.cs | 26 + .../DurableTask.ServiceFabric.csproj | 28 + .../ExceptionUtilities.cs | 40 ++ .../FabricOrchestrationInstanceStore.cs | 362 ++++++++++++ .../FabricOrchestrationProvider.cs | 103 ++++ .../FabricOrchestrationProviderFactory.cs | 60 ++ .../FabricOrchestrationProviderSettings.cs | 54 ++ .../FabricOrchestrationService.cs | 528 ++++++++++++++++++ .../FabricOrchestrationServiceClient.cs | 190 +++++++ .../FabricOrchestrationServiceController.cs | 168 ++++++ .../FabricProviderClient.cs | 55 ++ ...FabricOrchestrationServiceInstanceStore.cs | 87 +++ .../IFabricProviderClient.cs | 45 ++ .../IPartitionProvider.cs | 35 ++ src/DurableTask.ServiceFabric/Message.cs | 40 ++ .../CreateTaskOrchestrationParameters.cs | 33 ++ .../PurgeOrchestrationHistoryParameters.cs | 34 ++ .../OrchestrationInstanceComparer.cs | 43 ++ .../OrchestrationStatusExtensions.cs | 30 + .../PersistentSession.cs | 70 +++ src/DurableTask.ServiceFabric/README.md | 44 ++ .../RemoteOrchestrationServiceClient.cs | 225 ++++++++ .../Stores/ActivityProvider.cs | 68 +++ .../Stores/MessageProviderBase.cs | 190 +++++++ .../Stores/ScheduledMessageProvider.cs | 172 ++++++ .../Stores/SessionMessagesProvider.cs | 50 ++ .../Stores/SessionsProvider.cs | 356 ++++++++++++ .../CountBasedFixedDelayRetryPolicy.cs | 50 ++ .../TaskHelpers/RetryHelper.cs | 112 ++++ .../TaskHelpers/RetryPolicy.cs | 24 + .../TaskMessageItem.cs | 33 ++ .../TimerFiredEventComparer.cs | 57 ++ .../Tracing/ProviderEventSource.cs | 188 +++++++ src/DurableTask.ServiceFabric/Utils.cs | 88 +++ 98 files changed, 7510 insertions(+), 4 deletions(-) create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/App.config create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs create mode 100644 Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj create mode 100644 Test/DurableTask.ServiceFabric.Tests/Measure.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs create mode 100644 Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestration.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestrationData.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingActivity.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingOrchestration.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTask.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTaskInput.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/TestOrchestration.cs create mode 100644 Test/DurableTask.Test.Orchestrations/Performance/TestOrchestrationData.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/CounterException.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/ExceptionThrowingTask.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/GenerationBasicOrchestration.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/GetUserTask.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/GreetUserTask.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/OrchestrationRunningIntoRetry.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithSubOrchestration.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTasks.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTimer.cs create mode 100644 Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/Config/Settings.xml create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/Program.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs create mode 100644 Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml create mode 100644 Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml create mode 100644 Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.1Node.xml create mode 100644 Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml create mode 100644 Test/TestFabricApplication/TestApplication/PublishProfiles/Cloud.xml create mode 100644 Test/TestFabricApplication/TestApplication/PublishProfiles/Local.1Node.xml create mode 100644 Test/TestFabricApplication/TestApplication/PublishProfiles/Local.5Node.xml create mode 100644 Test/TestFabricApplication/TestApplication/Scripts/Deploy-FabricApplication.ps1 create mode 100644 Test/TestFabricApplication/TestApplication/TestApplication.sfproj create mode 100644 Test/TestFabricApplication/TestApplication/packages.config create mode 100644 src/DurableTask.ServiceFabric/AssemblyInfo.cs create mode 100644 src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs create mode 100644 src/DurableTask.ServiceFabric/Constants.cs create mode 100644 src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj create mode 100644 src/DurableTask.ServiceFabric/ExceptionUtilities.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationService.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs create mode 100644 src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs create mode 100644 src/DurableTask.ServiceFabric/FabricProviderClient.cs create mode 100644 src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs create mode 100644 src/DurableTask.ServiceFabric/IFabricProviderClient.cs create mode 100644 src/DurableTask.ServiceFabric/IPartitionProvider.cs create mode 100644 src/DurableTask.ServiceFabric/Message.cs create mode 100644 src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs create mode 100644 src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs create mode 100644 src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs create mode 100644 src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.cs create mode 100644 src/DurableTask.ServiceFabric/PersistentSession.cs create mode 100644 src/DurableTask.ServiceFabric/README.md create mode 100644 src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs create mode 100644 src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs create mode 100644 src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs create mode 100644 src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs create mode 100644 src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs create mode 100644 src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs create mode 100644 src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs create mode 100644 src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs create mode 100644 src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs create mode 100644 src/DurableTask.ServiceFabric/TaskMessageItem.cs create mode 100644 src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs create mode 100644 src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs create mode 100644 src/DurableTask.ServiceFabric/Utils.cs diff --git a/.gitignore b/.gitignore index 16efb5f9b..be686e726 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.user bin/ obj/ +pkg/ packages/ TestResults/ diff --git a/DurableTask.sln b/DurableTask.sln index 33ff4e70f..4e91a5226 100644 --- a/DurableTask.sln +++ b/DurableTask.sln @@ -44,55 +44,112 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis", "src\DurableTask.Redis\DurableTask.Redis.csproj", "{BDA5B320-5282-49FE-B0A7-1F8A0F06862C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis.Tests", "Test\DurableTask.Redis.Tests\DurableTask.Redis.Tests.csproj", "{B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric", "src\DurableTask.ServiceFabric\DurableTask.ServiceFabric.csproj", "{3C10169F-4ACB-4FF6-A12B-B74569613E81}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric.Tests", "Test\DurableTask.ServiceFabric.Tests\DurableTask.ServiceFabric.Tests.csproj", "{AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric.Integration.Tests", "Test\DurableTask.ServiceFabric.Integration.Tests\DurableTask.ServiceFabric.Integration.Tests.csproj", "{8C3D4CA2-4158-4631-BD5E-F5D066962FAF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFabricApplication", "TestFabricApplication", "{C53918E6-667A-4930-837C-0018C5D6E374}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.Common", "Test\TestFabricApplication\TestApplication.Common\TestApplication.Common.csproj", "{99EB4BD2-72E4-4471-8E8D-9454BEACFA35}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.StatefulService", "Test\TestFabricApplication\TestApplication.StatefulService\TestApplication.StatefulService.csproj", "{88A231A9-940D-43E4-B7CD-B46BA9375367}" +EndProject +Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "TestApplication", "Test\TestFabricApplication\TestApplication\TestApplication.sfproj", "{1D52F94E-933C-411F-96C4-4960B423586F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Debug|x64.Build.0 = Debug|Any CPU {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Release|Any CPU.Build.0 = Release|Any CPU + {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Release|x64.ActiveCfg = Release|Any CPU + {6F5D2EAD-726D-4FE5-A575-AEB96D1CCE37}.Release|x64.Build.0 = Release|Any CPU {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Debug|x64.ActiveCfg = Debug|Any CPU + {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Debug|x64.Build.0 = Debug|Any CPU {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Release|Any CPU.ActiveCfg = Release|Any CPU {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Release|Any CPU.Build.0 = Release|Any CPU + {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Release|x64.ActiveCfg = Release|Any CPU + {6639DA74-9063-4A7C-9484-0A71C4FB5C18}.Release|x64.Build.0 = Release|Any CPU {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Debug|x64.ActiveCfg = Debug|Any CPU + {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Debug|x64.Build.0 = Debug|Any CPU {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Release|Any CPU.ActiveCfg = Release|Any CPU {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Release|Any CPU.Build.0 = Release|Any CPU + {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Release|x64.ActiveCfg = Release|Any CPU + {879670A7-919B-4AF9-8AFB-7C4EDAA0CC04}.Release|x64.Build.0 = Release|Any CPU {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Debug|x64.ActiveCfg = Debug|Any CPU + {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Debug|x64.Build.0 = Debug|Any CPU {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Release|Any CPU.Build.0 = Release|Any CPU + {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Release|x64.ActiveCfg = Release|Any CPU + {BC204374-927C-4A48-8473-F5BF8C97EAEA}.Release|x64.Build.0 = Release|Any CPU {96980359-8003-4500-B97B-949A7FE01F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96980359-8003-4500-B97B-949A7FE01F31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96980359-8003-4500-B97B-949A7FE01F31}.Debug|x64.ActiveCfg = Debug|Any CPU + {96980359-8003-4500-B97B-949A7FE01F31}.Debug|x64.Build.0 = Debug|Any CPU {96980359-8003-4500-B97B-949A7FE01F31}.Release|Any CPU.ActiveCfg = Release|Any CPU {96980359-8003-4500-B97B-949A7FE01F31}.Release|Any CPU.Build.0 = Release|Any CPU + {96980359-8003-4500-B97B-949A7FE01F31}.Release|x64.ActiveCfg = Release|Any CPU + {96980359-8003-4500-B97B-949A7FE01F31}.Release|x64.Build.0 = Release|Any CPU {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|x64.ActiveCfg = Debug|Any CPU + {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|x64.Build.0 = Debug|Any CPU {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|Any CPU.Build.0 = Release|Any CPU + {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|x64.ActiveCfg = Release|Any CPU + {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|x64.Build.0 = Release|Any CPU {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Debug|x64.ActiveCfg = Debug|Any CPU + {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Debug|x64.Build.0 = Debug|Any CPU {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Release|Any CPU.Build.0 = Release|Any CPU + {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Release|x64.ActiveCfg = Release|Any CPU + {C8634F14-BE3B-4685-B0E1-C5726F8F3758}.Release|x64.Build.0 = Release|Any CPU {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Debug|x64.Build.0 = Debug|Any CPU {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Release|Any CPU.Build.0 = Release|Any CPU + {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Release|x64.ActiveCfg = Release|Any CPU + {ACC997BF-D541-4BAF-A91C-7EC0B65426A9}.Release|x64.Build.0 = Release|Any CPU {295A38D6-F8BF-446A-A274-E78BD1C56514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {295A38D6-F8BF-446A-A274-E78BD1C56514}.Debug|Any CPU.Build.0 = Debug|Any CPU + {295A38D6-F8BF-446A-A274-E78BD1C56514}.Debug|x64.ActiveCfg = Debug|Any CPU + {295A38D6-F8BF-446A-A274-E78BD1C56514}.Debug|x64.Build.0 = Debug|Any CPU {295A38D6-F8BF-446A-A274-E78BD1C56514}.Release|Any CPU.ActiveCfg = Release|Any CPU {295A38D6-F8BF-446A-A274-E78BD1C56514}.Release|Any CPU.Build.0 = Release|Any CPU + {295A38D6-F8BF-446A-A274-E78BD1C56514}.Release|x64.ActiveCfg = Release|Any CPU + {295A38D6-F8BF-446A-A274-E78BD1C56514}.Release|x64.Build.0 = Release|Any CPU {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Debug|x64.ActiveCfg = Debug|Any CPU + {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Debug|x64.Build.0 = Debug|Any CPU {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Release|Any CPU.ActiveCfg = Release|Any CPU {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Release|Any CPU.Build.0 = Release|Any CPU + {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Release|x64.ActiveCfg = Release|Any CPU + {F1B270EA-9DC2-4753-B3DE-B45D3C128FDB}.Release|x64.Build.0 = Release|Any CPU {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Debug|x64.Build.0 = Debug|Any CPU {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Release|Any CPU.Build.0 = Release|Any CPU {BDA5B320-5282-49FE-B0A7-1F8A0F06862C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -103,6 +160,56 @@ Global {B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}.Release|Any CPU.Build.0 = Release|Any CPU + {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Release|x64.ActiveCfg = Release|Any CPU + {7CD147F9-1BB9-41FD-A310-BCA07447A6B9}.Release|x64.Build.0 = Release|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Debug|x64.Build.0 = Debug|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Release|Any CPU.Build.0 = Release|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Release|x64.ActiveCfg = Release|Any CPU + {3C10169F-4ACB-4FF6-A12B-B74569613E81}.Release|x64.Build.0 = Release|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Debug|x64.Build.0 = Debug|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Release|Any CPU.Build.0 = Release|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Release|x64.ActiveCfg = Release|Any CPU + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}.Release|x64.Build.0 = Release|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Debug|x64.Build.0 = Debug|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Release|Any CPU.Build.0 = Release|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Release|x64.ActiveCfg = Release|Any CPU + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF}.Release|x64.Build.0 = Release|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Debug|x64.ActiveCfg = Debug|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Debug|x64.Build.0 = Debug|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Release|Any CPU.Build.0 = Release|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Release|x64.ActiveCfg = Release|Any CPU + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35}.Release|x64.Build.0 = Release|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Debug|x64.ActiveCfg = Debug|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Debug|x64.Build.0 = Debug|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Release|Any CPU.Build.0 = Release|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Release|x64.ActiveCfg = Release|Any CPU + {88A231A9-940D-43E4-B7CD-B46BA9375367}.Release|x64.Build.0 = Release|Any CPU + {1D52F94E-933C-411F-96C4-4960B423586F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Debug|x64.ActiveCfg = Debug|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Debug|x64.Build.0 = Debug|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Debug|x64.Deploy.0 = Debug|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Release|Any CPU.ActiveCfg = Release|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Release|x64.ActiveCfg = Release|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Release|x64.Build.0 = Release|x64 + {1D52F94E-933C-411F-96C4-4960B423586F}.Release|x64.Deploy.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -121,6 +228,13 @@ Global {7CD147F9-1BB9-41FD-A310-BCA07447A6B9} = {95C69A06-7F62-4652-A480-207B614C2869} {BDA5B320-5282-49FE-B0A7-1F8A0F06862C} = {DBCD161C-D409-48E5-924E-9B7FA1C36B84} {B1C3B554-A098-47F8-8FA9-C28B5BBF35B1} = {95C69A06-7F62-4652-A480-207B614C2869} + {3C10169F-4ACB-4FF6-A12B-B74569613E81} = {DBCD161C-D409-48E5-924E-9B7FA1C36B84} + {AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A} = {95C69A06-7F62-4652-A480-207B614C2869} + {8C3D4CA2-4158-4631-BD5E-F5D066962FAF} = {95C69A06-7F62-4652-A480-207B614C2869} + {C53918E6-667A-4930-837C-0018C5D6E374} = {95C69A06-7F62-4652-A480-207B614C2869} + {99EB4BD2-72E4-4471-8E8D-9454BEACFA35} = {C53918E6-667A-4930-837C-0018C5D6E374} + {88A231A9-940D-43E4-B7CD-B46BA9375367} = {C53918E6-667A-4930-837C-0018C5D6E374} + {1D52F94E-933C-411F-96C4-4960B423586F} = {C53918E6-667A-4930-837C-0018C5D6E374} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2D63A120-9394-48D9-8CA9-1184364FB854} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/App.config b/Test/DurableTask.ServiceFabric.Integration.Tests/App.config new file mode 100644 index 000000000..e936cc132 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs new file mode 100644 index 000000000..d04dde543 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs @@ -0,0 +1,62 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + using System.IO; + using DurableTask.ServiceFabric.Integration.Tests.DeploymentUtil; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class AssemblySetup + { + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext testContext) + { + //Todo: This will fail if local cluster is not setup, currently the test code does not automatically + //start a local cluster and that's a manual pre-req step. + //Todo: Also this will assume that latest SF test application and service code is packaged, if that's not true + //the issue might be hard to detect :-( + DeploymentHelper.CleanAsync().Wait(); + DeploymentHelper.DeployAsync(TestApplicationRootPath).Wait(); + } + + [AssemblyCleanup] + public static void AssemblyCleanup() + { + DeploymentHelper.CleanAsync().Wait(); + } + + static string TestApplicationRootPath + { + get + { + var sourceRoot = Environment.GetEnvironmentVariable("SourceRoot") ?? string.Empty; + var applicationPath = Path.Combine(sourceRoot.Trim(), "Test", "TestFabricApplication", "TestFabricApplication"); + + if (!Directory.Exists(applicationPath)) + { + throw new Exception("Could not find test application path, define SourceRoot environment variable to the source path"); + } + + if (!Directory.Exists(Path.Combine(applicationPath, "pkg", "Debug"))) + { + throw new Exception("Could not find test application package, make sure the test application is built and package generated before running the tests"); + } + + return applicationPath; + } + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs new file mode 100644 index 000000000..1d3711b65 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + static class Constants + { + public const string TestFabricApplicationAddress = "fabric:/TestFabricApplicationType/TestStatefulService"; + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs new file mode 100644 index 000000000..6c21af580 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs @@ -0,0 +1,108 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests.DeploymentUtil +{ + using System.Collections.Specialized; + using System.IO; + using System.Linq; + using System.Xml; + + class ApplicationInfoReader + { + string applicationRootPath; + string applicationPackagePath; + + readonly XmlElement applicationManifestRoot; + readonly XmlNamespaceManager applicationManifestNamespaceManager; + readonly XmlElement serviceManifestRoot; + readonly XmlNamespaceManager serviceManifestNamespaceManager; + + const string ApplicationManifestNodePath = "/sf:ApplicationManifest"; + const string ApplicationNameAttribute = "ApplicationTypeName"; + const string ApplicationVersionAttribute = "ApplicationTypeVersion"; + + const string StatefulServiceNodePath = "/sf:ServiceManifest/sf:ServiceTypes/sf:StatefulServiceType"; + const string StatelessServiceNodePath = "/sf:ServiceManifest/sf:ServiceTypes/sf:StatelessServiceType"; + const string ServiceTypeNameAttribute = "ServiceTypeName"; + + public ApplicationInfoReader(string applicationRootPath) + { + this.applicationRootPath = applicationRootPath; + this.applicationPackagePath = Path.Combine(applicationRootPath, @"pkg\Debug"); + + var applicationManifest = new XmlDocument(); + applicationManifest.Load(Path.Combine(this.applicationPackagePath, "ApplicationManifest.xml")); + this.applicationManifestNamespaceManager = GetXmlNamespaceManager(applicationManifest.NameTable); + this.applicationManifestRoot = applicationManifest.DocumentElement; + + var serviceManifest = new XmlDocument(); + var serviceDirectory = Directory.EnumerateDirectories(this.applicationPackagePath).First(); + serviceManifest.Load(Path.Combine(Path.Combine(this.applicationPackagePath, serviceDirectory), "ServiceManifest.xml")); + this.serviceManifestNamespaceManager = GetXmlNamespaceManager(serviceManifest.NameTable); + this.serviceManifestRoot = serviceManifest.DocumentElement; + } + + public string GetApplicationName() + { + return GetSingleNodeAttributeValue(applicationManifestRoot, applicationManifestNamespaceManager, ApplicationManifestNodePath, ApplicationNameAttribute); + } + + public string GetApplicationVersion() + { + return GetSingleNodeAttributeValue(applicationManifestRoot, applicationManifestNamespaceManager, ApplicationManifestNodePath, ApplicationVersionAttribute); + } + + public string GetServiceName() + { + return GetSingleNodeAttributeValue(serviceManifestRoot, serviceManifestNamespaceManager, StatefulServiceNodePath, ServiceTypeNameAttribute) ?? + GetSingleNodeAttributeValue(serviceManifestRoot, serviceManifestNamespaceManager, StatelessServiceNodePath, ServiceTypeNameAttribute); + } + + public NameValueCollection GetApplicationParameters() + { + var applicationParametersPath = Path.Combine(applicationRootPath, @"ApplicationParameters\Local.5Node.xml"); + + var applicationParameters = new XmlDocument(); + applicationParameters.Load(applicationParametersPath); + + var parametersPath = "/sf:Application/sf:Parameters/sf:Parameter"; + var parameters = new NameValueCollection(); + + foreach (var parameterNode in applicationParameters.DocumentElement.SelectNodes(parametersPath, GetXmlNamespaceManager(applicationParameters.NameTable)).OfType()) + { + parameters.Add(parameterNode.Attributes["Name"].Value, parameterNode.Attributes["Value"].Value); + } + + return parameters; + } + + public string ApplicationPackagePath + { + get { return this.applicationPackagePath; } + } + + string GetSingleNodeAttributeValue(XmlElement root, XmlNamespaceManager namespaceManager, string queryPath, string attributeName) + { + var node = root.SelectSingleNode(queryPath, namespaceManager); + return node?.Attributes?[attributeName].Value; + } + + XmlNamespaceManager GetXmlNamespaceManager(XmlNameTable nameTable) + { + var result = new XmlNamespaceManager(nameTable); + result.AddNamespace("sf", "http://schemas.microsoft.com/2011/01/fabric"); + return result; + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs new file mode 100644 index 000000000..c3b436a72 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs @@ -0,0 +1,106 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests.DeploymentUtil +{ + using System; + using System.Fabric; + using System.Fabric.Description; + using System.Fabric.Health; + using System.Fabric.Query; + using System.Linq; + using System.Threading.Tasks; + using System.Xml; + + static class DeploymentHelper + { + static readonly FabricClient client = new FabricClient(); + + public static async Task DeployAsync(string applicationRootPath) + { + var appInfoReader = new ApplicationInfoReader(applicationRootPath); + var serviceName = new Uri($"fabric:/{appInfoReader.GetApplicationName()}/{appInfoReader.GetServiceName()}"); + var applicationDescription = + new ApplicationDescription( + new Uri($"fabric:/{appInfoReader.GetApplicationName()}"), + appInfoReader.GetApplicationName(), + appInfoReader.GetApplicationVersion(), + appInfoReader.GetApplicationParameters()); + + await DeployAsync(appInfoReader.ApplicationPackagePath, applicationDescription); + await WaitForHealthStatusAsync(applicationDescription, serviceName); + } + + public static async Task CleanAsync() + { + var applications = await client.QueryManager.GetApplicationListAsync(); + foreach (var application in applications) + { + await client.ApplicationManager.DeleteApplicationAsync(new DeleteApplicationDescription(application.ApplicationName) { ForceDelete = true }); + + foreach (var node in await client.QueryManager.GetNodeListAsync()) + { + var replicas = (await client.QueryManager.GetDeployedReplicaListAsync(node.NodeName, application.ApplicationName)).OfType(); + foreach (var replica in replicas) + { + await client.ServiceManager.RemoveReplicaAsync(node.NodeName, replica.Partitionid, replica.ReplicaId); + } + } + } + + var applicationTypeList = await client.QueryManager.GetApplicationTypeListAsync(); + foreach (var applicationType in applicationTypeList) + { + await client.ApplicationManager.UnprovisionApplicationAsync(applicationType.ApplicationTypeName, applicationType.ApplicationTypeVersion); + } + } + + static async Task DeployAsync(string applicationPackagePath, ApplicationDescription applicationDescription) + { + var clusterManifest = new XmlDocument(); + var clusterManifestXml = await client.ClusterManager.GetClusterManifestAsync(); + clusterManifest.LoadXml(clusterManifestXml); + var namespaceManager = new XmlNamespaceManager(clusterManifest.NameTable); + namespaceManager.AddNamespace("sf", "http://schemas.microsoft.com/2011/01/fabric"); + var imageStoreConnectionStringPath = "/sf:ClusterManifest/sf:FabricSettings/sf:Section[@Name='Management']/sf:Parameter[@Name='ImageStoreConnectionString']"; + var imageStoreConnectionStringNode = clusterManifest.DocumentElement.SelectSingleNode(imageStoreConnectionStringPath, namespaceManager); + var imageStoreConnectionString = imageStoreConnectionStringNode.Attributes["Value"].Value; + + var path = Guid.NewGuid().ToString(); + client.ApplicationManager.CopyApplicationPackage(imageStoreConnectionString, applicationPackagePath, path); + + await client.ApplicationManager.ProvisionApplicationAsync(path); + + client.ApplicationManager.RemoveApplicationPackage(imageStoreConnectionString, path); + + await client.ApplicationManager.CreateApplicationAsync(applicationDescription); + } + + static async Task WaitForHealthStatusAsync(ApplicationDescription applicationDescription, Uri serviceName) + { + while (true) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + var states = (await client.HealthManager.GetApplicationHealthAsync(applicationDescription.ApplicationName)).ServiceHealthStates; + if (states.Any() && states.All(s => s.AggregatedHealthState == HealthState.Ok)) + { + return; + } + if (states.Any() && states.Any(s => s.AggregatedHealthState == HealthState.Error || s.AggregatedHealthState == HealthState.Invalid)) + { + throw new Exception($"Deployment of {serviceName} failed"); + } + } + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj b/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj new file mode 100644 index 000000000..276c8cec9 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj @@ -0,0 +1,34 @@ + + + + + net461 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs new file mode 100644 index 000000000..dd194dda0 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs @@ -0,0 +1,385 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.Test.Orchestrations.Performance; + using Microsoft.ServiceFabric.Services.Client; + using Microsoft.ServiceFabric.Services.Remoting.Client; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestApplication.Common; + using TestApplication.Common.Orchestrations; + + [TestClass] + public class FunctionalTests + { + TaskHubClient taskHubClient; + + [TestInitialize] + public void TestInitialize() + { + this.taskHubClient = Utilities.CreateTaskHubClient(); + } + + [TestMethod] + public async Task Orchestration_With_ScheduledTasks_Finishes() + { + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(SimpleOrchestrationWithTasks), null); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.AreEqual("\"Hello Gabbar\"", result.Output); + } + + [TestMethod] + public async Task Orchestration_With_Timer_Finishes_After_The_Wait_Time() + { + var waitTime = 37; + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(SimpleOrchestrationWithTasks), waitTime); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.AreEqual("\"Hello Gabbar\"", result.Output); + var orchestrationTime = result.CompletedTime - result.CreatedTime; + Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Timer Wait time : {waitTime}"); + } + + [TestMethod] + [Ignore] + public async Task GenerationBasicTest() + { + GenerationBasicOrchestration.Result = 0; + GenerationBasicTask.GenerationCount = 0; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(GenerationBasicOrchestration), 4); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.AreEqual(4, GenerationBasicOrchestration.Result, "Orchestration Result is wrong!!!"); + } + + [TestMethod] + public async Task Orchestration_With_SubOrchestration_Finishes() + { + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(SimpleOrchestrationWithSubOrchestration), null); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.AreEqual($"\"TaskResult = Hello World , SubOrchestration1Result = Hello Gabbar, SubOrchestration2Result = Hello Gabbar\"", result.Output); + } + + [TestMethod] + public async Task Orchestration_With_TimeoutWrapper_Test() + { + // Task finishes within timeout + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 0, + MinDelay = 0, + DelayUnit = TimeSpan.FromMilliseconds(1), + UseTimeoutTask = true, + ExecutionTimeout = TimeSpan.FromMinutes(1) + }; + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), input); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.IsNotNull(result); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Console.WriteLine($"Time for Orchestration with no delay running task wrapped in 1 minute timeout : {result.CompletedTime - result.CreatedTime}"); + + // Task does not finish within timeout + input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 5, + MinDelay = 5, + DelayUnit = TimeSpan.FromSeconds(1), + UseTimeoutTask = true, + ExecutionTimeout = TimeSpan.FromSeconds(1) + }; + + instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), input); + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.IsNotNull(result); + Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus); + Console.WriteLine($"Time for Orchestration with 5 second running task wrapped in 1 second timeout : {result.CompletedTime - result.CreatedTime}"); + } + + [TestMethod] + public async Task Orchestration_With_Same_Id_Cant_Be_Started_While_Running() + { + var instanceId = nameof(Orchestration_With_Same_Id_Cant_Be_Started_While_Running); + var testData = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 5, + MinDelay = 5, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + Func>> startFunc = async () => + { + OrchestrationInstance instance = null; + Exception exception = null; + + try + { + instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, testData); + } + catch (Exception e) + { + while (e is AggregateException) + { + e = e.InnerException; + } + + exception = e; + } + + return Tuple.Create(instance, exception); + }; + + var allResults = await Task.WhenAll(startFunc(), startFunc(), startFunc(), startFunc()); + + OrchestrationInstance createdInstance = null; + + for(int i = 0; i < allResults.Length; i++) + { + var result = allResults[i]; + if (result.Item1 != null) + { + if (createdInstance != null) + { + Assert.Fail($"Multiple orchestrations were started with the instance id {instanceId} at the same time"); + } + else + { + createdInstance = result.Item1; + } + } + else + { + Assert.IsInstanceOfType(result.Item2, typeof(Exception), + $"Exception Type Check Failed : Task {i} returned an unexpected exception {result.Item2}"); + } + } + + Assert.IsNotNull(createdInstance, $"No task was able to create an orchestration with the given instance id {instanceId}"); + var orchestrationResult = await this.taskHubClient.WaitForOrchestrationAsync(createdInstance, TimeSpan.FromMinutes(2)); + Assert.AreEqual(OrchestrationStatus.Completed, orchestrationResult.OrchestrationStatus); + } + + [TestMethod] + public async Task FabricProviderClient_ReturnsRunningOrchestrations() + { + var instanceIdPrefix = nameof(FabricProviderClient_ReturnsRunningOrchestrations); + int numberOfInstances = 5; + var startTasks = new List>(); + + for (int i = 0; i < numberOfInstances; i++) + { + var testData = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 5, + MinDelay = 5, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + var task = this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceIdPrefix + i, testData); + + startTasks.Add(task); + } + + var scheduledInstances = await Task.WhenAll(startTasks); + await Task.Delay(TimeSpan.FromSeconds(2)); + + foreach (var scheduledInstance in scheduledInstances) + { + var runtimeState = await this.taskHubClient.GetOrchestrationStateAsync(scheduledInstance.InstanceId); + Console.WriteLine($"Runtime Orchestration State for {scheduledInstance} : {runtimeState}"); + } + + var waitTasks = new List>(); + foreach (var scheduledInstance in scheduledInstances) + { + var waitTask = this.taskHubClient.WaitForOrchestrationAsync(scheduledInstance, TimeSpan.FromMinutes(2)); + waitTasks.Add(waitTask); + } + + var results = await Task.WhenAll(waitTasks); + foreach(var result in results) + { + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + } + } + + [TestMethod] + public async Task Orchestration_With_Long_InstanceId_Name_Test() + { + var instanceId = $"MySomewhatLongNamespaceName|MyRelativelyLongRootEntityName|{Guid.NewGuid().ToString("N")}|MyRatherLongRuleName"; + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 16, + NumberOfSerialTasks = 5, + MaxDelay = 0, + MinDelay = 0, + DelayUnit = TimeSpan.FromMilliseconds(1) + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, input); + + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); + Assert.IsNotNull(result); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + } + + [TestMethod] + public async Task QueryState_For_Latest_Execution() + { + var instanceId = nameof(QueryState_For_Latest_Execution); + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 15, + MinDelay = 15, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, input); + + var state = await this.taskHubClient.GetOrchestrationStateAsync(instanceId); + + Assert.IsNotNull(state); + Assert.AreEqual(instance.ExecutionId, state.OrchestrationInstance.ExecutionId); + + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + + input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 0, + MinDelay = 0, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + var newInstance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, input); + + // We want to make sure that once an orchestration is complete, we can create another instance with the same id. + var newState = await this.taskHubClient.GetOrchestrationStateAsync(instanceId); + + Assert.IsNotNull(newState); + Assert.AreEqual(newInstance.ExecutionId, newState.OrchestrationInstance.ExecutionId); + Assert.AreNotEqual(state.OrchestrationInstance.ExecutionId, newState.OrchestrationInstance.ExecutionId); + + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + } + + [TestMethod] + public async Task Retry_OnException_Test() + { + int retryAttempts = 3; + var minumumExpectedTime = TimeSpan.FromSeconds(7); //This should be calculated based on RetryOptions in the Orchestration code. + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(OrchestrationRunningIntoRetry), retryAttempts); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.AreEqual(retryAttempts.ToString(), result.Output); + var orchestrationTime = result.CompletedTime - result.CreatedTime; + Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Minumum expected time : {minumumExpectedTime}"); + Assert.IsTrue(orchestrationTime > minumumExpectedTime); + } + + [TestMethod] + public async Task ForceTerminate_Terminates_LatestExecution() + { + var testData = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 5, + MaxDelay = 5, + MinDelay = 5, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), testData); + await Task.Delay(TimeSpan.FromMilliseconds(1)); + + var reason = "Testing Terminate Functionality"; + await this.taskHubClient.TerminateInstanceAsync(instance, reason); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); + + Assert.AreEqual(OrchestrationStatus.Terminated, result.OrchestrationStatus); + Assert.AreEqual(reason, result.Output); + } + + [TestMethod] + public async Task ForceTerminate_Twice_Terminates_LatestExecution() + { + var testData = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 2, + MaxDelay = 5, + MinDelay = 5, + DelayUnit = TimeSpan.FromSeconds(1), + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), testData); + await Task.Delay(TimeSpan.FromSeconds(1)); + + var reason = "Testing Terminate (Twice) Functionality"; + var tasks = new List(); + tasks.Add(this.taskHubClient.TerminateInstanceAsync(instance, reason)); + tasks.Add(this.taskHubClient.TerminateInstanceAsync(instance, reason)); + await Task.WhenAll(tasks); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); + + Assert.AreEqual(OrchestrationStatus.Terminated, result.OrchestrationStatus); + Assert.AreEqual(reason, result.Output); + } + + [TestMethod] + public async Task Purge_Removes_State() + { + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(SimpleOrchestrationWithTasks), null); + var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + + instance = result.OrchestrationInstance; + await this.taskHubClient.PurgeOrchestrationInstanceHistoryAsync(DateTime.UtcNow, OrchestrationStateTimeRangeFilterType.OrchestrationCompletedTimeFilter); + var state = await this.taskHubClient.GetOrchestrationStateAsync(instance); + + Assert.IsNull(state); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs new file mode 100644 index 000000000..adf4a818c --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + + class SimplePartitionProvider : IPartitionProvider + { + Uri uri; + + public SimplePartitionProvider(Uri uri) + { + this.uri = uri; + } + + public Uri[] GetAllPartitions() + { + return new Uri[] { this.uri }; + } + + public Uri GetPartitionForInstance(string instanceId) + { + return this.uri; + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs new file mode 100644 index 000000000..5a71cd441 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs @@ -0,0 +1,256 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.Test.Orchestrations.Performance; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class StressTests + { + [TestMethod] + public async Task ExecuteStressTest() + { + var driverConfig = new DriverOrchestrationData() + { + NumberOfParallelOrchestrations = 40, + SubOrchestrationData = new TestOrchestrationData() + { + NumberOfParallelTasks = 10, + NumberOfSerialTasks = 5, + MaxDelay = 5, + MinDelay = 0, + DelayUnit = TimeSpan.FromSeconds(1) + } + }; + + await RunDriverOrchestrationHelper(driverConfig); + Console.WriteLine(); + + driverConfig.SubOrchestrationData.UseTimeoutTask = true; + driverConfig.SubOrchestrationData.ExecutionTimeout = TimeSpan.FromMinutes(2); + + await RunDriverOrchestrationHelper(driverConfig); + } + + // This test uses an implementation detail of client - the polling interval in the client for WaitForOrchestration API. + // The sleep time is chosen carefully to be close to polling interval to simulate WaitForOrchestration succeeding while + // the session could still be there in the store (with a previous implementation which was fixed when the test was introduced). + // The test should be modified if the polling interval changes or should be re thought if the polling logic itself changes. + // Todo : The above logic did change since the test was initially developed. Need to re-think this test. + [TestMethod] + public async Task ExecuteSameOrchestrationBackToBack() + { + int numberOfAttempts = 10; //Try a few times to make sure there is no failure. + var instanceId = nameof(ExecuteSameOrchestrationBackToBack); + var taskHubClient = Utilities.CreateTaskHubClient(); + + Dictionary> results = new Dictionary>(); + + for (int attemptNumber = 0; attemptNumber < numberOfAttempts; attemptNumber++) + { + var instance = await taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 1980, + MinDelay = 1900, + DelayUnit = TimeSpan.FromMilliseconds(1), + }); + + var state = await taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); + results.Add(attemptNumber, new Tuple(instance, state)); + } + + for(int attemptNumber = 0; attemptNumber < numberOfAttempts; attemptNumber++) + { + var instance = results[attemptNumber].Item1; + var state = results[attemptNumber].Item2; + Assert.IsNotNull(state, $"Attempt number {attemptNumber} with execution id {instance.ExecutionId} Failed with null value for WaitForOrchestration"); + Assert.AreEqual(OrchestrationStatus.Completed, state.OrchestrationStatus, $"Attempt number {attemptNumber} with execution id {instance.ExecutionId} did not reach 'Completed' state."); + Console.WriteLine($"Time for orchestration in attempt number {attemptNumber} : {state.CompletedTime - state.CreatedTime}"); + } + } + + [TestMethod] + public async Task Test_Lot_Of_Activities_Per_Orchestration() + { + var tests = new[] { 256, 1024, 2048, 3500, 4000, 8000, 10000, 15000, 20000 }; + foreach (var numberOfActivities in tests) + { + Console.WriteLine($"Begin testing orchestration with {numberOfActivities} parallel activities"); + var taskHubClient = Utilities.CreateTaskHubClient(); + var instance = await taskHubClient.CreateOrchestrationInstanceAsync(typeof(ExecutionCountingOrchestration), numberOfActivities); + var state = await taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(3)); + await taskHubClient.PurgeOrchestrationInstanceHistoryAsync(DateTime.Now, OrchestrationStateTimeRangeFilterType.OrchestrationCompletedTimeFilter); + Assert.IsNotNull(state); + Assert.AreEqual(OrchestrationStatus.Completed, state.OrchestrationStatus); + Console.WriteLine($"Time for orchestration {state.OrchestrationInstance.InstanceId} with {numberOfActivities} parallel activities : {state.CompletedTime - state.CreatedTime}"); + } + } + + [TestMethod] + public async Task SimplePerformanceExperiment() + { + var testOrchestratorInput = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 3, + MaxDelay = 3, + MinDelay = 3, + DelayUnit = TimeSpan.FromSeconds(1) + }; + + await RunTestOrchestrationsHelper(100, testOrchestratorInput); + Console.WriteLine(); + + testOrchestratorInput.UseTimeoutTask = true; + testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2); + + await RunTestOrchestrationsHelper(100, testOrchestratorInput); + } + + [TestMethod] + public async Task RunThousandOrchsWithHundredMilliSecondDelayInBetweenStartingEach() + { + var testOrchestratorInput = new TestOrchestrationData() + { + NumberOfParallelTasks = 16, + NumberOfSerialTasks = 4, + MaxDelay = 500, + MinDelay = 50, + DelayUnit = TimeSpan.FromMilliseconds(1) + }; + + await RunTestOrchestrationsHelper(1000, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromMilliseconds(100)); + Console.WriteLine(); + + testOrchestratorInput.UseTimeoutTask = true; + testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2); + + await RunTestOrchestrationsHelper(1000, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromMilliseconds(100)); + } + + [Ignore] //Long running test and not too interesting anyway. (The test is passing - enable as needed to test and ensure). + [TestMethod] + public async Task InfrequentLoadScenario() + { + var testOrchestratorInput = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 2, + MaxDelay = 0, + MinDelay = 0, + }; + + await RunTestOrchestrationsHelper(200, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromSeconds(1)); + Console.WriteLine(); + + testOrchestratorInput.UseTimeoutTask = true; + testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2); + + await RunTestOrchestrationsHelper(200, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromSeconds(1)); + } + + async Task RunDriverOrchestrationHelper(DriverOrchestrationData driverConfig) + { + var taskHubClient = Utilities.CreateTaskHubClient(); + Console.WriteLine($"Orchestration getting scheduled: {DateTime.Now}"); + + Stopwatch stopWatch = Stopwatch.StartNew(); + var instance = await taskHubClient.CreateOrchestrationInstanceAsync(typeof(DriverOrchestration), driverConfig); + var state = await taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(5)); + stopWatch.Stop(); + + Console.WriteLine($"Orchestration Status: {state.OrchestrationStatus}"); + Console.WriteLine($"Orchestration Result: {state.Output}"); + + TimeSpan totalTime = stopWatch.Elapsed; + TimeSpan orchestrationTime = state.CompletedTime - state.CreatedTime; + + Func elapsedTimeFormatter = timeSpan => $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds / 10:00}"; + Console.WriteLine($"Total Meastured Time: {elapsedTimeFormatter(totalTime)}"); + Console.WriteLine($"Top level Orchestration Time: {elapsedTimeFormatter(orchestrationTime)}"); + + var expectedResult = driverConfig.NumberOfParallelOrchestrations * (driverConfig.SubOrchestrationData.NumberOfParallelTasks + driverConfig.SubOrchestrationData.NumberOfSerialTasks); + + Assert.AreEqual(expectedResult.ToString(), state.Output); + } + + async Task RunTestOrchestrationsHelper(int numberOfInstances, TestOrchestrationData orchestrationInput, Func delayGeneratorFunction = null) + { + var taskHubClient = Utilities.CreateTaskHubClient(); + + List> results = new List>(); + List waitTasks = new List(); + Stopwatch stopWatch = Stopwatch.StartNew(); + for (int i = 0; i < numberOfInstances; i++) + { + var instance = await taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), orchestrationInput); + waitTasks.Add(Task.Run(async () => + { + var state = await taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); + results.Add(Tuple.Create(instance, state)); + })); + if (delayGeneratorFunction != null) + { + await Task.Delay(delayGeneratorFunction(i)); + } + } + + await Task.WhenAll(waitTasks); + stopWatch.Stop(); + + Func elapsedTimeFormatter = timeSpan => $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds / 10:00}"; + Console.WriteLine($"Total Meastured Time For All Orchestrations: {elapsedTimeFormatter(stopWatch.Elapsed)}"); + + var expectedResult = (orchestrationInput.NumberOfParallelTasks + orchestrationInput.NumberOfSerialTasks).ToString(); + TimeSpan minTime = TimeSpan.MaxValue; + TimeSpan maxTime = TimeSpan.FromMilliseconds(0); + + int failedOrchestrations = 0; + foreach (var kvp in results) + { + if (kvp.Item2 == null) + { + failedOrchestrations++; + var state = await taskHubClient.GetOrchestrationStateAsync(kvp.Item1); + Console.WriteLine($"Unfinished orchestration {kvp.Item1}, state : {kvp.Item2?.OrchestrationStatus}"); + continue; + } + + Assert.AreEqual(expectedResult, kvp.Item2.Output, $"Unexpected output for Orchestration : {kvp.Item1.InstanceId}"); + TimeSpan orchestrationTime = kvp.Item2.CompletedTime - kvp.Item2.CreatedTime; + if (minTime > orchestrationTime) + { + minTime = orchestrationTime; + } + if (maxTime < orchestrationTime) + { + maxTime = orchestrationTime; + } + } + + Console.WriteLine($"Minimum time taken for any orchestration instance : {elapsedTimeFormatter(minTime)}"); + Console.WriteLine($"Maximum time taken for any orchestration instance : {elapsedTimeFormatter(maxTime)}"); + + Assert.AreEqual(0, failedOrchestrations, $"{failedOrchestrations} orchestrations failed out of {numberOfInstances}."); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs new file mode 100644 index 000000000..968b4b7dc --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + using System.Diagnostics; + using System.Net.Http; + using System.Threading.Tasks; + using DurableTask.Core; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public static class Utilities + { + public static TaskHubClient CreateTaskHubClient() + { + var partitionProvider = new SimplePartitionProvider(new Uri("http://localhost:30303/api/dtfx/")); + var httpClient = new HttpClient(); + return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider, httpClient)); + } + + public static async Task ThrowsException(Func action, string expectedMessage) where TException : Exception + { + try + { + await action(); + Assert.Fail($"Method {action.Method} did not throw the expected exception of type {typeof(TException).Name}"); + } + catch (Exception ex) + { + AggregateException aggregate = ex as AggregateException; + if (aggregate != null) + { + ex = aggregate.InnerException; + } + + TException expected = ex as TException; + if (expected == null) + { + Assert.Fail($"Method {action.Method} is expected to throw exception of type {typeof(TException).Name} but has thrown {ex.GetType().Name} instead."); + } + else if (!string.Equals(expected.Message, expectedMessage, StringComparison.Ordinal)) + { + Assert.Fail($"Method {action.Method} is expected to throw exception with message '{expectedMessage}' but has thrown the message '{expected.Message}' instead."); + } + } + } + + public static async Task MeasureAsync(Func asyncAction) + { + var timer = Stopwatch.StartNew(); + await asyncAction(); + timer.Stop(); + return timer.Elapsed; + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs new file mode 100644 index 000000000..3a14f1022 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs @@ -0,0 +1,151 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Integration.Tests +{ + using System; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.Test.Orchestrations.Performance; + using Microsoft.ServiceFabric.Services.Client; + using Microsoft.ServiceFabric.Services.Remoting.Client; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestApplication.Common; + + [TestClass] + public class WaitForOrchestrationTests + { + TaskHubClient taskHubClient; + + [TestInitialize] + public void TestInitialize() + { + this.taskHubClient = Utilities.CreateTaskHubClient(); + } + + [TestMethod] + public async Task WaitForOrchestration_Returns_Null_When_Orchestration_Not_Started() + { + var instance = new OrchestrationInstance() { InstanceId = "NonExisting", ExecutionId = "NonExisting" }; + OrchestrationState result = null; + var waitTime = TimeSpan.FromMinutes(1); + var time = await Utilities.MeasureAsync(async () => + { + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, waitTime); + }); + + Assert.IsNull(result); + Assert.IsTrue(time < waitTime + waitTime); + Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait {time}"); + } + + [TestMethod] + public async Task WaitForOrchestration_Returns_State_When_Orchestration_Already_Finished() + { + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = 0, + MinDelay = 0, + DelayUnit = TimeSpan.FromMilliseconds(1), + UseTimeoutTask = false, + }; + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), input); + await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); //Wait till orchestration is finished + + var waitTime = TimeSpan.FromMilliseconds(100); //Let this be very low for the test to be effective. + OrchestrationState result = null; + var time = await Utilities.MeasureAsync(async () => + { + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, waitTime); + }); + + Assert.IsNotNull(result); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.IsTrue(time < waitTime); + Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait {time}, Actual Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}"); + } + + [TestMethod] + public async Task WaitForOrchestration_Waits_And_Returns_State_When_Orchestration_IsRunning() + { + var waitTime = TimeSpan.FromMinutes(1); + var orchestrationTimeInSeconds = 5; + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = orchestrationTimeInSeconds, + MinDelay = orchestrationTimeInSeconds, + DelayUnit = TimeSpan.FromSeconds(1), + UseTimeoutTask = false, + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), input); + + OrchestrationState result = null; + var time = await Utilities.MeasureAsync(async () => + { + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, waitTime); + }); + + Assert.IsNotNull(result); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Assert.IsTrue(time < waitTime); + Assert.IsTrue(time > TimeSpan.FromSeconds(orchestrationTimeInSeconds)); + Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait : {time}, Expected Minimum time for orchestration : {orchestrationTimeInSeconds} seconds, Actual Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}"); + } + + [TestMethod] + public async Task WaitForOrchestration_TimesOut_And_Returns_Null_When_Orchestration_DidNotFinishYet() + { + var initialwaitTimeInSeconds = 2; + var orchestrationTimeInSeconds = 5; + var waitTime = TimeSpan.FromSeconds(initialwaitTimeInSeconds); + var input = new TestOrchestrationData() + { + NumberOfParallelTasks = 0, + NumberOfSerialTasks = 1, + MaxDelay = orchestrationTimeInSeconds, + MinDelay = orchestrationTimeInSeconds, + DelayUnit = TimeSpan.FromSeconds(1), + UseTimeoutTask = false, + }; + + var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), input); + + OrchestrationState result = null; + var time = await Utilities.MeasureAsync(async () => + { + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, waitTime); + }); + + Assert.IsNotNull(result); + Assert.AreEqual(result.OrchestrationStatus, OrchestrationStatus.Running); + Assert.IsTrue(time > waitTime); + Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait : {time}, Expected Orchestration Running Time : {orchestrationTimeInSeconds} seconds"); + + var newWaitTime = TimeSpan.FromMinutes(1); + time = await Utilities.MeasureAsync(async () => + { + result = await this.taskHubClient.WaitForOrchestrationAsync(instance, newWaitTime); + }); + + Assert.IsNotNull(result); + Assert.IsTrue(time < newWaitTime); + Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); + Console.WriteLine($"Full WaitTime : {newWaitTime}, Actual time taken for Wait : {time}, Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}"); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs b/Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs new file mode 100644 index 000000000..493816418 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using System; + + using DurableTask.ServiceFabric.TaskHelpers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CountBasedFixedDelayRetryPolicyTests + { + [TestMethod] + public void CountBasedFixedDelayRetry_Attempts_MaxCount_Times() + { + var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(3, TimeSpan.Zero); + int attempts = 0; + while (testRetryPolicy.ShouldExecute()) + { + attempts++; + } + Assert.AreEqual(3, attempts); + } + + [TestMethod] + public void CountBasedFixedDelayRetry_Last_Retry_Delay_Is_Zero() + { + var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(1, TimeSpan.FromSeconds(1)); + TimeSpan lastDelay = TimeSpan.MaxValue; + while (testRetryPolicy.ShouldExecute()) + { + lastDelay = testRetryPolicy.GetNextDelay(); + } + Assert.AreEqual(TimeSpan.Zero, lastDelay); + } + + [TestMethod] + public void CountBasedFixedDelayRetry_Non_Last_Retry_Delay_Is_Fixed() + { + TimeSpan fixedDelay = TimeSpan.FromSeconds(23); + var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(3, fixedDelay); + int attemptNumber = 1; + TimeSpan nextDelay = TimeSpan.MaxValue; + while (testRetryPolicy.ShouldExecute()) + { + nextDelay = testRetryPolicy.GetNextDelay(); + if (attemptNumber < 3) + { + Assert.AreEqual(fixedDelay, nextDelay); + } + attemptNumber++; + } + Assert.AreEqual(TimeSpan.Zero, nextDelay); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj b/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj new file mode 100644 index 000000000..a02a0631a --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj @@ -0,0 +1,25 @@ + + + + + net461 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Test/DurableTask.ServiceFabric.Tests/Measure.cs b/Test/DurableTask.ServiceFabric.Tests/Measure.cs new file mode 100644 index 000000000..dda3ed6ed --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/Measure.cs @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Runtime.Serialization; + + class Measure + { + public static T DataContractSerialization(T testObject) + { + return (T)DataContractSerialization(typeof(T), testObject); + } + + public static object DataContractSerialization(Type testType, object testObject) + { + using (var stream = new MemoryStream()) + { + var serializer = new DataContractSerializer(testType); + var time = MeasureTime(() => serializer.WriteObject(stream, testObject)); + Console.WriteLine($"Time for serialization : {time.TotalMilliseconds} ms"); + Console.WriteLine($"Size of serialized stream : {stream.Length} bytes."); + + stream.Position = 0; + object deserialized = null; + time = MeasureTime(() => { deserialized = serializer.ReadObject(stream); }); + Console.WriteLine($"Time for deserialization : {time.TotalMilliseconds} ms"); + return deserialized; + } + } + + public static TimeSpan MeasureTime(Action action) + { + Stopwatch timer = Stopwatch.StartNew(); + action(); + timer.Stop(); + return timer.Elapsed; + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs b/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs new file mode 100644 index 000000000..66a7b89ef --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs @@ -0,0 +1,85 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using DurableTask.Core; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OrchestrationInstanceComparerTests + { + [TestMethod] + public void OrchestrationInstanceComparer_NullInstanceTests() + { + Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(null, null)); + Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(null, new OrchestrationInstance())); + Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(new OrchestrationInstance(), null)); + } + + [TestMethod] + public void OrchestrationInstanceComparer_SameOrchestrationInstances() + { + var first = new OrchestrationInstance() + { + InstanceId = "InstanceId", + ExecutionId = "ExecutionId" + }; + + var second = new OrchestrationInstance() + { + InstanceId = "InstanceId", + ExecutionId = "ExecutionId" + }; + + Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(first, first)); + Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(first, second)); + } + + [TestMethod] + public void OrchestrationInstanceComparer_DifferentExecutionIdInstances() + { + var first = new OrchestrationInstance() + { + InstanceId = "InstanceId", + ExecutionId = "ExecutionId1" + }; + + var second = new OrchestrationInstance() + { + InstanceId = "InstanceId", + ExecutionId = "ExecutionId2" + }; + + Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(first, second)); + } + + [TestMethod] + public void OrchestrationInstanceComparer_DifferentOrchestrationInstances() + { + var first = new OrchestrationInstance() + { + InstanceId = "InstanceId1", + ExecutionId = "ExecutionId1" + }; + + var second = new OrchestrationInstance() + { + InstanceId = "InstanceId2", + ExecutionId = "ExecutionId2" + }; + + Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(first, second)); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs b/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs new file mode 100644 index 000000000..3b7caa45b --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using DurableTask.Core; + using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PersistentSessionTests + { + [TestMethod] + public void PersistentSession_SerializationTest() + { + int numberOfHistoryEvents = 256; + + var events = new List(); + events.Add(new ExecutionStartedEvent(-1, "TestInput")); + for (int i = 0; i < numberOfHistoryEvents; i++) + { + events.Add(new TaskScheduledEvent(-1)); + events.Add(new TaskCompletedEvent(-1, -1, $"Task {i} Result")); + } + events.Add(new ExecutionCompletedEvent(-1, "FinalResult", OrchestrationStatus.Completed)); + + var instance = new OrchestrationInstance() + { + InstanceId = "testSession", + ExecutionId = Guid.NewGuid().ToString("N") + }; + + PersistentSession testSession = PersistentSession.Create(instance, events.ToImmutableList()); + + var actual = Measure.DataContractSerialization(testSession); + + Assert.IsNotNull(actual); + Assert.AreEqual(instance.InstanceId, actual.SessionId.InstanceId); + Assert.AreEqual(instance.ExecutionId, actual.SessionId.ExecutionId); + Assert.AreEqual(numberOfHistoryEvents * 2 + 2, actual.SessionState.Count); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs new file mode 100644 index 000000000..91e34b848 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SerializationPerformanceExperiments + { + [TestMethod] + public void DataContractSerializationSizeExperiment_PrimitiveTypes() + { + var testObjects = new List() + { + Int32.MinValue, + Int32.MaxValue, + "01234567890123456789012345678901", + Guid.NewGuid(), + DateTime.UtcNow, + Int64.MaxValue, + Int64.MinValue, + Int16.MaxValue, + Int16.MinValue, + new ComplexType() { LongProp = 33, StringProp = "0123456789" } + }; + + foreach (var testObject in testObjects) + { + var type = testObject.GetType(); + Console.WriteLine($"Type = {type.Name}"); + Console.WriteLine($"TestObject = {testObject}"); + Measure.DataContractSerialization(type, testObject); + Console.WriteLine(); + } + } + + [DataContract] + class ComplexType + { + [DataMember] public long LongProp; + + [DataMember] public string StringProp; + + public override string ToString() + { + return $"'LongProp = {LongProp}, StringProp = {StringProp}'"; + } + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs b/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs new file mode 100644 index 000000000..d071314d7 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using DurableTask.Core; + using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TaskMessageItemTests + { + [TestMethod] + public void TaskMessageItem_SerializationTests() + { + var expected = new TaskMessageItem(new TaskMessage() + { + OrchestrationInstance = new OrchestrationInstance() { InstanceId = "InstanceId", ExecutionId = "ExecutionId" }, + SequenceNumber = 33, + Event = new TaskScheduledEvent(-1) + }); + + var actual = Measure.DataContractSerialization(expected); + + Assert.IsNotNull(actual); + Assert.AreEqual(expected.TaskMessage.OrchestrationInstance.InstanceId, actual.TaskMessage.OrchestrationInstance.InstanceId); + Assert.AreEqual(expected.TaskMessage.OrchestrationInstance.ExecutionId, actual.TaskMessage.OrchestrationInstance.ExecutionId); + Assert.AreEqual(expected.TaskMessage.SequenceNumber, actual.TaskMessage.SequenceNumber); + Assert.AreEqual(expected.TaskMessage.Event.EventId, actual.TaskMessage.Event.EventId); + } + } +} diff --git a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs new file mode 100644 index 000000000..7d8c77104 --- /dev/null +++ b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs @@ -0,0 +1,137 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tests +{ + using System; + using System.Collections.Immutable; + using DurableTask.Core; + using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TimerFiredEventComparerTests + { + [TestMethod] + public void Messages_With_Equal_FiredAt_Value_With_Different_Key_Are_Treated_Separate_And_Sorted_On_Key() + { + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + + var instance = new OrchestrationInstance() + { + InstanceId = "InstanceId", + }; + + var currentTime = DateTime.UtcNow; + var builder = timerMessages.ToBuilder(); + int numberOfMessages = 5; + + // Add them in reverse order to expected order just to make the test case more interesting. + for (int i = numberOfMessages; i >= 1; i--) + { + builder.Add(new Message($"Message{i}", new TaskMessageItem(new TaskMessage() + { + SequenceNumber = i, + OrchestrationInstance = instance, + Event = new TimerFiredEvent(i) { FireAt = currentTime } + }))); + } + + Assert.AreEqual(numberOfMessages, builder.Count); + timerMessages = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); + Assert.AreEqual(numberOfMessages, timerMessages.Count); + + // Now enumerating the items should result in right order. + for (int i = 1; i <= numberOfMessages; i++) + { + var min = timerMessages.Min; + timerMessages = timerMessages.Remove(min); + Assert.AreEqual($"Message{i}", min.Key, "Ordering seems to be broken"); + } + + Assert.AreEqual(0, timerMessages.Count); + } + + [TestMethod] + public void Messages_With_Different_FiredAt_Value_Are_Sorted_Based_On_FiredAt() + { + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + + var instance = new OrchestrationInstance() + { + InstanceId = "InstanceId", + }; + + var currentTime = DateTime.UtcNow; + var builder = timerMessages.ToBuilder(); + int numberOfMessages = 5; + + // Add them in reverse order to expected order just to make the test case more interesting. + for (int i = numberOfMessages; i >= 1; i--) + { + builder.Add(new Message($"Message{6-i}", new TaskMessageItem(new TaskMessage() //Let the key values be in reverse order to fired at values again to make test more interesting. + { + SequenceNumber = i, + OrchestrationInstance = instance, + Event = new TimerFiredEvent(i) { FireAt = currentTime + TimeSpan.FromSeconds(i) } + }))); + } + + Assert.AreEqual(numberOfMessages, builder.Count); + timerMessages = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); + Assert.AreEqual(numberOfMessages, timerMessages.Count); + + // Now enumerating the items should result in right order. + for (int i = 1; i <= numberOfMessages; i++) + { + var min = timerMessages.Min; + timerMessages = timerMessages.Remove(min); + var firedAt = (min.Value.TaskMessage.Event as TimerFiredEvent)?.FireAt; + Assert.AreEqual(currentTime + TimeSpan.FromSeconds(i), firedAt, "Ordering seems to be broken"); + Assert.AreEqual($"Message{6 - i}", min.Key); + } + + Assert.AreEqual(0, timerMessages.Count); + } + + [TestMethod] + public void Messages_With_Equal_FiredAt_Value_With_Same_Key_Are_Treated_Equal() + { + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + + var instance = new OrchestrationInstance() + { + InstanceId = "InstanceId", + }; + + var currentTime = DateTime.UtcNow; + var builder = timerMessages.ToBuilder(); + int numberOfMessages = 3; + + // Add them in reverse order to expected order just to make the test case more interesting. + for (int i = numberOfMessages; i >= 1; i--) + { + builder.Add(new Message("Message", new TaskMessageItem(new TaskMessage() + { + SequenceNumber = i, + OrchestrationInstance = instance, + Event = new TimerFiredEvent(i) { FireAt = currentTime } + }))); + } + + Assert.AreEqual(1, builder.Count); + timerMessages = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); + Assert.AreEqual(1, timerMessages.Count); + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestration.cs b/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestration.cs new file mode 100644 index 000000000..362586529 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestration.cs @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DurableTask.Core; + + public class DriverOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, DriverOrchestrationData data) + { + int result = 0; + List> results = new List>(); + int i = 0; + for (; i < data.NumberOfParallelOrchestrations; i++) + { + results.Add(context.CreateSubOrchestrationInstance(typeof(TestOrchestration), data.SubOrchestrationData)); + } + + int[] counters = await Task.WhenAll(results.ToArray()); + result = counters.Sum(); + + return result; + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestrationData.cs b/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestrationData.cs new file mode 100644 index 000000000..3d1e36cef --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/DriverOrchestrationData.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.Test.Orchestrations.Performance +{ + using System.Runtime.Serialization; + + [DataContract] + [KnownType(typeof(DriverOrchestrationData))] + public class DriverOrchestrationData + { + [DataMember] + public int NumberOfParallelOrchestrations { get; set; } + + [DataMember] + public TestOrchestrationData SubOrchestrationData { get; set; } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingActivity.cs b/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingActivity.cs new file mode 100644 index 000000000..8dfc5c5a9 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingActivity.cs @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using DurableTask.Core; + + public sealed class ExecutionCountingActivity : AsyncTaskActivity + { + // This has to be reset before starting the orchestration and the orchestration + // must be run in sequence for this to determine the correct number of times all + // the activities together executed. + public static int Counter = 0; + + protected override async Task ExecuteAsync(TaskContext context, int taskId) + { + await Task.Delay(new Random().Next(50, 100)); + Interlocked.Increment(ref Counter); + return Counter; + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingOrchestration.cs b/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingOrchestration.cs new file mode 100644 index 000000000..e43f839a8 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/ExecutionCountingOrchestration.cs @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using DurableTask.Core; + + public sealed class ExecutionCountingOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, int numberOfActivities) + { + List> results = new List>(); + for (int i = 0; i < numberOfActivities; i++) + { + results.Add(context.ScheduleTask(typeof(ExecutionCountingActivity), i)); + } + + await Task.WhenAll(results); + return ExecutionCountingActivity.Counter; + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTask.cs b/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTask.cs new file mode 100644 index 000000000..e2f0fb264 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTask.cs @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using System; + using System.Threading.Tasks; + using DurableTask.Core; + + public sealed class RandomTimeWaitingTask : AsyncTaskActivity + { + protected override async Task ExecuteAsync(TaskContext context, RandomTimeWaitingTaskInput input) + { + int delayTime; + + if (input.MaxDelay == input.MinDelay) + { + delayTime = input.MaxDelay; + } + else + { + Random random = new Random(); + delayTime = random.Next(input.MinDelay, input.MaxDelay); + } + + // Uncomment this block to force some failures. + //if (new Random().NextDouble() < 0.1) + //{ + // throw new Exception(); + //} + + await Task.Delay(TimeSpan.FromMilliseconds(input.DelayUnit.TotalMilliseconds * delayTime)); + + return 1; + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTaskInput.cs b/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTaskInput.cs new file mode 100644 index 000000000..b5bdef6c6 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/RandomTimeWaitingTaskInput.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.Test.Orchestrations.Performance +{ + using System; + + public class RandomTimeWaitingTaskInput + { + public string TaskId { get; set; } + + public int MaxDelay { get; set; } + + public int MinDelay { get; set; } + + public TimeSpan DelayUnit { get; set; } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestration.cs b/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestration.cs new file mode 100644 index 000000000..436f89f4f --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestration.cs @@ -0,0 +1,93 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using DurableTask.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + public class TestOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, TestOrchestrationData data) + { + if (data.UseTimeoutTask) + { + return await RunTaskWithTimeout(context, data); + } + else + { + return await RunTaskCore(context, data); + } + } + + async Task RunTaskCore(OrchestrationContext context, TestOrchestrationData data) + { + int result = 0; + List> results = new List>(); + int i = 0; + int j = 0; + for (; i < data.NumberOfParallelTasks; i++) + { + results.Add(context.ScheduleTask( + typeof(RandomTimeWaitingTask), + new RandomTimeWaitingTaskInput + { + TaskId = "ParallelTask: " + i.ToString(), + MaxDelay = data.MaxDelay, + MinDelay = data.MinDelay, + DelayUnit = data.DelayUnit + })); + } + + int[] counters = await Task.WhenAll(results.ToArray()); + result = counters.Sum(); + + for (; j < data.NumberOfSerialTasks; j++) + { + int c = await context.ScheduleTask(typeof(RandomTimeWaitingTask), + new RandomTimeWaitingTaskInput + { + TaskId = "SerialTask" + (i + j), + MaxDelay = data.MaxDelay, + MinDelay = data.MinDelay, + DelayUnit = data.DelayUnit + }); + result += c; + } + + return result; + } + + async Task RunTaskWithTimeout(OrchestrationContext context, TestOrchestrationData data) + { + var timerCts = new CancellationTokenSource(); + + var timer = context.CreateTimer(context.CurrentUtcDateTime.Add(data.ExecutionTimeout), -1, timerCts.Token); + var mainTask = RunTaskCore(context, data); + + var resultTask = await Task.WhenAny(mainTask, timer); + + if (resultTask == timer) + { + throw new TimeoutException("Orchestration timed out"); + } + + timerCts.Cancel(); + return mainTask.Result; + } + } +} diff --git a/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestrationData.cs b/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestrationData.cs new file mode 100644 index 000000000..4dc829528 --- /dev/null +++ b/Test/DurableTask.Test.Orchestrations/Performance/TestOrchestrationData.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// 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.Test.Orchestrations.Performance +{ + using System; + using System.Runtime.Serialization; + + [DataContract] + [KnownType(typeof(TestOrchestrationData))] + public class TestOrchestrationData + { + [DataMember] + public int NumberOfParallelTasks { get; set; } + + [DataMember] + public int NumberOfSerialTasks { get; set; } + + [DataMember] + public int MaxDelay { get; set; } + + [DataMember] + public int MinDelay { get; set; } + + [DataMember] + public TimeSpan DelayUnit { get; set; } + + [DataMember] + public bool UseTimeoutTask { get; set; } + + [DataMember] + public TimeSpan ExecutionTimeout { get; set; } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/CounterException.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/CounterException.cs new file mode 100644 index 000000000..4472448a6 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/CounterException.cs @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class CounterException : Exception + { + readonly string CounterPropName = "Counter"; + + public CounterException(int counter) + { + this.Counter = counter; + } + + protected CounterException(SerializationInfo info, StreamingContext context) + { + this.Counter = info.GetInt32(CounterPropName); + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(CounterPropName, this.Counter); + base.GetObjectData(info, context); + } + + public int Counter { get; } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/ExceptionThrowingTask.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/ExceptionThrowingTask.cs new file mode 100644 index 000000000..3d6de96c5 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/ExceptionThrowingTask.cs @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using DurableTask.Core; + + public class ExceptionThrowingTask : TaskActivity + { + protected override bool Execute(TaskContext context, int remainingAttempts) + { + if (remainingAttempts > 0) + { + // ServiceEventSource.Current.Message($"{nameof(ExceptionThrowingTask)}.{nameof(Execute)} : Throwing Exception, RemainingAttempts = {remainingAttempts}"); + throw new CounterException(remainingAttempts); + } + + // ServiceEventSource.Current.Message($"{nameof(ExceptionThrowingTask)}.{nameof(Execute)} : Returning true, RemainingAttempts = {remainingAttempts}"); + return true; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GenerationBasicOrchestration.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GenerationBasicOrchestration.cs new file mode 100644 index 000000000..9868b3aed --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GenerationBasicOrchestration.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using System.Threading.Tasks; + using DurableTask.Core; + + public class GenerationBasicOrchestration : TaskOrchestration + { + // HACK: This is just a hack to communicate result of orchestration back to test + public static int Result; + + public override async Task RunTask(OrchestrationContext context, int numberOfGenerations) + { + int count = await context.ScheduleTask(typeof(GenerationBasicTask)); + numberOfGenerations--; + if (numberOfGenerations > 0) + { + context.ContinueAsNew(numberOfGenerations); + } + + // This is a HACK to get unit test up and running. Should never be done in actual code. + Result = count; + return count; + } + } + + public sealed class GenerationBasicTask : TaskActivity + { + // HACK: This is just a hack to communicate result of orchestration back to test + public static int GenerationCount = 0; + + protected override int Execute(TaskContext context, string input) + { + GenerationCount++; + return GenerationCount; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GetUserTask.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GetUserTask.cs new file mode 100644 index 000000000..23bb5a3d5 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GetUserTask.cs @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using DurableTask.Core; + + public class GetUserTask : TaskActivity + { + protected override string Execute(TaskContext context, string input) + { + return "Gabbar"; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GreetUserTask.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GreetUserTask.cs new file mode 100644 index 000000000..eb3fd98ed --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/GreetUserTask.cs @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using DurableTask.Core; + + public class GreetUserTask : TaskActivity + { + protected override string Execute(TaskContext context, string user) + { + return "Hello " + user; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/OrchestrationRunningIntoRetry.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/OrchestrationRunningIntoRetry.cs new file mode 100644 index 000000000..42d0374b1 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/OrchestrationRunningIntoRetry.cs @@ -0,0 +1,63 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.Common.Orchestrations +{ + using System; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.Core.Exceptions; + + public class OrchestrationRunningIntoRetry : TaskOrchestration + { + CounterException LatestException; + + public override async Task RunTask(OrchestrationContext context, int numberOfRetriesToEnforce) + { + var result = await context.ScheduleWithRetry(typeof(ExceptionThrowingTask), + new RetryOptions(TimeSpan.FromSeconds(1), 5) + { + BackoffCoefficient = 2, + MaxRetryInterval = TimeSpan.FromMinutes(1), + Handle = RetryExceptionHandler + }, + this.LatestException?.Counter-1 ?? numberOfRetriesToEnforce); + + if (result) + { + return numberOfRetriesToEnforce; + } + + throw new Exception($"Unexpected exception thrown from {nameof(OrchestrationRunningIntoRetry)}."); + } + + bool RetryExceptionHandler(Exception e) + { + TaskFailedException tfe = e as TaskFailedException; + + if (tfe != null && tfe.InnerException != null) + { + e = tfe.InnerException; + } + + CounterException ce = e as CounterException; + if (ce != null) + { + LatestException = ce; + return true; + } + + return false; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithSubOrchestration.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithSubOrchestration.cs new file mode 100644 index 000000000..159b5b381 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithSubOrchestration.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. +// ---------------------------------------------------------------------------------- + +namespace TestApplication.Common.Orchestrations +{ + using System.Threading.Tasks; + using DurableTask.Core; + + public class SimpleOrchestrationWithSubOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + var subOrch1 = context.CreateSubOrchestrationInstance(typeof(SimpleOrchestrationWithTasks), input); + var subOrch2 = context.CreateSubOrchestrationInstance(typeof(SimpleOrchestrationWithTasks), input); + var myTask = context.ScheduleTask(typeof(GreetUserTask), "World"); + await Task.WhenAll(subOrch1, subOrch2, myTask); + return $"TaskResult = {myTask.Result} , SubOrchestration1Result = {subOrch1.Result}, SubOrchestration2Result = {subOrch2.Result}"; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTasks.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTasks.cs new file mode 100644 index 000000000..957139074 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTasks.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. +// ---------------------------------------------------------------------------------- + +namespace TestApplication.Common.Orchestrations +{ + using System.Threading.Tasks; + using DurableTask.Core; + + public class SimpleOrchestrationWithTasks : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + string user = await context.ScheduleTask(typeof(GetUserTask)); + + string greeting = await context.ScheduleTask(typeof(GreetUserTask), user); + + return greeting; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTimer.cs b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTimer.cs new file mode 100644 index 000000000..5880c23fd --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/Orchestrations/SimpleOrchestrationWithTimer.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 TestApplication.Common.Orchestrations +{ + using System; + using System.Threading.Tasks; + using DurableTask.Core; + + public class SimpleOrchestrationWithTimer : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, int input) + { + await context.CreateTimer(context.CurrentUtcDateTime.Add(TimeSpan.FromSeconds(input)), null); + return await context.ScheduleTask(typeof(GreetUserTask), "Gabbar"); + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj b/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj new file mode 100644 index 000000000..cb4801b4d --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj @@ -0,0 +1,18 @@ + + + + net461 + + + + + + + + + + + + + + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs b/Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs new file mode 100644 index 000000000..8352bbbf5 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web.Http.Dependencies; + + using Microsoft.Extensions.DependencyInjection; + + + public class DefaultDependencyResolver : IDependencyResolver + { + private readonly IServiceProvider provider; + + public DefaultDependencyResolver(IServiceProvider provider) + { + this.provider = provider; + } + + public object GetService(Type serviceType) + { + var obj = provider.GetService(serviceType); + Console.WriteLine($"{serviceType.Name} and {obj?.GetType().Name}"); + return obj; + } + + public IEnumerable GetServices(Type serviceType) + { + var objs = provider.GetServices(serviceType); + Console.WriteLine($"{serviceType.Name} and {objs?.Count()}"); + return objs; + } + + public IDependencyScope BeginScope() + { + return this; + } + + public void Dispose() + { + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs b/Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs new file mode 100644 index 000000000..4dd992911 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using Owin; + + public interface IOwinAppBuilder + { + void Configuration(IAppBuilder appBuilder); + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs b/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs new file mode 100644 index 000000000..c6fe9f3b2 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs @@ -0,0 +1,136 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Diagnostics; + using System.Fabric; + using System.Fabric.Description; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Owin.Hosting; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + + public class OwinCommunicationListener : ICommunicationListener + { + private readonly ServiceContext serviceContext; + + /// + /// OWIN server handle. + /// + private IDisposable serverHandle; + + private IOwinAppBuilder startup; + private string publishAddress; + private string listeningAddress; + private string appRoot; + + public OwinCommunicationListener(IOwinAppBuilder startup, ServiceContext serviceContext) + : this(null, startup, serviceContext) + { + } + + public OwinCommunicationListener(string appRoot, IOwinAppBuilder startup, ServiceContext serviceContext) + { + this.startup = startup; + this.appRoot = appRoot; + this.serviceContext = serviceContext; + } + + public Task OpenAsync(CancellationToken cancellationToken) + { + Trace.WriteLine("Initialize"); + + EndpointResourceDescription serviceEndpoint = this.serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); + int port = serviceEndpoint.Port; + + if (this.serviceContext is StatefulServiceContext) + { + StatefulServiceContext statefulInitParams = (StatefulServiceContext)this.serviceContext; + + this.listeningAddress = String.Format( + CultureInfo.InvariantCulture, + "http://+:{0}/", + port); + } + else if (this.serviceContext is StatelessServiceContext) + { + this.listeningAddress = String.Format( + CultureInfo.InvariantCulture, + "http://+:{0}/{1}", + port, + string.IsNullOrWhiteSpace(this.appRoot) + ? "" + : this.appRoot.TrimEnd('/') + '/'); + } + else + { + throw new InvalidOperationException(); + } + + this.publishAddress = this.listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); + + Trace.WriteLine(String.Format("Opening on {0}", this.publishAddress)); + + try + { + Trace.WriteLine(String.Format("Starting web server on {0}", this.listeningAddress)); + + this.serverHandle = WebApp.Start(this.listeningAddress, appBuilder => this.startup.Configuration(appBuilder)); + + return Task.FromResult(this.publishAddress); + } + catch (Exception ex) + { + Trace.WriteLine(ex); + + this.StopWebServer(); + + throw; + } + } + + public Task CloseAsync(CancellationToken cancellationToken) + { + Trace.WriteLine("Close"); + + this.StopWebServer(); + + return Task.FromResult(true); + } + + public void Abort() + { + Trace.WriteLine("Abort"); + + this.StopWebServer(); + } + + private void StopWebServer() + { + if (this.serverHandle != null) + { + try + { + this.serverHandle.Dispose(); + } + catch (ObjectDisposedException) + { + // no-op + } + } + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/Config/Settings.xml b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/Config/Settings.xml new file mode 100644 index 000000000..9535402bb --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/Config/Settings.xml @@ -0,0 +1,12 @@ + + + +
+ +
+ +
+ diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml new file mode 100644 index 000000000..b188283d4 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + TestApplication.StatefulService.exe + + + + + + + + + + + + + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs new file mode 100644 index 000000000..bc4d922d4 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Diagnostics; + using System.Threading; + using Microsoft.ServiceFabric.Services.Runtime; + + internal static class Program + { + /// + /// This is the entry point of the service host process. + /// + private static void Main() + { + try + { + // The ServiceManifest.XML file defines one or more service type names. + // Registering a service maps a service type name to a .NET type. + // When Service Fabric creates an instance of this service type, + // an instance of the class is created in this host process. + + ServiceRuntime.RegisterServiceAsync("StatefulServiceType", + context => new TestStatefulService(context)).GetAwaiter().GetResult(); + + ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(TestStatefulService).Name); + + // Prevents this host process from terminating so services keep running. + Thread.Sleep(Timeout.Infinite); + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + throw; + } + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs b/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs new file mode 100644 index 000000000..067da2a06 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs @@ -0,0 +1,189 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Diagnostics.Tracing; + using System.Threading.Tasks; + using Microsoft.ServiceFabric.Services.Runtime; + + [EventSource(Name = "MyCompany-TestFabricApplication-TestStatefulService")] + internal sealed class ServiceEventSource : EventSource + { + public static readonly ServiceEventSource Current = new ServiceEventSource(); + + static ServiceEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + // Instance constructor is private to enforce singleton semantics + private ServiceEventSource() : base() { } + + #region Keywords + // Event keywords can be used to categorize events. + // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). + // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. + public static class Keywords + { + public const EventKeywords Requests = (EventKeywords)0x1L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; + } + #endregion + + #region Events + // Define an instance method for each event you want to record and apply an [Event] attribute to it. + // The method name is the name of the event. + // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). + // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. + // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). + // Put [NonEvent] attribute on all methods that do not define an event. + // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx + + [NonEvent] + public void Message(string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + Message(finalMessage); + } + } + + private const int MessageEventId = 1; + [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] + public void Message(string message) + { + if (this.IsEnabled()) + { + WriteEvent(MessageEventId, message); + } + } + + [NonEvent] + public void ServiceMessage(StatefulService service, string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + ServiceMessage( + service.Context.ServiceName.ToString(), + service.Context.ServiceTypeName, + service.Context.ReplicaId, + service.Context.PartitionId, + service.Context.CodePackageActivationContext.ApplicationName, + service.Context.CodePackageActivationContext.ApplicationTypeName, + service.Context.NodeContext.NodeName, + finalMessage); + } + } + + // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. + // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. + // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. + private const int ServiceMessageEventId = 2; + [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] + private +#if UNSAFE + unsafe +#endif + void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { +#if !UNSAFE + WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); +#else + const int numArgs = 8; + fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) + { + EventData* eventData = stackalloc EventData[numArgs]; + eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; + eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; + eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; + eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; + eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; + eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; + eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; + eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; + + WriteEventCore(ServiceMessageEventId, numArgs, eventData); + } +#endif + } + + private const int ServiceTypeRegisteredEventId = 3; + [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)] + public void ServiceTypeRegistered(int hostProcessId, string serviceType) + { + WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); + } + + private const int ServiceHostInitializationFailedEventId = 4; + [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)] + public void ServiceHostInitializationFailed(string exception) + { + WriteEvent(ServiceHostInitializationFailedEventId, exception); + } + + // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. + // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, + // and other statistics. + private const int ServiceRequestStartEventId = 5; + [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] + public void ServiceRequestStart(string requestTypeName) + { + WriteEvent(ServiceRequestStartEventId, requestTypeName); + } + + private const int ServiceRequestStopEventId = 6; + [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] + public void ServiceRequestStop(string requestTypeName) + { + WriteEvent(ServiceRequestStopEventId, requestTypeName); + } + + private const int ServiceRequestFailedEventId = 7; + [Event(ServiceRequestFailedEventId, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] + public void ServiceRequestFailed(string requestTypeName, string exception) + { + WriteEvent(ServiceRequestFailedEventId, exception); + } + #endregion + + #region Private methods +#if UNSAFE + private int SizeInBytes(string s) + { + if (s == null) + { + return 0; + } + else + { + return (s.Length + 1) * sizeof(char); + } + } +#endif + #endregion + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs new file mode 100644 index 000000000..32a283932 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs @@ -0,0 +1,58 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System.Net; + using System.Web.Http; + + using DurableTask.Core; + using DurableTask.ServiceFabric; + using Microsoft.Extensions.DependencyInjection; + using Owin; + + class Startup : IOwinAppBuilder + { + FabricOrchestrationProvider fabricOrchestrationProvider; + + public Startup(FabricOrchestrationProvider fabricOrchestrationProvider) + { + this.fabricOrchestrationProvider = fabricOrchestrationProvider; + } + + public void Configuration(IAppBuilder appBuilder) + { + ServicePointManager.DefaultConnectionLimit = 256; + HttpConfiguration config = new HttpConfiguration(); + config.DependencyResolver = new DefaultDependencyResolver(GenerateServiceProvider()); + + config.Routes.MapHttpRoute( + name: "DurableTasks", + routeTemplate: "api/dtfx/{action}", + defaults: new { controller = "FabricOrchestrationService", id = RouteParameter.Optional } + ); + + config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; + appBuilder.UseWebApi(config); + } + + private ServiceProvider GenerateServiceProvider() + { + var services = new ServiceCollection(); + services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); + services.AddTransient(); + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider; + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj new file mode 100644 index 000000000..e0d4e1207 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj @@ -0,0 +1,27 @@ + + + + net461 + net461 + x64 + + Exe + + + + + + + + + + + + + + + + + + + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs new file mode 100644 index 000000000..ac6ee6bb3 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs @@ -0,0 +1,171 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.ServiceFabric; + using DurableTask.Test.Orchestrations.Performance; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + using TestApplication.Common.Orchestrations; + + /// + /// An instance of this class is created for each service replica by the Service Fabric runtime. + /// + internal sealed class TestStatefulService : StatefulService + { + readonly FabricOrchestrationProviderFactory fabricProviderFactory; + + FabricOrchestrationProvider fabricProvider; + TaskHubWorker worker; + TaskHubClient client; + ReplicaRole currentRole; + + public TestStatefulService(StatefulServiceContext context) : base(context) + { + var settings = new FabricOrchestrationProviderSettings(); + settings.TaskOrchestrationDispatcherSettings.DispatcherCount = 5; + settings.TaskActivityDispatcherSettings.DispatcherCount = 5; + + this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, settings); + this.fabricProvider = this.fabricProviderFactory.CreateProvider(); + this.client = new TaskHubClient(fabricProvider.OrchestrationServiceClient); + } + + /// + /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. + /// + /// + /// For more information on service communication, see http://aka.ms/servicefabricservicecommunication + /// + /// A collection of listeners. + protected override IEnumerable CreateServiceReplicaListeners() + { + return new[] + { + new ServiceReplicaListener(initParams => new OwinCommunicationListener("TestStatefulService", new Startup(this.fabricProvider), initParams)) + }; + } + + /// + /// This is the main entry point for your service replica. + /// This method executes when this replica of your service becomes primary and has write status. + /// + /// Canceled when Service Fabric needs to shut down this service replica. + protected override async Task RunAsync(CancellationToken cancellationToken) + { + if (this.fabricProvider == null) + { + this.fabricProvider = this.fabricProviderFactory.CreateProvider(); + this.client = new TaskHubClient(this.fabricProvider.OrchestrationServiceClient); + } + + this.worker = new TaskHubWorker(this.fabricProvider.OrchestrationService); + + await this.worker + .AddTaskOrchestrations(KnownOrchestrationInstances.Values.Select(instance => new DefaultObjectCreator(instance)).ToArray()) + .AddTaskOrchestrations(KnownOrchestrationTypeNames.Values.ToArray()) + .AddTaskActivities(KnownActivities) + .StartAsync(); + + this.worker.TaskActivityDispatcher.IncludeDetails = true; + } + + protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + ServiceEventSource.Current.ServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) + { + await ShutdownAsync(); + } + this.currentRole = newRole; + ServiceEventSource.Current.ServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); + } + + protected override async Task OnCloseAsync(CancellationToken cancellationToken) + { + ServiceEventSource.Current.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); + await ShutdownAsync(); + } + + async Task ShutdownAsync() + { + try + { + if (this.worker != null) + { + ServiceEventSource.Current.ServiceMessage(this, "Stopping Taskhub Worker"); + await this.worker.StopAsync(isForced: true); + this.worker.Dispose(); + this.fabricProvider.Dispose(); + this.fabricProvider = null; + this.worker = null; + ServiceEventSource.Current.ServiceMessage(this, "Stopped Taskhub Worker"); + } + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); + throw; + } + } + + Type GetOrchestrationType(string typeName) + { + if (KnownOrchestrationInstances.ContainsKey(typeName)) + { + return KnownOrchestrationInstances[typeName].GetType(); + } + + if (!KnownOrchestrationTypeNames.ContainsKey(typeName)) + { + throw new Exception($"Unknown Orchestration Type Name : {typeName}"); + } + + return KnownOrchestrationTypeNames[typeName]; + } + + static Dictionary KnownOrchestrationInstances = new Dictionary + { + { typeof(OrchestrationRunningIntoRetry).Name, new OrchestrationRunningIntoRetry() }, + }; + + static Dictionary KnownOrchestrationTypeNames = new Dictionary + { + { typeof(SimpleOrchestrationWithTasks).Name, typeof(SimpleOrchestrationWithTasks) }, + { typeof(SimpleOrchestrationWithTimer).Name, typeof(SimpleOrchestrationWithTimer) }, + { typeof(GenerationBasicOrchestration).Name, typeof(GenerationBasicOrchestration) }, + { typeof(SimpleOrchestrationWithSubOrchestration).Name, typeof(SimpleOrchestrationWithSubOrchestration) }, + { typeof(DriverOrchestration).Name, typeof(DriverOrchestration) }, + { typeof(TestOrchestration).Name, typeof(TestOrchestration) }, + { typeof(ExecutionCountingOrchestration).Name, typeof(ExecutionCountingOrchestration) }, + }; + + static Type[] KnownActivities = + { + typeof(GetUserTask), + typeof(GreetUserTask), + typeof(GenerationBasicTask), + typeof(RandomTimeWaitingTask), + typeof(ExceptionThrowingTask), + typeof(ExecutionCountingActivity), + }; + } +} diff --git a/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml new file mode 100644 index 000000000..8b9eb8af2 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml new file mode 100644 index 000000000..683ffaa5b --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.1Node.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.1Node.xml new file mode 100644 index 000000000..3411afa5b --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.1Node.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml new file mode 100644 index 000000000..683ffaa5b --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/PublishProfiles/Cloud.xml b/Test/TestFabricApplication/TestApplication/PublishProfiles/Cloud.xml new file mode 100644 index 000000000..1301dd83f --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/PublishProfiles/Cloud.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.1Node.xml b/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.1Node.xml new file mode 100644 index 000000000..6e1403e96 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.1Node.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.5Node.xml b/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.5Node.xml new file mode 100644 index 000000000..f42d759c3 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/PublishProfiles/Local.5Node.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/Scripts/Deploy-FabricApplication.ps1 b/Test/TestFabricApplication/TestApplication/Scripts/Deploy-FabricApplication.ps1 new file mode 100644 index 000000000..2897b10e8 --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/Scripts/Deploy-FabricApplication.ps1 @@ -0,0 +1,258 @@ +<# +.SYNOPSIS +Deploys a Service Fabric application type to a cluster. + +.DESCRIPTION +This script deploys a Service Fabric application type to a cluster. It is invoked by Visual Studio when deploying a Service Fabric Application project. + +.NOTES +WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary. + +.PARAMETER PublishProfileFile +Path to the file containing the publish profile. + +.PARAMETER ApplicationPackagePath +Path to the folder of the packaged Service Fabric application. + +.PARAMETER DeployOnly +Indicates that the Service Fabric application should not be created or upgraded after registering the application type. + +.PARAMETER ApplicationParameter +Hashtable of the Service Fabric application parameters to be used for the application. + +.PARAMETER UnregisterUnusedApplicationVersionsAfterUpgrade +Indicates whether to unregister any unused application versions that exist after an upgrade is finished. + +.PARAMETER OverrideUpgradeBehavior +Indicates the behavior used to override the upgrade settings specified by the publish profile. +'None' indicates that the upgrade settings will not be overridden. +'ForceUpgrade' indicates that an upgrade will occur with default settings, regardless of what is specified in the publish profile. +'VetoUpgrade' indicates that an upgrade will not occur, regardless of what is specified in the publish profile. + +.PARAMETER UseExistingClusterConnection +Indicates that the script should make use of an existing cluster connection that has already been established in the PowerShell session. The cluster connection parameters configured in the publish profile are ignored. + +.PARAMETER OverwriteBehavior +Overwrite Behavior if an application exists in the cluster with the same name. Available Options are Never, Always, SameAppTypeAndVersion. This setting is not applicable when upgrading an application. +'Never' will not remove the existing application. This is the default behavior. +'Always' will remove the existing application even if its Application type and Version is different from the application being created. +'SameAppTypeAndVersion' will remove the existing application only if its Application type and Version is same as the application being created. + +.PARAMETER SkipPackageValidation +Switch signaling whether the package should be validated or not before deployment. + +.PARAMETER SecurityToken +A security token for authentication to cluster management endpoints. Used for silent authentication to clusters that are protected by Azure Active Directory. + +.PARAMETER CopyPackageTimeoutSec +Timeout in seconds for copying application package to image store. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' + +Deploy the application using the default package location for a Debug build. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -DoNotCreateApplication + +Deploy the application but do not create the application instance. + +.EXAMPLE +. Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'} + +Deploy the application by providing values for parameters that are defined in the application manifest. +#> + +Param +( + [String] + $PublishProfileFile, + + [String] + $ApplicationPackagePath, + + [Switch] + $DeployOnly, + + [Hashtable] + $ApplicationParameter, + + [Boolean] + $UnregisterUnusedApplicationVersionsAfterUpgrade, + + [String] + [ValidateSet('None', 'ForceUpgrade', 'VetoUpgrade')] + $OverrideUpgradeBehavior = 'None', + + [Switch] + $UseExistingClusterConnection, + + [String] + [ValidateSet('Never','Always','SameAppTypeAndVersion')] + $OverwriteBehavior = 'Never', + + [Switch] + $SkipPackageValidation, + + [String] + $SecurityToken, + + [int] + $CopyPackageTimeoutSec +) + +function Read-XmlElementAsHashtable +{ + Param ( + [System.Xml.XmlElement] + $Element + ) + + $hashtable = @{} + if ($Element.Attributes) + { + $Element.Attributes | + ForEach-Object { + $boolVal = $null + if ([bool]::TryParse($_.Value, [ref]$boolVal)) { + $hashtable[$_.Name] = $boolVal + } + else { + $hashtable[$_.Name] = $_.Value + } + } + } + + return $hashtable +} + +function Read-PublishProfile +{ + Param ( + [ValidateScript({Test-Path $_ -PathType Leaf})] + [String] + $PublishProfileFile + ) + + $publishProfileXml = [Xml] (Get-Content $PublishProfileFile) + $publishProfile = @{} + + $publishProfile.ClusterConnectionParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("ClusterConnectionParameters") + $publishProfile.UpgradeDeployment = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment") + $publishProfile.CopyPackageParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("CopyPackageParameters") + + if ($publishProfileXml.PublishProfile.Item("UpgradeDeployment")) + { + $publishProfile.UpgradeDeployment.Parameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment").Item("Parameters") + if ($publishProfile.UpgradeDeployment["Mode"]) + { + $publishProfile.UpgradeDeployment.Parameters[$publishProfile.UpgradeDeployment["Mode"]] = $true + } + } + + $publishProfileFolder = (Split-Path $PublishProfileFile) + $publishProfile.ApplicationParameterFile = [System.IO.Path]::Combine($PublishProfileFolder, $publishProfileXml.PublishProfile.ApplicationParameterFile.Path) + + return $publishProfile +} + +$LocalFolder = (Split-Path $MyInvocation.MyCommand.Path) + +if (!$PublishProfileFile) +{ + $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml" +} + +if (!$ApplicationPackagePath) +{ + $ApplicationPackagePath = "$LocalFolder\..\pkg\Release" +} + +$ApplicationPackagePath = Resolve-Path $ApplicationPackagePath + +$publishProfile = Read-PublishProfile $PublishProfileFile + +if (-not $UseExistingClusterConnection) +{ + $ClusterConnectionParameters = $publishProfile.ClusterConnectionParameters + if ($SecurityToken) + { + $ClusterConnectionParameters["SecurityToken"] = $SecurityToken + } + + try + { + [void](Connect-ServiceFabricCluster @ClusterConnectionParameters) + } + catch [System.Fabric.FabricObjectClosedException] + { + Write-Warning "Service Fabric cluster may not be connected." + throw + } +} + +$RegKey = "HKLM:\SOFTWARE\Microsoft\Service Fabric SDK" +$ModuleFolderPath = (Get-ItemProperty -Path $RegKey -Name FabricSDKPSModulePath).FabricSDKPSModulePath +Import-Module "$ModuleFolderPath\ServiceFabricSDK.psm1" + +$IsUpgrade = ($publishProfile.UpgradeDeployment -and $publishProfile.UpgradeDeployment.Enabled -and $OverrideUpgradeBehavior -ne 'VetoUpgrade') -or $OverrideUpgradeBehavior -eq 'ForceUpgrade' + +$PublishParameters = @{ + 'ApplicationPackagePath' = $ApplicationPackagePath + 'ApplicationParameterFilePath' = $publishProfile.ApplicationParameterFile + 'ApplicationParameter' = $ApplicationParameter + 'ErrorAction' = 'Stop' +} + +if ($publishProfile.CopyPackageParameters.CopyPackageTimeoutSec) +{ + $PublishParameters['CopyPackageTimeoutSec'] = $publishProfile.CopyPackageParameters.CopyPackageTimeoutSec +} + +if ($publishProfile.CopyPackageParameters.CompressPackage) +{ + $PublishParameters['CompressPackage'] = $publishProfile.CopyPackageParameters.CompressPackage +} + +# CopyPackageTimeoutSec parameter overrides the value from the publish profile +if ($CopyPackageTimeoutSec) +{ + $PublishParameters['CopyPackageTimeoutSec'] = $CopyPackageTimeoutSec +} + +if ($IsUpgrade) +{ + $Action = "RegisterAndUpgrade" + if ($DeployOnly) + { + $Action = "Register" + } + + $UpgradeParameters = $publishProfile.UpgradeDeployment.Parameters + + if ($OverrideUpgradeBehavior -eq 'ForceUpgrade') + { + # Warning: Do not alter these upgrade parameters. It will create an inconsistency with Visual Studio's behavior. + $UpgradeParameters = @{ UnmonitoredAuto = $true; Force = $true } + } + + $PublishParameters['Action'] = $Action + $PublishParameters['UpgradeParameters'] = $UpgradeParameters + $PublishParameters['UnregisterUnusedVersions'] = $UnregisterUnusedApplicationVersionsAfterUpgrade + + Publish-UpgradedServiceFabricApplication @PublishParameters +} +else +{ + $Action = "RegisterAndCreate" + if ($DeployOnly) + { + $Action = "Register" + } + + $PublishParameters['Action'] = $Action + $PublishParameters['OverwriteBehavior'] = $OverwriteBehavior + $PublishParameters['SkipPackageValidation'] = $SkipPackageValidation + + Publish-NewServiceFabricApplication @PublishParameters +} \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj new file mode 100644 index 000000000..bc65629ae --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj @@ -0,0 +1,47 @@ + + + + + 1d52f94e-933c-411f-96c4-4960b423586f + 2.4 + 1.5 + 1.6.7 + v4.6.1 + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Service Fabric Tools\Microsoft.VisualStudio.Azure.Fabric.ApplicationProject.targets + + + + + + + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/packages.config b/Test/TestFabricApplication/TestApplication/packages.config new file mode 100644 index 000000000..45379443b --- /dev/null +++ b/Test/TestFabricApplication/TestApplication/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/DurableTask.Core/History/ExecutionStartedEvent.cs b/src/DurableTask.Core/History/ExecutionStartedEvent.cs index 764c9b646..e022678cd 100644 --- a/src/DurableTask.Core/History/ExecutionStartedEvent.cs +++ b/src/DurableTask.Core/History/ExecutionStartedEvent.cs @@ -38,6 +38,13 @@ public ExecutionStartedEvent(int eventId, string input) Input = input; } + /// + /// Creates a new ExecutionStartedEvent + /// + internal ExecutionStartedEvent() + { + } + /// /// Gets the event type /// diff --git a/src/DurableTask.Core/History/HistoryEvent.cs b/src/DurableTask.Core/History/HistoryEvent.cs index 5b9f4ae72..e1b768070 100644 --- a/src/DurableTask.Core/History/HistoryEvent.cs +++ b/src/DurableTask.Core/History/HistoryEvent.cs @@ -39,6 +39,16 @@ namespace DurableTask.Core.History [KnownType(typeof(HistoryStateEvent))] public abstract class HistoryEvent : IExtensibleDataObject { + + /// + /// Creates a new history event + /// + internal HistoryEvent() + { + IsPlayed = false; + Timestamp = DateTime.UtcNow; + } + /// /// Creates a new history event with the supplied event id /// @@ -54,7 +64,7 @@ protected HistoryEvent(int eventId) /// Gets the event id /// [DataMember] - public int EventId { get; private set; } + public int EventId { get; internal set; } /// /// Gets the IsPlayed status diff --git a/src/DurableTask.Core/TaskMessage.cs b/src/DurableTask.Core/TaskMessage.cs index 64bc8ff29..ac8175630 100644 --- a/src/DurableTask.Core/TaskMessage.cs +++ b/src/DurableTask.Core/TaskMessage.cs @@ -26,19 +26,19 @@ public class TaskMessage : IExtensibleDataObject /// Event information for this task message /// [DataMember] - public HistoryEvent Event; + public HistoryEvent Event { get; set; } /// /// Sequence number for ordering of messages in history tracking /// [DataMember] - public long SequenceNumber; + public long SequenceNumber { get; set; } /// /// The orchestration instance information /// [DataMember] - public OrchestrationInstance OrchestrationInstance; + public OrchestrationInstance OrchestrationInstance { get; set; } /// /// Implementation for . diff --git a/src/DurableTask.ServiceFabric/AssemblyInfo.cs b/src/DurableTask.ServiceFabric/AssemblyInfo.cs new file mode 100644 index 000000000..f179965ce --- /dev/null +++ b/src/DurableTask.ServiceFabric/AssemblyInfo.cs @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------------------------- +// 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.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +#if !SIGN_ASSEMBLY +[assembly: InternalsVisibleTo("DurableTask.ServiceFabric.Tests")] +#endif diff --git a/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs b/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs new file mode 100644 index 000000000..36cdf3844 --- /dev/null +++ b/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs @@ -0,0 +1,59 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + sealed class AsyncManualResetEvent + { + TaskCompletionSource tcs; + + public AsyncManualResetEvent() + { + this.tcs = new TaskCompletionSource(); + } + + public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + if (timeout < TimeSpan.Zero) + { + timeout = TimeSpan.Zero; + } + + var delayTask = Task.Delay(timeout, cancellationToken); + var resultTask = await Task.WhenAny(delayTask, this.tcs.Task); + return delayTask != resultTask; + } + + public void Set() + { + this.tcs.TrySetResult(true); + } + + public void Reset() + { + while (true) + { + var thisTcs = this.tcs; + + if (!thisTcs.Task.IsCompleted || Interlocked.CompareExchange(ref this.tcs, new TaskCompletionSource(), thisTcs) == thisTcs) + { + return; + } + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/Constants.cs b/src/DurableTask.ServiceFabric/Constants.cs new file mode 100644 index 000000000..f46fccde0 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Constants.cs @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + static class Constants + { + internal const string CollectionNameUniquenessPrefix = "DtfxSfp_"; + internal const string SessionMessagesDictionaryPrefix = CollectionNameUniquenessPrefix + "SessionMessages_"; + internal const string OrchestrationDictionaryName = CollectionNameUniquenessPrefix + "Orchestrations"; + internal const string ActivitiesQueueName = CollectionNameUniquenessPrefix + "Activities"; + internal const string InstanceStoreDictionaryName = CollectionNameUniquenessPrefix + "InstanceStore"; + internal const string ExecutionStoreDictionaryName = CollectionNameUniquenessPrefix + "ExecutionIdStore"; + internal const string ScheduledMessagesDictionaryName = CollectionNameUniquenessPrefix + "ScheduledMessages"; + } +} diff --git a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj new file mode 100644 index 000000000..6516e3020 --- /dev/null +++ b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj @@ -0,0 +1,28 @@ + + + + + net461 + true + Microsoft.Azure.DurableTask.ServiceFabric + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs new file mode 100644 index 000000000..7b6117548 --- /dev/null +++ b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Fabric; + + using DurableTask.ServiceFabric.Tracing; + + static class ExceptionUtilities + { + public static bool IsRetryableFabricException(Exception e) + { + return e is TimeoutException || e is FabricTransientException; + } + + public static void LogReliableCollectionException(string uniqueIdentifier, int attemptNumber, Exception e, bool isTransient) + { + if (isTransient) + { + ProviderEventSource.Log.RetryableFabricException(uniqueIdentifier, attemptNumber, e.ToString()); + } + else + { + ProviderEventSource.Log.ExceptionInReliableCollectionOperations(uniqueIdentifier, e.ToString()); + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs new file mode 100644 index 000000000..9fb02df59 --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs @@ -0,0 +1,362 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.Core.Tracking; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + using Microsoft.ServiceFabric.Data.Collections; + + // Potential todo: + // - Support for querying state across executions (makes sense only after ContinuedAsNew is supported) + // - Support writing multiple state events for a given orchestration instance/execution (?) + // - Support writing/querying/purging history events + class FabricOrchestrationInstanceStore : IFabricOrchestrationServiceInstanceStore + { + const string InstanceStoreCollectionNamePrefix = Constants.CollectionNameUniquenessPrefix + "InstSt_"; + const string TimeFormatString = "yyyy-MM-dd-HH"; + const string TimeFormatStringPrefix = "yyyy-MM-dd-"; + readonly IReliableStateManager stateManager; + readonly CancellationToken cancellationToken; + readonly ConcurrentDictionary orchestrationWaiters = new ConcurrentDictionary(OrchestrationInstanceComparer.Default); + + IReliableDictionary instanceStore; + IReliableDictionary executionIdStore; + + public FabricOrchestrationInstanceStore(IReliableStateManager stateManager, CancellationToken token) + { + this.stateManager = stateManager; + this.cancellationToken = token; + } + + public Task InitializeStoreAsync(bool recreate) + { + if (recreate) + { + return DeleteStoreAsync(); + } + + return Task.CompletedTask; + } + + public async Task StartAsync() + { + await EnsureStoreInitialized(); + var nowait = CleanupOldDictionaries(); + } + + public async Task DeleteStoreAsync() + { + await this.stateManager.RemoveAsync(Constants.InstanceStoreDictionaryName); + await this.stateManager.RemoveAsync(Constants.ExecutionStoreDictionaryName); + } + + public async Task WriteEntitesAsync(ITransaction transaction, IEnumerable entities) + { + await EnsureStoreInitialized(); + foreach (var entity in entities) + { + var state = entity as OrchestrationStateInstanceEntity; + if (state != null && state.State != null) + { + var instance = state.State.OrchestrationInstance; + string key = GetKey(instance.InstanceId, instance.ExecutionId); + + if (state.State.OrchestrationStatus.IsRunningOrPending()) + { + if (state.State.OrchestrationStatus == OrchestrationStatus.Pending) + { + await this.executionIdStore.AddOrUpdateAsync(transaction, instance.InstanceId, instance.ExecutionId, (k, old) => instance.ExecutionId); + } + await this.instanceStore.AddOrUpdateAsync(transaction, key, state.State, + (k, oldValue) => state.State); + } + else + { + var backupDictionaryName = GetDictionaryKeyFromTimeFormat(DateTime.UtcNow); + // It's intentional to not pass 'transaction' parameter to this call, this API doesn't seem to follow + // see-what-you-commit-within-the-transaction rules. If we add it within the transaction and immediately + // try to AddOrUpdateAsync an entry in dictionary that doesn't work. + var backupDictionary = await this.stateManager.GetOrAddAsync>(backupDictionaryName); + await backupDictionary.AddOrUpdateAsync(transaction, key, state.State, + (k, oldValue) => state.State); + await this.instanceStore.TryRemoveAsync(transaction, key); + } + } + else + { + throw new NotSupportedException(); + } + } + } + + public async Task> GetOrchestrationStateAsync(string instanceId, bool allInstances) + { + if (allInstances) + { + throw new NotImplementedException("Querying for state across all executions for an orchestration is not supported, only the latest execution can be queried"); + } + + await EnsureStoreInitialized(); + + string latestExecutionId = await GetLatestExecutionId(instanceId); + + if (latestExecutionId != null) + { + var state = await GetOrchestrationStateAsync(instanceId, latestExecutionId); + if (state != null) + { + return new List() { state }; + } + } + + return ImmutableList.Empty; + } + + public async Task GetLatestExecutionId(string instanceId) + { + string latestExecutionId = null; + + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var tx = this.stateManager.CreateTransaction()) + { + var executionIdValue = await this.executionIdStore.TryGetValueAsync(tx, instanceId); + if (executionIdValue.HasValue) + { + latestExecutionId = executionIdValue.Value; + } + } + }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetLatestExecutionId)}"); + + return latestExecutionId; + } + + public async Task GetOrchestrationStateAsync(string instanceId, string executionId) + { + await EnsureStoreInitialized(); + var queryKey = this.GetKey(instanceId, executionId); + var result = await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.stateManager.CreateTransaction()) + { + var state = await this.instanceStore.TryGetValueAsync(txn, queryKey); + if (state.HasValue) + { + return new OrchestrationStateInstanceEntity() + { + State = state.Value, + }; + } + } + return null; + }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, ExecutionId = {executionId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetOrchestrationStateAsync)}:QueryInstanceStore"); + + if (result != null) + { + return result; + } + + // If querying for orchestration which completed an hour ago, we won't return the results. + var now = DateTime.UtcNow; + for (int i = 0; i < 2; i++) + { + var backupDictionaryName = GetDictionaryKeyFromTimeFormat(now - TimeSpan.FromHours(i)); + var backupDictionary = await this.stateManager.TryGetAsync>(backupDictionaryName); + + if (backupDictionary.HasValue) + { + result = await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.stateManager.CreateTransaction()) + { + var state = await backupDictionary.Value.TryGetValueAsync(txn, queryKey); + if (state.HasValue) + { + return new OrchestrationStateInstanceEntity() + { + State = state.Value, + }; + } + } + + return null; + }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, ExecutionId = {executionId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetOrchestrationStateAsync)}:QueryBackupInstanceStore {backupDictionaryName}"); + + if (result != null) + { + return result; + } + } + } + + return null; + } + + public Task> GetOrchestrationHistoryEventsAsync(string instanceId, string executionId) + { + throw new NotImplementedException(); + } + + // Todo: This is incomplete and inaccurate implemenation done for testing purposes. + // The method will cleanup state for every orchestration happening in the hour time window of given time, + // for example, if the given time is 9.35, it will delete state for all orchestrations that + // are completed between 9.00 to 9.59!!! + public Task PurgeOrchestrationHistoryEventsAsync(DateTime threshholdHourlyDateTimeUtc) + { + return this.stateManager.RemoveAsync(GetDictionaryKeyFromTimeFormat(threshholdHourlyDateTimeUtc)); + } + + string GetKey(string instanceId, string executionId) + { + return string.Concat(instanceId, "_", executionId); + } + + string GetDictionaryKeyFromTimeFormat(DateTime time) + { + return GetInstanceStoreBackupDictionaryKey(time, TimeFormatString); + } + + string GetDictionaryKeyFromTimePrefixFormat(DateTime time) + { + return GetInstanceStoreBackupDictionaryKey(time, TimeFormatStringPrefix); + } + + string GetInstanceStoreBackupDictionaryKey(DateTime time, string formatString) + { + return InstanceStoreCollectionNamePrefix + time.ToString(formatString); + } + + Task CleanupDayOldDictionaries() + { + return Utils.RunBackgroundJob(async () => + { + var purgeTime = GetDictionaryKeyFromTimePrefixFormat(DateTime.UtcNow - TimeSpan.FromDays(1)); + + for (int i = 0; i < 24; i++) + { + await this.stateManager.RemoveAsync($"{purgeTime}{i:D2}"); + } + }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(12), delayOnException: TimeSpan.FromHours(1), actionName: $"{nameof(CleanupDayOldDictionaries)}", token: this.cancellationToken); + } + + Task CleanupOldDictionaries() + { + return Utils.RunBackgroundJob(async () => + { + List toDelete = new List(); + List toKeep = new List(); + var currentTime = DateTime.UtcNow; + var ttl = TimeSpan.FromDays(1); + + var enumerationTime = await Utils.MeasureAsync(async () => + { + var enumerator = this.stateManager.GetAsyncEnumerator(); + while (await enumerator.MoveNextAsync(this.cancellationToken)) + { + var storeName = enumerator.Current.Name.AbsolutePath.Trim('/'); + if (storeName.StartsWith(InstanceStoreCollectionNamePrefix)) + { + DateTime storeTime; + if (DateTime.TryParseExact(storeName.Substring(InstanceStoreCollectionNamePrefix.Length), TimeFormatString, CultureInfo.InvariantCulture, DateTimeStyles.None, out storeTime) + && (currentTime - storeTime > ttl)) + { + toDelete.Add(storeName); + continue; + } + } + + toKeep.Add(storeName); + } + }); + ProviderEventSource.Log.LogTimeTaken($"Enumerating all reliable states (count: {toDelete.Count + toKeep.Count})", enumerationTime.TotalMilliseconds); + + ProviderEventSource.Log.ReliableStateManagement($"Deleting {toDelete.Count} stores", String.Join(",", toDelete)); + ProviderEventSource.Log.ReliableStateManagement($"All remaining {toKeep.Count} stores", String.Join(",", toKeep)); + + foreach (var storeName in toDelete) + { + var deleteTime = await Utils.MeasureAsync(async () => + { + await this.stateManager.RemoveAsync(storeName); + }); + ProviderEventSource.Log.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); + } + }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(1), delayOnException: TimeSpan.FromMinutes(10), actionName: $"{nameof(CleanupOldDictionaries)}", token: this.cancellationToken); + } + + async Task EnsureStoreInitialized() + { + if (this.instanceStore == null) + { + this.instanceStore = await this.stateManager.GetOrAddAsync>(Constants.InstanceStoreDictionaryName); + } + if (this.executionIdStore == null) + { + this.executionIdStore = await this.stateManager.GetOrAddAsync>(Constants.ExecutionStoreDictionaryName); + } + } + + public async Task WaitForOrchestrationAsync(OrchestrationInstance instance, TimeSpan timeout) + { + var currentState = await this.GetOrchestrationStateAsync(instance.InstanceId, instance.ExecutionId); + + // If querying state for an orchestration that's not started or completed and state cleaned up, we will immediately return null. + if (currentState?.State == null) + { + return null; + } + + if (currentState.State.OrchestrationStatus.IsTerminalState()) + { + return currentState; + } + + var waiter = this.orchestrationWaiters.GetOrAdd(instance, new AsyncManualResetEvent()); + bool completed = await waiter.WaitAsync(timeout, this.cancellationToken); + + if (!completed) + { + this.orchestrationWaiters.TryRemove(instance, out waiter); + } + + currentState = await this.GetOrchestrationStateAsync(instance.InstanceId, instance.ExecutionId); + if (currentState?.State != null && currentState.State.OrchestrationStatus.IsTerminalState()) + { + return currentState; + } + + return null; + } + + public void OnOrchestrationCompleted(OrchestrationInstance instance) + { + AsyncManualResetEvent resetEvent; + if (this.orchestrationWaiters.TryRemove(instance, out resetEvent)) + { + resetEvent.Set(); + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs new file mode 100644 index 000000000..07b03d7a8 --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Threading; + + using DurableTask.Core; + using DurableTask.ServiceFabric.Stores; + using Microsoft.ServiceFabric.Data; + + /// + /// Manages instances of a service fabric based store provider implementations + /// for and + /// to be used in constructing and . + /// + /// + /// Use to create an instance of . + /// Note that this provider object should not be used once method is called + /// on the worker object created using this provider. A new provider object should be created after that point. + /// + public sealed class FabricOrchestrationProvider : IDisposable + { + readonly FabricOrchestrationService orchestrationService; + readonly FabricOrchestrationServiceClient orchestrationClient; + readonly FabricProviderClient fabricProviderClient; + readonly CancellationTokenSource cancellationTokenSource; + + internal FabricOrchestrationProvider(IReliableStateManager stateManager, FabricOrchestrationProviderSettings settings) + { + this.cancellationTokenSource = new CancellationTokenSource(); + var sessionsProvider = new SessionsProvider(stateManager, cancellationTokenSource.Token); + var instanceStore = new FabricOrchestrationInstanceStore(stateManager, cancellationTokenSource.Token); + this.orchestrationService = new FabricOrchestrationService(stateManager, sessionsProvider, instanceStore, settings, cancellationTokenSource); + this.orchestrationClient = new FabricOrchestrationServiceClient(stateManager, sessionsProvider, instanceStore); + this.fabricProviderClient = new FabricProviderClient(stateManager, sessionsProvider); + } + + /// + /// instance that can be used for constructing . + /// + public IOrchestrationService OrchestrationService + { + get + { + EnsureValidInstance(); + return this.orchestrationService; + } + } + + /// + /// instance that can be used for constructing . + /// + public IOrchestrationServiceClient OrchestrationServiceClient + { + get + { + EnsureValidInstance(); + return this.orchestrationClient; + } + } + + /// + /// An instance of . + /// + public IFabricProviderClient FabricProviderClient + { + get + { + EnsureValidInstance(); + return this.fabricProviderClient; + } + } + + /// + /// Disposes the object. The object should be disposed after + /// is invoked on the created with the . + /// + public void Dispose() + { + this.cancellationTokenSource.Dispose(); + } + + void EnsureValidInstance() + { + if (this.cancellationTokenSource.IsCancellationRequested) + { + throw new InvalidOperationException($"{nameof(IOrchestrationService)} instance has been stopped. Discard this provider instance and create a new provider object."); + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs new file mode 100644 index 000000000..e2f5c4872 --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using Microsoft.ServiceFabric.Data; + + /// + /// Factory to create instances of . + /// + public class FabricOrchestrationProviderFactory + { + readonly IReliableStateManager stateManager; + readonly FabricOrchestrationProviderSettings settings; + + /// + /// Constructor that uses default . + /// + /// Reliable state manager instance. Comes from service fabric stateful service implementation. + public FabricOrchestrationProviderFactory(IReliableStateManager stateManager) + : this(stateManager, new FabricOrchestrationProviderSettings()) + { + } + + /// + /// Constructor that takes the custom . + /// + /// Reliable state manager instance. Comes from service fabric stateful service implementation. + /// Settings to be used for the provider. Refer to for documentation about specific settings. + public FabricOrchestrationProviderFactory(IReliableStateManager stateManager, FabricOrchestrationProviderSettings settings) + { + if (stateManager == null) + { + throw new ArgumentNullException(nameof(stateManager)); + } + + this.stateManager = stateManager; + this.settings = settings ?? new FabricOrchestrationProviderSettings(); + } + + /// + /// Creates a new object using the factory's state manager and settings. + /// + public FabricOrchestrationProvider CreateProvider() + { + return new FabricOrchestrationProvider(this.stateManager, this.settings); + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs new file mode 100644 index 000000000..c2b87677d --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using DurableTask.Core; + using DurableTask.Core.Settings; + + /// + /// Provides settings for service fabric based custom provider implementations + /// for and + /// to be used in constructing and . + /// + public sealed class FabricOrchestrationProviderSettings + { + /// + /// Constructor. Initializes all settings to their default values. + /// + public FabricOrchestrationProviderSettings() + { + TaskOrchestrationDispatcherSettings = new TaskOrchestrationDispatcherSettings() + { + MaxConcurrentOrchestrations = 1000, + DispatcherCount = 10 + }; + + TaskActivityDispatcherSettings = new TaskActivityDispatcherSettings() + { + MaxConcurrentActivities = 1000, + DispatcherCount = 10 + }; + } + + /// + /// Settings to configure the Task Orchestration Dispatcher + /// + public TaskOrchestrationDispatcherSettings TaskOrchestrationDispatcherSettings { get; set; } + + /// + /// Settings to configure the Task Activity Dispatcher + /// + public TaskActivityDispatcherSettings TaskActivityDispatcherSettings { get; set; } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs new file mode 100644 index 000000000..78b92f23c --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs @@ -0,0 +1,528 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.Core.History; + using DurableTask.Core.Tracking; + using DurableTask.ServiceFabric.Stores; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + + class FabricOrchestrationService : IOrchestrationService + { + readonly IReliableStateManager stateManager; + readonly IFabricOrchestrationServiceInstanceStore instanceStore; + readonly SessionsProvider orchestrationProvider; + readonly ActivityProvider activitiesProvider; + readonly ScheduledMessageProvider scheduledMessagesProvider; + readonly FabricOrchestrationProviderSettings settings; + readonly CancellationTokenSource cancellationTokenSource; + + ConcurrentDictionary sessionInfos = new ConcurrentDictionary(); + + public FabricOrchestrationService(IReliableStateManager stateManager, + SessionsProvider orchestrationProvider, + IFabricOrchestrationServiceInstanceStore instanceStore, + FabricOrchestrationProviderSettings settings, + CancellationTokenSource cancellationTokenSource) + { + this.stateManager = stateManager ?? throw new ArgumentNullException(nameof(stateManager)); + this.orchestrationProvider = orchestrationProvider; + this.instanceStore = instanceStore; + this.settings = settings; + this.cancellationTokenSource = cancellationTokenSource; + this.activitiesProvider = new ActivityProvider(this.stateManager, Constants.ActivitiesQueueName, cancellationTokenSource.Token); + this.scheduledMessagesProvider = new ScheduledMessageProvider(this.stateManager, Constants.ScheduledMessagesDictionaryName, orchestrationProvider, cancellationTokenSource.Token); + } + + public Task StartAsync() + { + return Task.WhenAll(this.activitiesProvider.StartAsync(), + this.scheduledMessagesProvider.StartAsync(), + this.instanceStore.StartAsync(), + this.orchestrationProvider.StartAsync()); + } + + public Task StopAsync() + { + return StopAsync(false); + } + + public Task StopAsync(bool isForced) + { + if (!this.cancellationTokenSource.IsCancellationRequested) + { + this.cancellationTokenSource.Cancel(); + } + + return Task.CompletedTask; + } + + public Task CreateAsync() + { + return CreateAsync(true); + } + + public Task CreateAsync(bool recreateInstanceStore) + { + return DeleteAsync(deleteInstanceStore: recreateInstanceStore); + // Actual creation will be done on demand when we call GetOrAddAsync in StartAsync method. + } + + public Task CreateIfNotExistsAsync() + { + return Task.CompletedTask; + } + + public Task DeleteAsync() + { + return DeleteAsync(true); + } + + public Task DeleteAsync(bool deleteInstanceStore) + { + List tasks = new List(); + tasks.Add(this.stateManager.RemoveAsync(Constants.OrchestrationDictionaryName)); + tasks.Add(this.stateManager.RemoveAsync(Constants.ScheduledMessagesDictionaryName)); + tasks.Add(this.stateManager.RemoveAsync(Constants.ActivitiesQueueName)); + + if (deleteInstanceStore) + { + tasks.Add(this.stateManager.RemoveAsync(Constants.InstanceStoreDictionaryName)); + } + + return Task.WhenAll(tasks); + } + + public bool IsMaxMessageCountExceeded(int currentMessageCount, OrchestrationRuntimeState runtimeState) + { + return false; + } + + public int GetDelayInSecondsAfterOnProcessException(Exception exception) + { + return GetDelayForFetchOrProcessException(exception); + } + + public int GetDelayInSecondsAfterOnFetchException(Exception exception) + { + return GetDelayForFetchOrProcessException(exception); + } + + public int TaskOrchestrationDispatcherCount => this.settings.TaskOrchestrationDispatcherSettings.DispatcherCount; + public int MaxConcurrentTaskOrchestrationWorkItems => this.settings.TaskOrchestrationDispatcherSettings.MaxConcurrentOrchestrations; + + // Note: Do not rely on cancellationToken parameter to this method because the top layer does not yet implement any cancellation. + public async Task LockNextTaskOrchestrationWorkItemAsync(TimeSpan receiveTimeout, CancellationToken cancellationToken) + { + var currentSession = await this.orchestrationProvider.AcceptSessionAsync(receiveTimeout); + + if (currentSession == null) + { + return null; + } + + List> newMessages; + try + { + newMessages = await this.orchestrationProvider.ReceiveSessionMessagesAsync(currentSession); + + var currentRuntimeState = new OrchestrationRuntimeState(currentSession.SessionState); + var workItem = new TaskOrchestrationWorkItem() + { + NewMessages = newMessages.Select(m => m.Value.TaskMessage).ToList(), + InstanceId = currentSession.SessionId.InstanceId, + OrchestrationRuntimeState = currentRuntimeState + }; + + if (newMessages.Count == 0) + { + if (currentRuntimeState.ExecutionStartedEvent == null) + { + ProviderEventSource.Log.UnexpectedCodeCondition($"Orchestration with no execution started event found: {currentSession.SessionId}"); + return null; + } + + bool isComplete = currentRuntimeState.OrchestrationStatus.IsTerminalState(); + if (isComplete) + { + await this.HandleCompletedOrchestration(workItem); + } + + this.orchestrationProvider.TryUnlockSession(currentSession.SessionId, isComplete: isComplete); + return null; + } + + var sessionInfo = new SessionInformation() + { + Instance = currentSession.SessionId, + LockTokens = newMessages.Select(m => m.Key).ToList() + }; + + if (!this.sessionInfos.TryAdd(workItem.InstanceId, sessionInfo)) + { + ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(FabricOrchestrationService)}.{nameof(LockNextTaskOrchestrationWorkItemAsync)} : Multiple receivers processing the same session : {currentSession.SessionId.InstanceId}?"); + } + + return workItem; + } + catch (Exception) + { + this.orchestrationProvider.TryUnlockSession(currentSession.SessionId, abandon: true); + throw; + } + } + + public Task RenewTaskOrchestrationWorkItemLockAsync(TaskOrchestrationWorkItem workItem) + { + throw new NotImplementedException(); + } + + public async Task CompleteTaskOrchestrationWorkItemAsync( + TaskOrchestrationWorkItem workItem, + OrchestrationRuntimeState newOrchestrationRuntimeState, + IList outboundMessages, + IList orchestratorMessages, + IList timerMessages, + TaskMessage continuedAsNewMessage, + OrchestrationState orchestrationState) + { + SessionInformation sessionInfo = GetSessionInfo(workItem.InstanceId); + + if (continuedAsNewMessage != null) + { + throw new Exception("ContinueAsNew is not supported yet"); + } + + bool isComplete = workItem.OrchestrationRuntimeState.OrchestrationStatus.IsTerminalState(); + + IList sessionsToEnqueue = null; + List> scheduledMessages = null; + List> activityMessages = null; + + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + bool retryOnException; + do + { + try + { + retryOnException = false; + sessionsToEnqueue = null; + scheduledMessages = null; + activityMessages = null; + + using (var txn = this.stateManager.CreateTransaction()) + { + if (outboundMessages?.Count > 0) + { + activityMessages = outboundMessages.Select(m => new Message(Guid.NewGuid().ToString(), new TaskMessageItem(m))).ToList(); + await this.activitiesProvider.SendBatchBeginAsync(txn, activityMessages); + } + + if (timerMessages?.Count > 0) + { + scheduledMessages = timerMessages.Select(m => new Message(Guid.NewGuid().ToString(), new TaskMessageItem(m))).ToList(); + await this.scheduledMessagesProvider.SendBatchBeginAsync(txn, scheduledMessages); + } + + if (orchestratorMessages?.Count > 0) + { + if (workItem.OrchestrationRuntimeState?.ParentInstance != null) + { + sessionsToEnqueue = await this.orchestrationProvider.TryAppendMessageBatchAsync(txn, orchestratorMessages.Select(tm => new TaskMessageItem(tm))); + } + else + { + await this.orchestrationProvider.AppendMessageBatchAsync(txn, orchestratorMessages.Select(tm => new TaskMessageItem(tm))); + sessionsToEnqueue = orchestratorMessages.Select(m => m.OrchestrationInstance).ToList(); + } + } + + await this.orchestrationProvider.CompleteMessages(txn, sessionInfo.Instance, sessionInfo.LockTokens); + + // When an orchestration is completed, we need to drop the session which involves 2 steps (1) Removing the row from sessions + // (2) Dropping the session messages dictionary. The second step is done in background thread for performance so is not + // part of transaction. Since it will happen outside the trasanction, if this transaction fails for some reason and we dropped + // the session as part of this transaction, we wouldn't have updated the session state but would have lost the messages + // in the session messages dictionary which are needed for state to reach complete state (when the orchestration is picked up again in next fetch). + // So we don't want to drop session as part of this transaction. + // Instead, we drop the session as part of a subsequent different transaction. + // However, framework passes us 'null' value for 'newOrchestrationRuntimeState' when orchestration is completed and + // if we updated the session state to null and this transaction succeded, and a node failures occurs and we + // never call the subsequent transaction, we will lose the runtime state of orchestration and never will be able to + // mark it as complete even if it is. So we use the work item's runtime state when 'newOrchestrationRuntimeState' is null + // so that the latest state is what is stored for the session. + // As part of next transaction, we are going to remove the row anyway for the session and it doesn't matter to update it to 'null'. + await this.orchestrationProvider.UpdateSessionState(txn, sessionInfo.Instance, newOrchestrationRuntimeState ?? workItem.OrchestrationRuntimeState); + + // We skip writing to instanceStore when orchestration reached terminal state to avoid a minor timing issue that + // wait for an orchestration completes but another orchestration with the same name cannot be started immediately + // because the session is still in store. We update the instance store on orchestration completion and drop the + // session as part of the next atomic transaction. + if (this.instanceStore != null && orchestrationState != null && !isComplete) + { + await this.instanceStore.WriteEntitesAsync(txn, new InstanceEntityBase[] + { + new OrchestrationStateInstanceEntity() + { + State = orchestrationState + } + }); + } + await txn.CommitAsync(); + } + } + catch (FabricReplicationOperationTooLargeException ex) + { + ProviderEventSource.Log.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {sessionInfo.Instance}, Action = {nameof(CompleteTaskOrchestrationWorkItemAsync)}", ex.ToString()); + retryOnException = true; + newOrchestrationRuntimeState = null; + outboundMessages = null; + timerMessages = null; + orchestratorMessages = null; + if (orchestrationState != null) + { + orchestrationState.OrchestrationStatus = OrchestrationStatus.Failed; + orchestrationState.Output = $"Fabric exception when trying to process orchestration: {ex}. Investigate and consider reducing the serialization size of orchestration inputs/outputs/overall length to avoid the issue."; + } + } + } while (retryOnException); + }, uniqueActionIdentifier: $"OrchestrationId = '{workItem.InstanceId}', Action = '{nameof(CompleteTaskOrchestrationWorkItemAsync)}'"); + + if (activityMessages != null) + { + this.activitiesProvider.SendBatchComplete(activityMessages); + } + if (scheduledMessages != null) + { + this.scheduledMessagesProvider.SendBatchComplete(scheduledMessages); + } + if (sessionsToEnqueue != null) + { + foreach (var instance in sessionsToEnqueue) + { + this.orchestrationProvider.TryEnqueueSession(instance); + } + } + + if (isComplete) + { + await HandleCompletedOrchestration(workItem); + } + } + + // Caller should ensure the workItem has reached terminal state. + private async Task HandleCompletedOrchestration(TaskOrchestrationWorkItem workItem) + { + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.stateManager.CreateTransaction()) + { + await this.instanceStore.WriteEntitesAsync(txn, new InstanceEntityBase[] + { + new OrchestrationStateInstanceEntity() + { + State = Utils.BuildOrchestrationState(workItem.OrchestrationRuntimeState) + } + }); + // DropSession does 2 things (like mentioned in the comments above) - remove the row from sessions dictionary + // and delete the session messages dictionary. The second step is in a background thread and not part of transaction. + // However even if this transaction failed but we ended up deleting session messages dictionary, that's ok - at + // that time, it should be an empty dictionary and we would have updated the runtime session state to full completed + // state in the transaction from Complete method. So the subsequent attempt would be able to complete the session. + await this.orchestrationProvider.DropSession(txn, workItem.OrchestrationRuntimeState.OrchestrationInstance); + await txn.CommitAsync(); + } + }, uniqueActionIdentifier: $"OrchestrationId = '{workItem.InstanceId}', Action = '{nameof(HandleCompletedOrchestration)}'"); + + this.instanceStore.OnOrchestrationCompleted(workItem.OrchestrationRuntimeState.OrchestrationInstance); + + ProviderEventSource.Log.OrchestrationFinished(workItem.InstanceId, + workItem.OrchestrationRuntimeState.OrchestrationStatus.ToString(), + (workItem.OrchestrationRuntimeState.CompletedTime - workItem.OrchestrationRuntimeState.CreatedTime).TotalSeconds, + workItem.OrchestrationRuntimeState.Output, + workItem.OrchestrationRuntimeState.OrchestrationInstance.ExecutionId); + } + + public Task AbandonTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem workItem) + { + SessionInformation sessionInfo = TryRemoveSessionInfo(workItem.InstanceId); + if (sessionInfo == null) + { + ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(AbandonTaskOrchestrationWorkItemAsync)} : Could not get a session info object while trying to abandon session {workItem.InstanceId}"); + } + else + { + this.orchestrationProvider.TryUnlockSession(sessionInfo.Instance, abandon: true); + } + return Task.CompletedTask; + } + + public Task ReleaseTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem workItem) + { + bool isComplete = workItem.OrchestrationRuntimeState.OrchestrationStatus.IsTerminalState(); + + SessionInformation sessionInfo = TryRemoveSessionInfo(workItem.InstanceId); + if (sessionInfo != null) + { + this.orchestrationProvider.TryUnlockSession(sessionInfo.Instance, isComplete: isComplete); + } + + return Task.CompletedTask; + } + + public int TaskActivityDispatcherCount => this.settings.TaskActivityDispatcherSettings.DispatcherCount; + public int MaxConcurrentTaskActivityWorkItems => this.settings.TaskActivityDispatcherSettings.MaxConcurrentActivities; + + // Note: Do not rely on cancellationToken parameter to this method because the top layer does not yet implement any cancellation. + public async Task LockNextTaskActivityWorkItem(TimeSpan receiveTimeout, CancellationToken cancellationToken) + { + var message = await this.activitiesProvider.ReceiveAsync(receiveTimeout); + + if (message != null) + { + return new TaskActivityWorkItem() + { + Id = message.Key, + TaskMessage = message.Value.TaskMessage + }; + } + + return null; + } + + public async Task CompleteTaskActivityWorkItemAsync(TaskActivityWorkItem workItem, TaskMessage responseMessage) + { + bool added = false; + + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + bool retryOnException; + do + { + try + { + added = false; + retryOnException = false; + + using (var txn = this.stateManager.CreateTransaction()) + { + await this.activitiesProvider.CompleteAsync(txn, workItem.Id); + added = await this.orchestrationProvider.TryAppendMessageAsync(txn, new TaskMessageItem(responseMessage)); + await txn.CommitAsync(); + } + } + catch (FabricReplicationOperationTooLargeException ex) + { + ProviderEventSource.Log.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {responseMessage.OrchestrationInstance}, ActivityId = {workItem.Id}, Action = {nameof(CompleteTaskActivityWorkItemAsync)}", ex.ToString()); + retryOnException = true; + var originalEvent = responseMessage.Event; + int taskScheduledId = GetTaskScheduledId(originalEvent); + string details = $"Fabric exception when trying to save activity result: {ex}. Consider reducing the serialization size of activity result to avoid the issue."; + responseMessage.Event = new TaskFailedEvent(originalEvent.EventId, taskScheduledId, ex.Message, details); + } + } while (retryOnException); + }, uniqueActionIdentifier: $"Orchestration = '{responseMessage.OrchestrationInstance}', ActivityId = '{workItem.Id}', Action = '{nameof(CompleteTaskActivityWorkItemAsync)}'"); + + if (added) + { + this.orchestrationProvider.TryEnqueueSession(responseMessage.OrchestrationInstance); + } + } + + public Task AbandonTaskActivityWorkItemAsync(TaskActivityWorkItem workItem) + { + this.activitiesProvider.Abandon(workItem.Id); + return Task.CompletedTask; + } + + public Task RenewTaskActivityWorkItemLockAsync(TaskActivityWorkItem workItem) + { + return Task.FromResult(workItem); + } + + int GetTaskScheduledId(HistoryEvent historyEvent) + { + TaskCompletedEvent tce = historyEvent as TaskCompletedEvent; + if (tce != null) + { + return tce.TaskScheduledId; + } + + TaskFailedEvent tfe = historyEvent as TaskFailedEvent; + if (tfe != null) + { + return tfe.TaskScheduledId; + } + + return -1; + } + + int GetDelayForFetchOrProcessException(Exception exception) + { + //Todo: Need to fine tune + if (exception is TimeoutException) + { + return 1; + } + + if (exception is FabricNotReadableException) + { + return 2; + } + + return 0; + } + + SessionInformation GetSessionInfo(string sessionId) + { + ProviderEventSource.Log.TraceMessage(sessionId, $"{nameof(GetSessionInfo)} - Getting session info"); + SessionInformation sessionInfo; + if (!this.sessionInfos.TryGetValue(sessionId, out sessionInfo)) + { + var message = $"{nameof(GetSessionInfo)}. Trying to get a session that's not in locked sessions {sessionId}"; + ProviderEventSource.Log.UnexpectedCodeCondition(message); + throw new Exception(message); + } + + return sessionInfo; + } + + SessionInformation TryRemoveSessionInfo(string sessionId) + { + SessionInformation sessionInfo; + var removed = this.sessionInfos.TryRemove(sessionId, out sessionInfo); + ProviderEventSource.Log.TraceMessage(sessionId, $"{nameof(TryRemoveSessionInfo)}: Removed = {removed}"); + return sessionInfo; + } + + class SessionInformation + { + public OrchestrationInstance Instance { get; set; } + + public List LockTokens { get; set; } + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs new file mode 100644 index 000000000..338d17f5e --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -0,0 +1,190 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.Core.History; + using DurableTask.Core.Tracking; + using DurableTask.ServiceFabric.Stores; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + + class FabricOrchestrationServiceClient : IOrchestrationServiceClient + { + readonly IReliableStateManager stateManager; + readonly IFabricOrchestrationServiceInstanceStore instanceStore; + readonly SessionsProvider orchestrationProvider; + + public FabricOrchestrationServiceClient(IReliableStateManager stateManager, SessionsProvider orchestrationProvider, IFabricOrchestrationServiceInstanceStore instanceStore) + { + this.stateManager = stateManager ?? throw new ArgumentNullException(nameof(stateManager)); + this.orchestrationProvider = orchestrationProvider ?? throw new ArgumentNullException(nameof(orchestrationProvider)); + this.instanceStore = instanceStore ?? throw new ArgumentNullException(nameof(instanceStore)); + } + + #region IOrchestrationServiceClient + public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) + { + ExecutionStartedEvent startEvent = creationMessage.Event as ExecutionStartedEvent; + if (startEvent == null) + { + await this.SendTaskOrchestrationMessageAsync(creationMessage); + return; + } + + var instance = creationMessage.OrchestrationInstance; + + var added = await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var tx = this.stateManager.CreateTransaction()) + { + if (await this.orchestrationProvider.TryAddSession(tx, new TaskMessageItem(creationMessage))) + { + await WriteExecutionStartedEventToInstanceStore(tx, startEvent); + await tx.CommitAsync(); + return true; + } + + return false; + } + }, uniqueActionIdentifier: $"Orchestration = '{instance}', Action = '{nameof(CreateTaskOrchestrationAsync)}'"); + + if (added) + { + ProviderEventSource.Log.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); + this.orchestrationProvider.TryEnqueueSession(creationMessage.OrchestrationInstance); + } + else + { + throw new InvalidOperationException($"An orchestration with id '{creationMessage.OrchestrationInstance.InstanceId}' is already running."); + } + } + + public Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) + { + // Todo: Support for dedupeStatuses? + if (dedupeStatuses != null) + { + throw new NotSupportedException($"DedupeStatuses are not supported yet with service fabric provider"); + } + + return CreateTaskOrchestrationAsync(creationMessage); + } + + public Task SendTaskOrchestrationMessageAsync(TaskMessage message) + { + return this.orchestrationProvider.AppendMessageAsync(new TaskMessageItem(message)); + } + + public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] messages) + { + foreach(var message in messages) + { + await this.SendTaskOrchestrationMessageAsync(message); + } + } + + public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) + { + var latestExecutionId = await this.instanceStore.GetLatestExecutionId(instanceId); + + if (latestExecutionId == null) + { + throw new ArgumentException($"No execution id found for given instanceId {instanceId}, can only terminate the latest execution of a given orchestration"); + } + + var taskMessage = new TaskMessage + { + OrchestrationInstance = new OrchestrationInstance { InstanceId = instanceId, ExecutionId = latestExecutionId }, + Event = new ExecutionTerminatedEvent(-1, reason) + }; + + await SendTaskOrchestrationMessageAsync(taskMessage); + } + + public async Task> GetOrchestrationStateAsync(string instanceId, bool allExecutions) + { + var stateInstances = await this.instanceStore.GetOrchestrationStateAsync(instanceId, allExecutions); + + var result = new List(); + foreach(var stateInstance in stateInstances) + { + if (stateInstance != null) + { + result.Add(stateInstance.State); + } + } + return result; + } + + public async Task GetOrchestrationStateAsync(string instanceId, string executionId) + { + var stateInstance = await this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId); + return stateInstance?.State; + } + + public Task GetOrchestrationHistoryAsync(string instanceId, string executionId) + { + throw new NotImplementedException(); + } + + public Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) + { + if (timeRangeFilterType != OrchestrationStateTimeRangeFilterType.OrchestrationCompletedTimeFilter) + { + throw new NotSupportedException("Purging is supported only for Orchestration completed time filter."); + } + + return this.instanceStore.PurgeOrchestrationHistoryEventsAsync(thresholdDateTimeUtc); + } + + public async Task WaitForOrchestrationAsync(string instanceId, string executionId, TimeSpan timeout, CancellationToken cancellationToken) + { + var instance = new OrchestrationInstance() { InstanceId = instanceId, ExecutionId = executionId }; + var state = await this.instanceStore.WaitForOrchestrationAsync(instance, timeout); + return state?.State; + } + #endregion + + Task WriteExecutionStartedEventToInstanceStore(ITransaction tx, ExecutionStartedEvent startEvent) + { + var createdTime = DateTime.UtcNow; + var initialState = new OrchestrationState() + { + Name = startEvent.Name, + Version = startEvent.Version, + OrchestrationInstance = startEvent.OrchestrationInstance, + OrchestrationStatus = OrchestrationStatus.Pending, + Input = startEvent.Input, + Tags = startEvent.Tags, + CreatedTime = createdTime, + LastUpdatedTime = createdTime + }; + + return this.instanceStore.WriteEntitesAsync(tx, new InstanceEntityBase[] + { + new OrchestrationStateInstanceEntity() + { + State = initialState + } + }); + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs new file mode 100644 index 000000000..fe168fb64 --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -0,0 +1,168 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Web.Http; + using System.Web.Http.Results; + + using DurableTask.Core; + using DurableTask.Core.Exceptions; + using DurableTask.ServiceFabric.Models; + using DurableTask.ServiceFabric.Tracing; + + /// + /// A Web Api controller that provides TaskHubClient operations. + /// + public class FabricOrchestrationServiceController : ApiController + { + private readonly IOrchestrationServiceClient orchestrationServiceClient; + + /// + /// Creates an instance of FabricOrchestrationServiceController for given OrchestrationServiceClient + /// + /// IOrchestrationServiceClient instance + public FabricOrchestrationServiceController(IOrchestrationServiceClient orchestrationServiceClient) + { + this.orchestrationServiceClient = orchestrationServiceClient; + } + + /// + /// Creates a task orchestration. + /// + /// Parameters for creating task orchestration. + /// Will throw an OrchestrationAlreadyExistsException exception + /// If any orchestration with the same instance Id exists in the instance store and it has a status specified in dedupeStatuses. + /// object. + [HttpPut] + public async Task CreateTaskOrchestration([FromBody] CreateTaskOrchestrationParameters parameters) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(CreateTaskOrchestration)); + try + { + if (parameters.DedupeStatuses == null) + { + await this.orchestrationServiceClient.CreateTaskOrchestrationAsync(parameters.TaskMessage); + } + else + { + await this.orchestrationServiceClient.CreateTaskOrchestrationAsync(parameters.TaskMessage, parameters.DedupeStatuses); + } + + return new OkResult(this); + } + catch (OrchestrationAlreadyExistsException exception) + { + return new ExceptionResult(exception, this); + } + } + + /// + /// Sends an orchestration message to TaskHubClient. + /// + /// Message to send + /// object. + [HttpPost] + public async Task SendTaskOrchestrationMessage([FromBody] TaskMessage message) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(SendTaskOrchestrationMessage)); + await this.orchestrationServiceClient.SendTaskOrchestrationMessageAsync(message); + return new OkResult(this); + } + + /// + /// Sends an array of orchestration messages to TaskHubClient. + /// + /// Message to send + /// object. + [HttpPost] + public async Task SendTaskOrchestrationMessageBatch([FromBody] TaskMessage[] messages) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(SendTaskOrchestrationMessageBatch)); + await this.orchestrationServiceClient.SendTaskOrchestrationMessageBatchAsync(messages); + return new OkResult(this); + } + + /// + /// Gets the state of orchestration. + /// + /// Instance id of the orchestration + /// Execution id of the orchestration + /// object. + [HttpGet] + public async Task GetOrchestrationState(string instanceId, string executionId) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationState)); + var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, executionId); + return state; + } + + /// + /// Gets the state of orchestration. + /// + /// Instance id of the orchestration + /// True if method should fetch all executions of the instance, false if the method should only fetch the most recent execution + /// List of . + [HttpGet] + public async Task> GetOrchestrationState(string instanceId, bool allExecutions) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationState)); + var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, allExecutions); + return state; + } + + /// + /// Terminates an orchestration. + /// + /// Instance id of the orchestration + /// Execution id of the orchestration + /// object. + [HttpDelete] + public async Task ForceTerminateTaskOrchestration(string instanceId, string reason) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(ForceTerminateTaskOrchestration)); + await this.orchestrationServiceClient.ForceTerminateTaskOrchestrationAsync(instanceId, reason); + return new OkResult(this); + } + + /// + /// Gets the history of orchestration. + /// + /// Instance id of the orchestration + /// Execution id of the orchestration + /// Orchestration history + [HttpGet] + public async Task GetOrchestrationHistory(string instanceId, string executionId) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationHistory)); + var result = await this.orchestrationServiceClient.GetOrchestrationHistoryAsync(instanceId, executionId); + return result; + } + + /// + /// Purges orchestration instance state and history for orchestrations older than the specified threshold time. + /// + /// Purge history parameters + /// object. + [HttpPost] + public async Task PurgeOrchestrationHistory(PurgeOrchestrationHistoryParameters purgeParameters) + { + ProviderEventSource.Log.LogServingNetworkAction(nameof(PurgeOrchestrationHistory)); + await this.orchestrationServiceClient.PurgeOrchestrationHistoryAsync(purgeParameters.ThresholdDateTimeUtc, purgeParameters.TimeRangeFilterType); + return new OkResult(this); + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricProviderClient.cs b/src/DurableTask.ServiceFabric/FabricProviderClient.cs new file mode 100644 index 000000000..36611f607 --- /dev/null +++ b/src/DurableTask.ServiceFabric/FabricProviderClient.cs @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.Core.Serializing; + using DurableTask.ServiceFabric.Stores; + using Microsoft.ServiceFabric.Data; + using Newtonsoft.Json; + + internal class FabricProviderClient : IFabricProviderClient + { + readonly IReliableStateManager stateManager; + readonly SessionsProvider orchestrationProvider; + readonly JsonDataConverter FormattingConverter = new JsonDataConverter(new JsonSerializerSettings() { Formatting = Formatting.Indented }); + + public FabricProviderClient(IReliableStateManager stateManager, SessionsProvider orchestrationProvider) + { + this.stateManager = stateManager ?? throw new ArgumentNullException(nameof(stateManager)); + this.orchestrationProvider = orchestrationProvider ?? throw new ArgumentNullException(nameof(orchestrationProvider)); + } + + public async Task> GetRunningOrchestrations() + { + var sessions = await this.orchestrationProvider.GetSessions(); + return sessions.Select(s => s.SessionId); + } + + public async Task GetOrchestrationRuntimeState(string instanceId) + { + var session = await this.orchestrationProvider.GetSession(instanceId); + if (session == null) + { + throw new ArgumentException($"There is no running or pending Orchestration with the instanceId {instanceId}"); + } + return FormattingConverter.Serialize(session.SessionState); + } + } +} diff --git a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs new file mode 100644 index 000000000..75d01e1c7 --- /dev/null +++ b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs @@ -0,0 +1,87 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using DurableTask.Core; + using DurableTask.Core.Tracking; + using Microsoft.ServiceFabric.Data; + + // Todo: Figure out a way to not include internal XML documentation in the generated documentation file + // or change all the below comments to non-xml style. + internal interface IFabricOrchestrationServiceInstanceStore + { + /// + /// Runs initialization to prepare the instance store for use + /// + /// Flag to indicate whether the store should be recreated. + Task InitializeStoreAsync(bool recreate); + + /// + /// Starts the instance store object. + /// + /// + Task StartAsync(); + + /// + /// Deletes instances instance store + /// + Task DeleteStoreAsync(); + + /// + /// Writes a list of history events to instance store + /// + /// Service fabric transaction in which this operation is executed. + /// List of history events to write + Task WriteEntitesAsync(ITransaction transaction, IEnumerable entities); + + /// + /// Gets a list of orchestration states for a given instance + /// + /// The instance id to return state for + /// Flag indiciation whether to get all history execution ids or just the most recent + /// List of matching orchestration states + Task> GetOrchestrationStateAsync(string instanceId, bool allInstances); + + /// + /// Gets the orchestration state for a given instance and execution id + /// + /// The instance id to return state for + /// The execution id to return state for + /// The matching orchestation state or null if not found + Task GetOrchestrationStateAsync(string instanceId, string executionId); + + /// + /// Gets the list of history events for a given instance and execution id + /// + /// The instance id to return history for + /// The execution id to return history for + /// List of history events + Task> GetOrchestrationHistoryEventsAsync(string instanceId, string executionId); + + /// + /// Purges history from storage for given time range + /// + /// The datetime in UTC to use as the threshold for purging history + Task PurgeOrchestrationHistoryEventsAsync(DateTime thresholdHourlyDateTimeUtc); + + void OnOrchestrationCompleted(OrchestrationInstance instance); + + Task WaitForOrchestrationAsync(OrchestrationInstance instance, TimeSpan timeout); + + Task GetLatestExecutionId(string instanceId); + } +} diff --git a/src/DurableTask.ServiceFabric/IFabricProviderClient.cs b/src/DurableTask.ServiceFabric/IFabricProviderClient.cs new file mode 100644 index 000000000..bad4a2277 --- /dev/null +++ b/src/DurableTask.ServiceFabric/IFabricProviderClient.cs @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using DurableTask.Core; + + /// + /// Provides additional useful functionality which is not available through . + /// + public interface IFabricProviderClient + { + /// + /// Gets all the orchestration instances which are currently running or pending. + /// + /// + Task> GetRunningOrchestrations(); + + /// + /// Gets runtime state of a running or pending orchestration which includes the history events. + /// + /// The + /// of the orchestration. + /// Returns serialized runtime state which includes all the history events if the + /// orchestration is running or pending. + /// The API is intended for diagnostics purpose, so current implementation returns + /// a formatted json serialized string which can be very large. + /// When the orchestration already completed or was never started. + Task GetOrchestrationRuntimeState(string instanceId); + } +} diff --git a/src/DurableTask.ServiceFabric/IPartitionProvider.cs b/src/DurableTask.ServiceFabric/IPartitionProvider.cs new file mode 100644 index 000000000..d38949962 --- /dev/null +++ b/src/DurableTask.ServiceFabric/IPartitionProvider.cs @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + + /// + /// Provides partition related information. + /// + public interface IPartitionProvider + { + /// + /// Gets end points for all the partitions. + /// + /// All the end points. + Uri[] GetAllPartitions(); + + /// + /// Gets partition end point for given instanceId. + /// + /// Partition end point + Uri GetPartitionForInstance(string instanceId); + } +} diff --git a/src/DurableTask.ServiceFabric/Message.cs b/src/DurableTask.ServiceFabric/Message.cs new file mode 100644 index 000000000..acf4b375f --- /dev/null +++ b/src/DurableTask.ServiceFabric/Message.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + + sealed class Message where TKey : IComparable, IEquatable + { + public Message(TKey key, TValue value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + Key = key; + Value = value; + } + + public TKey Key { get; private set; } + + public TValue Value { get; private set; } + } +} diff --git a/src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs b/src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs new file mode 100644 index 000000000..507a6a1fb --- /dev/null +++ b/src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Models +{ + using DurableTask.Core; + + /// + /// Task orchestration creation parameters. + /// + public class CreateTaskOrchestrationParameters + { + /// + /// Task message representing task orchestration. + /// + public TaskMessage TaskMessage { get; set; } + + /// + /// States of previous orchestration executions to be considered while de-duping new orchestrations on the client. + /// + public OrchestrationStatus[] DedupeStatuses { get; set; } + } +} diff --git a/src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs b/src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs new file mode 100644 index 000000000..af6321b49 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Models +{ + using System; + using DurableTask.Core; + + /// + /// Purging orchestration history parameters. + /// + public class PurgeOrchestrationHistoryParameters + { + /// + /// Starting date time for purging orchestrations. + /// + public DateTime ThresholdDateTimeUtc { get; set; } + + /// + /// Orchestration start time range filter type. + /// + public OrchestrationStateTimeRangeFilterType TimeRangeFilterType { get; set; } + } +} diff --git a/src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs b/src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs new file mode 100644 index 000000000..e83fe4dc8 --- /dev/null +++ b/src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System.Collections.Generic; + using DurableTask.Core; + + internal class OrchestrationInstanceComparer : IEqualityComparer + { + public static readonly OrchestrationInstanceComparer Default = new OrchestrationInstanceComparer(); + + public bool Equals(OrchestrationInstance first, OrchestrationInstance second) + { + if (first == null || second == null) + { + return first == second; + } + + if (string.Equals(first.InstanceId, second.InstanceId) && string.Equals(first.ExecutionId, second.ExecutionId)) + { + return true; + } + + return false; + } + + public int GetHashCode(OrchestrationInstance instance) + { + return instance.GetHashCode(); + } + } +} diff --git a/src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.cs b/src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.cs new file mode 100644 index 000000000..731557ad8 --- /dev/null +++ b/src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.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. +// ---------------------------------------------------------------------------------- + +namespace DurableTask.ServiceFabric +{ + using DurableTask.Core; + + static class OrchestrationStatusExtensions + { + public static bool IsTerminalState(this OrchestrationStatus status) + { + return !status.IsRunningOrPending(); + } + + public static bool IsRunningOrPending(this OrchestrationStatus status) + { + return status == OrchestrationStatus.Pending || status == OrchestrationStatus.Running; + } + } +} diff --git a/src/DurableTask.ServiceFabric/PersistentSession.cs b/src/DurableTask.ServiceFabric/PersistentSession.cs new file mode 100644 index 000000000..2acea3db9 --- /dev/null +++ b/src/DurableTask.ServiceFabric/PersistentSession.cs @@ -0,0 +1,70 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Runtime.Serialization; + using DurableTask.Core; + using DurableTask.Core.History; + + [DataContract] + sealed partial class PersistentSession : IExtensibleDataObject + { + // Note : Ideally all the properties in this class should be readonly because this + // class is designed to be immutable class. We use private settable properties + // for DataContract serialization to work - the private setters should not be used + // in the code within this class as that would violate the immutable design. Any + // method that mutates the state should return a new instance instead. + [DataMember] + public OrchestrationInstance SessionId { get; private set; } + + // Note: The properties below are marked IEnumerable but not + // IImmutableList because DataContract serialization cannot deserialize the latter. + // Except for the constructor and serialization methods, rest of the code in this class + // should use only the public immutable list members. + + /// + /// Do not use except in the constructor or serialization methods, use property instead. + /// + [DataMember] + IEnumerable sessionState { get; set; } + + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + this.sessionState = this.sessionState.ToImmutableList(); + } + + private PersistentSession(OrchestrationInstance sessionId, IImmutableList sessionState) + { + this.SessionId = sessionId; + this.sessionState = sessionState ?? ImmutableList.Empty; + } + + public static PersistentSession Create(OrchestrationInstance sessionId) + { + return Create(sessionId, null); + } + + public static PersistentSession Create(OrchestrationInstance sessionId, IImmutableList sessionState) + { + return new PersistentSession(sessionId, sessionState); + } + + public ImmutableList SessionState => this.sessionState.ToImmutableList(); + + public ExtensionDataObject ExtensionData { get; set; } + } +} diff --git a/src/DurableTask.ServiceFabric/README.md b/src/DurableTask.ServiceFabric/README.md new file mode 100644 index 000000000..6f0932ad4 --- /dev/null +++ b/src/DurableTask.ServiceFabric/README.md @@ -0,0 +1,44 @@ +# Service Fabric Reliable Collections Provider For Durable Task Framework + +This page outlines the current status of the custom provider for Durable Task Framework based on Service Fabric Reliable Collections. + +What are Reliable Collections? +https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-reliable-collections + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +One fundamental way Service Fabric provider differs from other providers from a user perspective is that: +With all other providers, the provider is an external store relative to Task Hub worker i.e., Task hub worker runs in a cluster and provider is hosted in a different cluster elsewhere and runs on different nodes. +However, with the Service fabric provider, the provider runs on the same cluster as Task hub worker (and Task hub client as well for now). The scale-out of task hub worker nodes should be planned with a regular service fabric partitioning model. + +## What has been done? + +* Scheduled Tasks, Timers, Sub orchestrations work! +* Fabric based instance store has limited functionality for now! + * No support for history events. + * Only the latest state for a given Orchestration instance is stored (previous state changes are overwritten by latest). [For a period of 24 hour time window] + * State can be queried only for Orchestrations still running / pending OR completed within an hour ago. + * State of orchestration that's completed a day ago will be cleaned up. + +## What is pending? + +* ContinueAsNew needs to be implemented. +* Providing support for Azure Storage instance store with Service Fabric based provider. (So that we can persist the state more permanantly) +* External events should work but not tested. +* Support for history event tracking. +* Support for querying orchestration state across executions (only makes sense after we add support for ContinueWith) +* No automatic lock expiry for activities / orchestrations that are fetched from store but neither completed nor abandoned. (It's not clear if this is really needed with fabric provider given task hub worker and provider run on the same node.) +* No equivalent of dead letter support (any bad activity / session will keep forever in the system). + +## Development Notes + +There are pre-requisites for setting up this project for developing or making contributions. + +* Current recommended Visual Studio is 2017. +* Service fabric SDK should be installed. + * If this is not installed, when opening the solution, you get a dialog saying .sfproj is not supported (This is a test application project used for functional tests to run.) +* In visual studio, the default test architecture should be set to x64 for detecting tests properly. +* There are 2 manual steps that must be done before running tests: + * A 5 node local cluster must be setup using Service Fabric Local Cluster Manager (installed by SDK). + * After the solution is built, the "TestFabricApplication" must be packaged using the context menu. (The packaged application will be deployed to local cluster as part of test setup) \ No newline at end of file diff --git a/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs new file mode 100644 index 000000000..e0ae95b02 --- /dev/null +++ b/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs @@ -0,0 +1,225 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Net.Http.Formatting; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + + using DurableTask.Core; + using DurableTask.Core.Exceptions; + using DurableTask.ServiceFabric.Models; + using Newtonsoft.Json; + + /// + /// Allows to interact with a remote IOrchestrationServiceClient + /// + public class RemoteOrchestrationServiceClient : IOrchestrationServiceClient + { + private readonly IPartitionProvider partitionProvider; + private readonly HttpClient httpClient; + private readonly TimeSpan pollDelay = TimeSpan.FromSeconds(10); + + /// + /// Creates a new instance. + /// + public RemoteOrchestrationServiceClient(IPartitionProvider partitionProvider, HttpClient httpClient) + { + this.partitionProvider = partitionProvider; + this.httpClient = httpClient; + } + + /// + /// Creates a new orchestration + /// + /// Orchestration creation message + /// Will throw an OrchestrationAlreadyExistsException exception If any orchestration with the same instance Id exists in the instance store. + /// + public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) + { + var uri = ConstructEndpointUri(creationMessage.OrchestrationInstance, "CreateTaskOrchestration"); + await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage }); + } + + private async Task PutJsonAsync(Uri uri, object @object) + { + var mediaFormatter = new JsonMediaTypeFormatter() + { + SerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } + }; + + var result = await this.httpClient.PutAsync(uri, @object, mediaFormatter); + if (!result.IsSuccessStatusCode) + { + throw new Exception("CreateTaskOrchestrationAsync failed"); + } + } + + /// + /// Creates a new orchestration and specifies a subset of states which should be de duplicated on in the client side + /// + /// Orchestration creation message + /// States of previous orchestration executions to be considered while de-duping new orchestrations on the client + /// Will throw an OrchestrationAlreadyExistsException exception If any orchestration with the same instance Id exists in the instance store and it has a status specified in dedupeStatuses. + /// + public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) + { + var uri = ConstructEndpointUri(creationMessage.OrchestrationInstance, "CreateTaskOrchestration"); + await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage, DedupeStatuses = dedupeStatuses }); + } + + /// + /// Forcefully terminate the specified orchestration instance + /// + /// Instance to terminate + /// Reason for termination + public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) + { + var uri = ConstructEndpointUri(instanceId, "ForceTerminateTaskOrchestration"); + var builder = new UriBuilder($"{uri}?instanceId={instanceId}&reason={HttpUtility.UrlEncode(reason)}"); + var response = await this.httpClient.DeleteAsync(builder.Uri); + if (!response.IsSuccessStatusCode) + { + throw new Exception("Unable to terminate task instance"); + } + } + + /// + /// Get a string dump of the execution history of the specified orchestration instance specified execution (generation) of the specified instance + /// + /// Instance id + /// Execution id + /// String with formatted JSON representing the execution history + public async Task GetOrchestrationHistoryAsync(string instanceId, string executionId) + { + var uri = ConstructEndpointUri(instanceId, "GetOrchestrationHistory"); + var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); + var history = await this.httpClient.GetStringAsync(builder.Uri); + return history; + } + + /// + /// Get a list of orchestration states from the instance storage for the most current execution (generation) of the specified instance. + /// + /// Instance id + /// True if method should fetch all executions of the instance, false if the method should only fetch the most recent execution + /// List of OrchestrationState objects that represents the list of orchestrations in the instance store + public async Task> GetOrchestrationStateAsync(string instanceId, bool allExecutions) + { + var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); + var builder = new UriBuilder($"{uri}?instanceId={instanceId}&allExecutions={allExecutions}"); + var stateString = await this.httpClient.GetStringAsync(builder.Uri); + var states = JsonConvert.DeserializeObject>(stateString); + return states; + } + + /// + /// Get a list of orchestration states from the instance storage for the specified execution (generation) of the specified instance. + /// + /// Instance id + /// Execution id + /// The OrchestrationState of the specified instanceId or null if not found + public async Task GetOrchestrationStateAsync(string instanceId, string executionId) + { + var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); + var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); + var stateString = await this.httpClient.GetStringAsync(builder.Uri); + var state = JsonConvert.DeserializeObject(stateString); + return state; + } + + /// + /// Purges orchestration instance state and history for orchestrations older than the specified threshold time. + /// Also purges the blob storage. + /// + /// Threshold date time in UTC + /// What to compare the threshold date time against + public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) + { + List allTasks = new List(); + foreach(var endpoint in this.partitionProvider.GetAllPartitions()) + { + var uri = $"{endpoint.ToString()}/PurgeOrchestrationHistory"; + var task = this.httpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); + allTasks.Add(task); + } + + await Task.WhenAll(allTasks.ToArray()); + } + + /// + /// Send a new message for an orchestration + /// + /// Message to send + /// + public async Task SendTaskOrchestrationMessageAsync(TaskMessage message) + { + var uri = ConstructEndpointUri(message.OrchestrationInstance, "SendTaskOrchestrationMessage"); + await this.PutJsonAsync(uri, message); + } + + /// + /// Send a new set of messages for an orchestration + /// + /// Messages to send + /// + public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] messages) + { + foreach(var message in messages) + { + await SendTaskOrchestrationMessageAsync(message); + } + } + + /// + /// Wait for an orchestration to reach any terminal state within the given timeout + /// + /// Instance id of the orchestration + /// Execution id of the orchestration + /// Maximum amount of time to wait + /// Task cancellation token + public async Task WaitForOrchestrationAsync(string instanceId, string executionId, TimeSpan timeout, CancellationToken cancellationToken) + { + var maxTime = DateTime.Now.Add(timeout); + OrchestrationState state = null; + while (!cancellationToken.IsCancellationRequested && DateTime.Now < maxTime) + { + state = await this.GetOrchestrationStateAsync(instanceId, executionId); + if (state != null && state.OrchestrationStatus.IsTerminalState()) + { + return state; + } + + await Task.Delay(this.pollDelay); + } + + // Either cancellation was requested or timedout, return the last known state. + return state; + } + + private Uri ConstructEndpointUri(OrchestrationInstance instance, string action) + { + return new Uri($"{this.partitionProvider.GetPartitionForInstance(instance.InstanceId)}/{action}"); + } + + private Uri ConstructEndpointUri(string instanceId, string action) + { + return new Uri($"{this.partitionProvider.GetPartitionForInstance(instanceId)}/{action}"); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs b/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs new file mode 100644 index 000000000..2e5fd82ee --- /dev/null +++ b/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Stores +{ + using System; + using System.Collections.Concurrent; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.ServiceFabric.Data; + + class ActivityProvider : MessageProviderBase where TKey : IComparable, IEquatable + { + readonly ConcurrentQueue inMemoryQueue = new ConcurrentQueue(); + + public ActivityProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) : base(stateManager, storeName, token) + { + } + + protected override void AddItemInMemory(TKey key, TValue value) + { + this.inMemoryQueue.Enqueue(key); + } + + public async Task> ReceiveAsync(TimeSpan receiveTimeout) + { + if (!IsStopped()) + { + TKey key; + bool newItemsBeforeTimeout = true; + while (newItemsBeforeTimeout) + { + if (this.inMemoryQueue.TryDequeue(out key)) + { + try + { + return await GetValueAsync(key); + } + catch (Exception) + { + this.inMemoryQueue.Enqueue(key); + throw; + } + } + + newItemsBeforeTimeout = await WaitForItemsAsync(receiveTimeout); + } + } + return null; + } + + public void Abandon(TKey key) + { + this.inMemoryQueue.Enqueue(key); + SetWaiterForNewItems(); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs new file mode 100644 index 000000000..0394b5331 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs @@ -0,0 +1,190 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Stores +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Threading.Tasks; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + using Microsoft.ServiceFabric.Data.Collections; + + [SuppressMessage("Microsoft.Design", "CA1001", Justification = "Disposing is done through StartAsync and StopAsync")] + abstract class MessageProviderBase where TKey : IComparable, IEquatable + { + readonly string storeName; + readonly AsyncManualResetEvent waitEvent = new AsyncManualResetEvent(); + readonly TimeSpan metricsInterval = TimeSpan.FromMinutes(1); + + protected MessageProviderBase(IReliableStateManager stateManager, string storeName, CancellationToken token) + { + this.StateManager = stateManager; + this.storeName = storeName; + this.CancellationToken = token; + } + + protected IReliableStateManager StateManager { get; } + + protected CancellationToken CancellationToken { get; private set; } + + protected IReliableDictionary Store { get; private set; } + + public virtual async Task StartAsync() + { + await InitializeStore(); + await EnumerateItems(kvp => AddItemInMemory(kvp.Key, kvp.Value)); + var nowait = LogMetrics(); + } + + protected async Task InitializeStore() + { + this.Store = await this.StateManager.GetOrAddAsync>(this.storeName); + } + + public Task CompleteAsync(ITransaction tx, TKey key) + { + return this.Store.TryRemoveAsync(tx, key); + } + + public async Task CompleteBatchAsync(ITransaction tx, IEnumerable keys) + { + foreach (var key in keys) + { + await this.Store.TryRemoveAsync(tx, key); + } + } + + /// + /// Caller has to pass in a transaction and must call SendComplete with the same item + /// after the transaction commit succeeded. + /// + public Task SendBeginAsync(ITransaction tx, Message item) + { + return this.Store.TryAddAsync(tx, item.Key, item.Value); + } + + public void SendComplete(Message item) + { + AddItemInMemory(item.Key, item.Value); + this.waitEvent.Set(); + } + + /// + /// Caller has to pass in a transaction and must call SendBatchComplete with the same items + /// after the transaction commit succeeded. + /// + public async Task SendBatchBeginAsync(ITransaction tx, IEnumerable> items) + { + foreach (var item in items) + { + await this.Store.TryAddAsync(tx, item.Key, item.Value); + } + } + + public void SendBatchComplete(IEnumerable> items) + { + foreach (var item in items) + { + AddItemInMemory(item.Key, item.Value); + } + this.waitEvent.Set(); + } + + protected Task> GetValueAsync(TKey key) + { + return RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var tx = this.StateManager.CreateTransaction()) + { + var result = await this.Store.TryGetValueAsync(tx, key); + if (result.HasValue) + { + return new Message(key, result.Value); + } + var errorMessage = $"Internal Server Error: Did not find an item in reliable dictionary while having the item key {key} in memory"; + ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + throw new Exception(errorMessage); + } + }, uniqueActionIdentifier: $"Key = {key}, Action = MessageProviderBase.GetValueAsync, StoreName : {this.storeName}"); + } + + protected Task EnumerateItems(Action> itemAction) + { + return RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var tx = this.StateManager.CreateTransaction()) + { + var count = await this.Store.GetCountAsync(tx); + + if (count > 0) + { + var enumerable = await this.Store.CreateEnumerableAsync(tx, EnumerationMode.Unordered); + using (var enumerator = enumerable.GetAsyncEnumerator()) + { + while (await enumerator.MoveNextAsync(this.CancellationToken)) + { + itemAction(enumerator.Current); + } + } + } + } + }, uniqueActionIdentifier: $"Action = MessageProviderBase.EnumerateItems, StoreName : {this.storeName}"); + } + + public Task EnsureStoreInitialized() + { + if (this.Store == null) + { + return InitializeStore(); + } + + return Task.CompletedTask; + } + + protected Task WaitForItemsAsync(TimeSpan timeout) + { + this.waitEvent.Reset(); + return this.waitEvent.WaitAsync(timeout, this.CancellationToken); + } + + protected void SetWaiterForNewItems() + { + this.waitEvent.Set(); + } + + protected abstract void AddItemInMemory(TKey key, TValue value); + + protected bool IsStopped() + { + return this.CancellationToken.IsCancellationRequested; + } + + protected Task LogMetrics() + { + return Utils.RunBackgroundJob(async () => + { + long count = 0; + using (var tx = this.StateManager.CreateTransaction()) + { + count = await this.Store.GetCountAsync(tx); + } + + ProviderEventSource.Log.LogStoreCount(this.storeName, count); + }, initialDelay: metricsInterval, delayOnSuccess: metricsInterval, delayOnException: metricsInterval, actionName: $"Log Store Count of {this.storeName}", token: this.CancellationToken); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs new file mode 100644 index 000000000..de1986278 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs @@ -0,0 +1,172 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Stores +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.Core.History; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + + class ScheduledMessageProvider : MessageProviderBase + { + readonly SessionsProvider sessionsProvider; + readonly object @lock = new object(); + + ImmutableSortedSet> inMemorySet = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + DateTime nextActivationCheck; + + public ScheduledMessageProvider(IReliableStateManager stateManager, string storeName, SessionsProvider sessionsProvider, CancellationToken token) : base(stateManager, storeName, token) + { + this.sessionsProvider = sessionsProvider; + } + + public override async Task StartAsync() + { + await InitializeStore(); + + var builder = this.inMemorySet.ToBuilder(); + + await this.EnumerateItems(kvp => + { + var timerEvent = kvp.Value?.TaskMessage?.Event as TimerFiredEvent; + if (timerEvent == null) + { + ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Seeing a non timer event in scheduled messages while filling the pending items collection in role start"); + } + else + { + builder.Add(new Message(kvp.Key, kvp.Value)); + } + }); + + lock (@lock) + { + if (this.inMemorySet.Count > 0) + { + ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Before we set the In memory set from the builder, there are items in it which should not happen."); + } + this.inMemorySet = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); + } + + var metricsTask = LogMetrics(); + var nowait = ProcessScheduledMessages(); + } + + protected override void AddItemInMemory(string key, TaskMessageItem value) + { + lock (@lock) + { + this.inMemorySet = this.inMemorySet.Add(new Message(key, value)); + } + + var timerEvent = value.TaskMessage.Event as TimerFiredEvent; + if (timerEvent != null && timerEvent.FireAt < this.nextActivationCheck) + { + SetWaiterForNewItems(); + } + } + + // Since this method is started as part of StartAsync, the other stores maynot be immediately initialized + // by the time this invokes operations on those stores. But that would be a transient error and the next + // iteration of processing should take care of making things right. + async Task ProcessScheduledMessages() + { + while (!IsStopped()) + { + try + { + var currentTime = DateTime.UtcNow; + var nextCheck = currentTime + TimeSpan.FromSeconds(1); + + var builder = this.inMemorySet.ToBuilder(); + List> activatedMessages = new List>(); + + while (builder.Count > 0) + { + var firstPendingMessage = builder.Min; + var timerEvent = firstPendingMessage.Value.TaskMessage.Event as TimerFiredEvent; + + if (timerEvent == null) + { + throw new Exception("Internal Server Error : Ended up adding non TimerFiredEvent TaskMessage as scheduled message"); + } + + if (timerEvent.FireAt <= currentTime) + { + activatedMessages.Add(firstPendingMessage); + builder.Remove(firstPendingMessage); + } + else + { + nextCheck = timerEvent.FireAt; + break; + } + } + + if (IsStopped()) + { + break; + } + + if (activatedMessages.Count > 0) + { + var keys = activatedMessages.Select(m => m.Key); + var values = activatedMessages.Select(m => m.Value).ToList(); + + IList modifiedSessions = null; + + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var tx = this.StateManager.CreateTransaction()) + { + modifiedSessions = await this.sessionsProvider.TryAppendMessageBatchAsync(tx, values); + await this.CompleteBatchAsync(tx, keys); + await tx.CommitAsync(); + } + }, uniqueActionIdentifier: $"Action = '{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}'"); + + lock (@lock) + { + this.inMemorySet = this.inMemorySet.Except(activatedMessages); + } + + if (modifiedSessions != null) + { + foreach (var sessionId in modifiedSessions) + { + this.sessionsProvider.TryEnqueueSession(sessionId); + } + } + } + + this.nextActivationCheck = nextCheck; + await WaitForItemsAsync(this.nextActivationCheck - DateTime.UtcNow); + } + catch (Exception e) + { + ProviderEventSource.Log.ExceptionWhileRunningBackgroundJob($"{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}", e.ToString()); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + } + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs new file mode 100644 index 000000000..bafa504cc --- /dev/null +++ b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Stores +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.ServiceFabric.Data; + using Microsoft.ServiceFabric.Data.Collections; + + class SessionMessagesProvider : MessageProviderBase where TKey : IComparable, IEquatable + { + public SessionMessagesProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) + : base(stateManager, storeName, token) + { + } + + protected override void AddItemInMemory(TKey key, TValue value) + { + throw new NotSupportedException(); + } + + public override Task StartAsync() + { + return InitializeStore(); + } + + public async Task>> ReceiveBatchAsync() + { + List> result = new List>(); + if (!IsStopped()) + { + await this.EnumerateItems(kvp => result.Add(new Message(kvp.Key, kvp.Value))); + } + return result; + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs new file mode 100644 index 000000000..09d541957 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs @@ -0,0 +1,356 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Stores +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + + /// + /// There are three interesting methods in this class that require some explanation. + /// 'AcceptSession' : Intent is to fetch the next available session and hand it out to consumer. + /// 'TryUnlockSession' : Intent is to indicate that the above consumer is done processing the session. + /// 'TryEnqueueSession' : Intent is to make the session available for a consumer if needed. + /// + /// 'AcceptSession' should give a session to only one consumer at a time (strict requirement). + /// We should also give out a session with messages in it but this is somewhat loose requirement because consumer + /// can handle the scenario where there are no messages. But we don't want to keep making the session available + /// to pick up when there are no incoming messages. (So giving a session with no messages once or even twice is ok + /// but that should not happening recurringly) + /// + /// Incoming messages can come at anytime, before the session picked up, + /// after 'AcceptSession' gave out a session but the consumer read messages in it, after consumer read messages in it, or + /// after the session finished processing by consumer. + /// + /// 'AcceptSession; will fetch from 'fetchQueue' and returns it (so 'fetchQueueu' should never have a given session twice in + /// the queue otherwise 'one consumer at a time' is violated. Also, we should not keep adding to 'fetchQueue' unless the session + /// is interesting i.e., it needs processing). + /// + /// lockedSessions with 'LockState' is used to achieve the above. + /// + /// 'AcceptSession' fetches from 'fetchQueue' and transitions the session from 'InFetchQueue' to 'Locked'. + /// (The implementation contract is such that if an item is in 'fetchQueue', it should be in 'InFetchQueue' state in lockedSessions) + /// + /// 'TryEnqueuSession' will try to add to lockedSessions with 'InFetchQueue' and only first such call after the session is unlocked will be + /// able to add and that call will add the session back to 'fetchQueue'. + /// If there is an entry in lockedSessions, 'TryEnqueuSession' does only one thing - + /// if the session is already 'Locked' (that means a consumer has the session), + /// it will just transition the state to 'NewMessagesWhileLocked' (so that session could be enqueued back again as it gets unlocked). + /// Note that once the session is in this state, subsequent calls to 'TryEnqueueSession' before unlock will keep it in this state. + /// Also Note that if session is in 'InFetchQueue' state, TryEnqueuSession does not need to (and should not) change the state. + /// + /// 'TryUnlockSession' first removes the session from lockedSessions [unlock complete at this point] + /// but if the removed state is 'NewMessagesWhileLocked', it attempts to put it back on fetchQueue by calling 'TryEnqueueSession'. + /// Note that at this point if another new incoming message calls 'TryEnqueueSession' only one of these should be able to add to 'fetchQueue'. + /// + /// Note that if new messages were sent after 'AcceptSession' but before consumer reads messages from session, we + /// will hand out those messages but still add the session to fetchQueue (as part of unlock) and so the next fetch sees the session but + /// with no messages. Though this can be made perfect by introducing one more state to 'LockedState', since consumer + /// can handle no messages scenario, it's simpler to ignore the difference between the above and new messages being sent + /// after consumer reads messages but before the session is unlocked. + /// + class SessionsProvider : MessageProviderBase + { + ConcurrentQueue fetchQueue = new ConcurrentQueue(); + ConcurrentDictionary lockedSessions = new ConcurrentDictionary(); + + ConcurrentDictionary> sessionMessageProviders = new ConcurrentDictionary>(OrchestrationInstanceComparer.Default); + + public SessionsProvider(IReliableStateManager stateManager, CancellationToken token) : base(stateManager, Constants.OrchestrationDictionaryName, token) + { + } + + public async Task AcceptSessionAsync(TimeSpan receiveTimeout) + { + if (!IsStopped()) + { + string returnInstanceId; + bool newItemsBeforeTimeout = true; + while (newItemsBeforeTimeout) + { + if (this.fetchQueue.TryDequeue(out returnInstanceId)) + { + try + { + return await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.StateManager.CreateTransaction()) + { + var existingValue = await this.Store.TryGetValueAsync(txn, returnInstanceId); + + if (existingValue.HasValue) + { + if (this.lockedSessions.TryUpdate(returnInstanceId, newValue: LockState.Locked, comparisonValue: LockState.InFetchQueue)) + { + ProviderEventSource.Log.TraceMessage(returnInstanceId, "Session Locked Accepted"); + return existingValue.Value; + } + else + { + var errorMessage = $"Internal Server Error : Unexpected to dequeue the session {returnInstanceId} which was already locked before"; + ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + throw new Exception(errorMessage); + } + } + else + { + var errorMessage = $"Internal Server Error: Did not find the session object in reliable dictionary while having the session {returnInstanceId} in memory"; + ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + throw new Exception(errorMessage); + } + } + }, uniqueActionIdentifier: $"{nameof(SessionsProvider)}.{nameof(AcceptSessionAsync)}, SessionId : {returnInstanceId}"); + } + catch (Exception) + { + this.fetchQueue.Enqueue(returnInstanceId); + throw; + } + } + + newItemsBeforeTimeout = await WaitForItemsAsync(receiveTimeout); + } + } + return null; + } + + protected override void AddItemInMemory(string key, PersistentSession value) + { + this.TryEnqueueSession(key); + } + + public async Task>> ReceiveSessionMessagesAsync(PersistentSession session) + { + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(session.SessionId); + var messages = await sessionMessageProvider.ReceiveBatchAsync(); + ProviderEventSource.Log.TraceMessage(session.SessionId.InstanceId, $"Number of received messages {messages.Count}"); + return messages; + } + + public async Task CompleteMessages(ITransaction transaction, OrchestrationInstance instance, List lockTokens) + { + SessionMessagesProvider sessionMessageProvider; + if (this.sessionMessageProviders.TryGetValue(instance, out sessionMessageProvider)) + { + ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); + await sessionMessageProvider.CompleteBatchAsync(transaction, lockTokens); + } + else + { + ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); + } + } + + public async Task UpdateSessionState(ITransaction transaction, OrchestrationInstance instance, OrchestrationRuntimeState newSessionState) + { + var sessionStateEvents = newSessionState?.Events.ToImmutableList(); + var result = PersistentSession.Create(instance, sessionStateEvents); + await this.Store.SetAsync(transaction, instance.InstanceId, result); + } + + public async Task AppendMessageAsync(TaskMessageItem newMessage) + { + await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.StateManager.CreateTransaction()) + { + await this.AppendMessageAsync(txn, newMessage); + await txn.CommitAsync(); + } + }, uniqueActionIdentifier: $"Orchestration = '{newMessage.TaskMessage.OrchestrationInstance}', Action = '{nameof(SessionsProvider)}.{nameof(AppendMessageAsync)}'"); + + this.TryEnqueueSession(newMessage.TaskMessage.OrchestrationInstance); + } + + public async Task AppendMessageAsync(ITransaction transaction, TaskMessageItem newMessage) + { + await EnsureStoreInitialized(); + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(newMessage.TaskMessage.OrchestrationInstance); + await sessionMessageProvider.SendBeginAsync(transaction, new Message(Guid.NewGuid(), newMessage)); + await this.Store.TryAddAsync(transaction, newMessage.TaskMessage.OrchestrationInstance.InstanceId, PersistentSession.Create(newMessage.TaskMessage.OrchestrationInstance)); + } + + public async Task TryAppendMessageAsync(ITransaction transaction, TaskMessageItem newMessage) + { + if (await this.Store.ContainsKeyAsync(transaction, newMessage.TaskMessage.OrchestrationInstance.InstanceId)) + { + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(newMessage.TaskMessage.OrchestrationInstance); + await sessionMessageProvider.SendBeginAsync(transaction, new Message(Guid.NewGuid(), newMessage)); + return true; + } + + return false; + } + + public async Task> TryAppendMessageBatchAsync(ITransaction transaction, IEnumerable newMessages) + { + List modifiedSessions = new List(); + + var groups = newMessages.GroupBy(m => m.TaskMessage.OrchestrationInstance, OrchestrationInstanceComparer.Default); + + foreach (var group in groups) + { + if (await this.Store.ContainsKeyAsync(transaction, group.Key.InstanceId)) + { + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(group.Key); + await sessionMessageProvider.SendBatchBeginAsync(transaction, group.Select(tm => new Message(Guid.NewGuid(), tm))); + modifiedSessions.Add(group.Key); + } + } + + return modifiedSessions; + } + + public async Task AppendMessageBatchAsync(ITransaction transaction, IEnumerable newMessages) + { + var groups = newMessages.GroupBy(m => m.TaskMessage.OrchestrationInstance, OrchestrationInstanceComparer.Default); + + foreach (var group in groups) + { + var groupMessages = group.AsEnumerable(); + + await this.Store.TryAddAsync(transaction, group.Key.InstanceId, PersistentSession.Create(group.Key)); + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(group.Key); + await sessionMessageProvider.SendBatchBeginAsync(transaction, group.Select(tm => new Message(Guid.NewGuid(), tm))); + } + } + + public async Task> GetSessions() + { + await EnsureStoreInitialized(); + + var result = new List(); + await this.EnumerateItems(kvp => result.Add(kvp.Value)); + return result; + } + + public void TryUnlockSession(OrchestrationInstance instance, bool abandon = false, bool isComplete = false) + { + ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); + LockState lockState; + if (!this.lockedSessions.TryRemove(instance.InstanceId, out lockState) || lockState == LockState.InFetchQueue) + { + var errorMessage = $"{nameof(SessionsProvider)}.{nameof(TryUnlockSession)} : Trying to unlock the session {instance.InstanceId} which was not locked."; + ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + throw new Exception(errorMessage); + } + + if (!isComplete && (abandon || lockState == LockState.NewMessagesWhileLocked)) + { + this.TryEnqueueSession(instance); + } + ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Session Unlock End, Abandon = {abandon}, removed lock state = {lockState}"); + } + + public async Task TryAddSession(ITransaction transaction, TaskMessageItem newMessage) + { + await EnsureStoreInitialized(); + + bool added = await this.Store.TryAddAsync(transaction, newMessage.TaskMessage.OrchestrationInstance.InstanceId, PersistentSession.Create(newMessage.TaskMessage.OrchestrationInstance)); + + if (added) + { + var sessionMessageProvider = await GetOrAddSessionMessagesInstance(newMessage.TaskMessage.OrchestrationInstance); + await sessionMessageProvider.SendBeginAsync(transaction, new Message(Guid.NewGuid(), newMessage)); + } + + return added; + } + + public async Task GetSession(string instanceId) + { + await EnsureStoreInitialized(); + + return await RetryHelper.ExecuteWithRetryOnTransient(async () => + { + using (var txn = this.StateManager.CreateTransaction()) + { + var value = await this.Store.TryGetValueAsync(txn, instanceId); + if (value.HasValue) + { + return value.Value; + } + } + return null; + }, uniqueActionIdentifier: $"Orchestration InstanceId = {instanceId}, Action = {nameof(SessionsProvider)}.{nameof(GetSession)}"); + } + + public void TryEnqueueSession(OrchestrationInstance instance) + { + this.TryEnqueueSession(instance.InstanceId); + } + + void TryEnqueueSession(string instanceId) + { + if (this.lockedSessions.TryAdd(instanceId, LockState.InFetchQueue)) + { + ProviderEventSource.Log.TraceMessage(instanceId, "Session Getting Enqueued"); + this.fetchQueue.Enqueue(instanceId); + SetWaiterForNewItems(); + } + else + { + this.lockedSessions.TryUpdate(instanceId, LockState.NewMessagesWhileLocked, LockState.Locked); + } + } + + public async Task DropSession(ITransaction txn, OrchestrationInstance instance) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + await this.Store.TryRemoveAsync(txn, instance.InstanceId); + + SessionMessagesProvider sessionMessagesProvider; + this.sessionMessageProviders.TryRemove(instance, out sessionMessagesProvider); + + var noWait = RetryHelper.ExecuteWithRetryOnTransient(() => this.StateManager.RemoveAsync(GetSessionMessagesDictionaryName(instance)), + uniqueActionIdentifier: $"Orchestration = '{instance}', Action = 'DropSessionMessagesDictionaryBackgroundTask'"); + } + + async Task> GetOrAddSessionMessagesInstance(OrchestrationInstance instance) + { + var newInstance = new SessionMessagesProvider(this.StateManager, GetSessionMessagesDictionaryName(instance), this.CancellationToken); + var sessionMessageProvider = this.sessionMessageProviders.GetOrAdd(instance, newInstance); + await sessionMessageProvider.EnsureStoreInitialized(); + return sessionMessageProvider; + } + + string GetSessionMessagesDictionaryName(OrchestrationInstance instance) + { + var sessionKey = $"{instance.InstanceId}_{instance.ExecutionId}"; + return Constants.SessionMessagesDictionaryPrefix + sessionKey; + } + + enum LockState + { + InFetchQueue = 0, + + Locked, + + NewMessagesWhileLocked + } + } +} diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs b/src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs new file mode 100644 index 000000000..7195ecdc0 --- /dev/null +++ b/src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.TaskHelpers +{ + using System; + + class CountBasedFixedDelayRetryPolicy : RetryPolicy + { + readonly int maxNumberOfAttempts; + readonly TimeSpan delay; + int pendingAttempts; + + public CountBasedFixedDelayRetryPolicy(int maxNumberOfAttempts, TimeSpan delay) + { + this.maxNumberOfAttempts = maxNumberOfAttempts; + this.delay = delay; + this.pendingAttempts = maxNumberOfAttempts; + } + + public override bool ShouldExecute() + { + return this.pendingAttempts-- > 0; + } + + public override TimeSpan GetNextDelay() + { + if (this.pendingAttempts < 1) + { + return TimeSpan.Zero; + } + return this.delay; + } + + public static RetryPolicy GetNewDefaultPolicy() + { + return new CountBasedFixedDelayRetryPolicy(3, TimeSpan.FromMilliseconds(100)); + } + } +} diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs new file mode 100644 index 000000000..b8f15bb52 --- /dev/null +++ b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs @@ -0,0 +1,112 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.TaskHelpers +{ + using System; + using System.Diagnostics; + using System.Threading.Tasks; + + using DurableTask.ServiceFabric.Tracing; + + static class RetryHelper + { + public static Task ExecuteWithRetryOnTransient(Func action, string uniqueActionIdentifier) + { + return ExecuteWithRetryOnTransient(action, CountBasedFixedDelayRetryPolicy.GetNewDefaultPolicy(), uniqueActionIdentifier); + } + + static async Task ExecuteWithRetryOnTransient(Func action, RetryPolicy retryPolicy, string uniqueActionIdentifier) + { + Exception lastException = null; + + int attemptNumber = 0; + while (retryPolicy.ShouldExecute()) + { + attemptNumber++; + Stopwatch timer = Stopwatch.StartNew(); + try + { + await action(); + timer.Stop(); + ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); + return; + } + catch (Exception e) + { + timer.Stop(); + lastException = e; + bool shouldRetry = ExceptionUtilities.IsRetryableFabricException(e); + + ExceptionUtilities.LogReliableCollectionException(uniqueActionIdentifier, attemptNumber, e, shouldRetry); + ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); + + if (shouldRetry) + { + await Task.Delay(retryPolicy.GetNextDelay()); + } + else + { + throw; + } + } + } + + throw lastException; + } + + public static Task ExecuteWithRetryOnTransient(Func> action, string uniqueActionIdentifier) + { + return ExecuteWithRetryOnTransient(action, CountBasedFixedDelayRetryPolicy.GetNewDefaultPolicy(), uniqueActionIdentifier); + } + + static async Task ExecuteWithRetryOnTransient(Func> action, RetryPolicy retryPolicy, string uniqueActionIdentifier) + { + Exception lastException = null; + + int attemptNumber = 0; + while (retryPolicy.ShouldExecute()) + { + attemptNumber++; + Stopwatch timer = Stopwatch.StartNew(); + try + { + var result = await action(); + timer.Stop(); + ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); + return result; + } + catch (Exception e) + { + timer.Stop(); + lastException = e; + bool shouldRetry = ExceptionUtilities.IsRetryableFabricException(e); + + ExceptionUtilities.LogReliableCollectionException(uniqueActionIdentifier, attemptNumber, e, shouldRetry); + ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); + + if (shouldRetry) + { + await Task.Delay(retryPolicy.GetNextDelay()); + } + else + { + throw; + } + } + } + + throw lastException; + } + } +} diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs b/src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs new file mode 100644 index 000000000..7b3f46e7c --- /dev/null +++ b/src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs @@ -0,0 +1,24 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.TaskHelpers +{ + using System; + + abstract class RetryPolicy + { + public abstract bool ShouldExecute(); + + public abstract TimeSpan GetNextDelay(); + } +} diff --git a/src/DurableTask.ServiceFabric/TaskMessageItem.cs b/src/DurableTask.ServiceFabric/TaskMessageItem.cs new file mode 100644 index 000000000..94d19fa15 --- /dev/null +++ b/src/DurableTask.ServiceFabric/TaskMessageItem.cs @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Runtime.Serialization; + using DurableTask.Core; + + [DataContract] + sealed class TaskMessageItem : IExtensibleDataObject + { + public TaskMessageItem(TaskMessage taskMessage) + { + this.TaskMessage = taskMessage ?? throw new ArgumentNullException(nameof(taskMessage)); + } + + [DataMember] + public TaskMessage TaskMessage { get; private set; } + + public ExtensionDataObject ExtensionData { get; set; } + } +} diff --git a/src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs b/src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs new file mode 100644 index 000000000..0679aa7a9 --- /dev/null +++ b/src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Collections.Generic; + using DurableTask.Core.History; + + sealed class TimerFiredEventComparer : IComparer> + { + public static readonly TimerFiredEventComparer Instance = new TimerFiredEventComparer(); + + public int Compare(Message first, Message second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + var firstTimer = first.Value.TaskMessage.Event as TimerFiredEvent; + var secondTimer = second.Value.TaskMessage.Event as TimerFiredEvent; + + if (firstTimer == null) + { + throw new ArgumentException(nameof(first)); + } + if (secondTimer == null) + { + throw new ArgumentException(nameof(second)); + } + + int firedAtCompareValue = DateTime.Compare(firstTimer.FireAt, secondTimer.FireAt); + if (firedAtCompareValue != 0) + { + return firedAtCompareValue; + } + + return string.Compare(first.Key, second.Key, StringComparison.Ordinal); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs new file mode 100644 index 000000000..a8cf90d9f --- /dev/null +++ b/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs @@ -0,0 +1,188 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tracing +{ + using System.Diagnostics.Tracing; + using System.Threading.Tasks; + using System; + + /// + /// The event source which emits ETW events for service fabric based provider functionality. + /// + [EventSource(Name = "DurableTask-ServiceFabricProvider", Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C")] + public sealed class ProviderEventSource : EventSource + { + /// + /// Singleton instance of the event source. + /// + public static readonly ProviderEventSource Log = new ProviderEventSource(); + + static ProviderEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + private ProviderEventSource() : base() + { + } + + private static class Keywords + { + public const EventKeywords Orchestration = (EventKeywords)0x1L; + public const EventKeywords Activity = (EventKeywords)0x2L; + public const EventKeywords Common = (EventKeywords)0x4L; + public const EventKeywords Warning = (EventKeywords)0x8L; + public const EventKeywords Error = (EventKeywords)0x10L; + public const EventKeywords NetworkAction = (EventKeywords)0x20L; + } + + #region Informational 1-500 + [Event(1, Level = EventLevel.Informational, Message = "Orchestration with instanceId : '{0}' and executionId : '{1}' is Created.", Channel = EventChannel.Operational)] + internal void OrchestrationCreated(string instanceId, string executionId) + { + if (IsEnabled(EventLevel.Informational, Keywords.Orchestration)) + { + WriteEvent(1, instanceId, executionId); + } + } + + [Event(2, Level = EventLevel.Informational, Message = "Orchestration with instanceId : '{0}' and executionId : '{4}' Finished with the status {1} and result {3} in {2} seconds.", Channel = EventChannel.Operational)] + internal void OrchestrationFinished(string instanceId, string terminalStatus, double runningTimeInSeconds, string result, string executionId) + { + if (IsEnabled(EventLevel.Informational, Keywords.Orchestration)) + { + WriteEvent(2, instanceId, terminalStatus, runningTimeInSeconds, result, executionId); + } + } + + [Event(4, Level = EventLevel.Informational, Message = "Current number of entries in store {0} : {1}", Channel = EventChannel.Operational)] + internal void LogStoreCount(string storeName, long count) + { + if (IsEnabled(EventLevel.Informational, Keywords.Common)) + { + WriteEvent(4, storeName, count); + } + } + + [Event(8, Level = EventLevel.Informational, Message = "Time taken for {0} : {1} milli seconds.", Channel = EventChannel.Operational)] + internal void LogTimeTaken(string uniqueActionIdentifier, double elapsedMilliseconds) + { + if (IsEnabled(EventLevel.Informational, Keywords.Common)) + { + WriteEvent(8, uniqueActionIdentifier, elapsedMilliseconds); + } + } + + [Event(9, Level = EventLevel.Informational, Message = "{0} : {1}", Channel = EventChannel.Operational)] + internal void ReliableStateManagement(string operationIdentifier, string operationData) + { + if (IsEnabled(EventLevel.Informational, Keywords.Common)) + { + WriteEvent(9, operationIdentifier, operationData); + } + } + + [Event(10, Level = EventLevel.Informational, Message = "Serving action: {0}", Channel = EventChannel.Operational)] + internal void LogServingNetworkAction(string operation) + { + if (IsEnabled(EventLevel.Informational, Keywords.NetworkAction)) + { + WriteEvent(10, operation); + } + } + #endregion + + #region Warnings 1001-1500 + [Event(1001, Level = EventLevel.Warning, Message = "Exception in the background job {0} : {1}", Channel = EventChannel.Operational)] + internal void ExceptionWhileRunningBackgroundJob(string operationIdentifier, string exception) + { + if (IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) + { + WriteEvent(1001, operationIdentifier, exception); + } + } + + [Event(1002, Level = EventLevel.Warning, Message = "Hint : {0}, AttemptNumber : {1}, Exception: {2}", Channel = EventChannel.Operational)] + internal void RetryableFabricException(string uniqueIdentifier, int attemptNumber, string exception) + { + if (IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) + { + WriteEvent(1002, uniqueIdentifier, attemptNumber, exception); + } + } + #endregion + + #region Errors 1501-2000 + [Event(1501, Level = EventLevel.Error, Message = "We are seeing something that we don't expect to see : {0}", Channel = EventChannel.Operational)] + internal void UnexpectedCodeCondition(string uniqueMessage) + { + if (IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) + { + WriteEvent(1501, uniqueMessage); + } + +#if DEBUG + // This is so that tests fail when this event happens. + throw new Exception(uniqueMessage); +#endif + } + + [Event(1502, Level = EventLevel.Error, Message = "Hint : {0}, Exception: {1}", Channel = EventChannel.Operational)] + internal void ExceptionInReliableCollectionOperations(string uniqueIdentifier, string exception) + { + if (IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) + { + WriteEvent(1502, uniqueIdentifier, exception); + } + } + #endregion + + #region Verbose Events 501-1000 + [Event(501, Level = EventLevel.Verbose, Message = "Trace Message for Session {0} : {1}", Channel = EventChannel.Debug)] + internal void TraceMessage(string instanceId, string message) + { +#if DEBUG + if (IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + WriteEvent(501, instanceId, message); + } +#endif + } + + [Event(502, Level = EventLevel.Verbose, Message = "Time taken for {0} : {1} milli seconds.", Channel = EventChannel.Analytic)] + internal void LogMeasurement(string uniqueActionIdentifier, long elapsedMilliseconds) + { +#if DEBUG + if (IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + WriteEvent(502, uniqueActionIdentifier, elapsedMilliseconds); + } +#endif + } + + [Event(503, Level = EventLevel.Verbose, Message = "Size of {0} : {1} bytes.", Channel = EventChannel.Analytic)] + internal void LogSizeMeasure(string uniqueObjectIdentifier, long sizeInBytes) + { +#if DEBUG + if (IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + WriteEvent(503, uniqueObjectIdentifier, sizeInBytes); + } +#endif + } + #endregion + } +} diff --git a/src/DurableTask.ServiceFabric/Utils.cs b/src/DurableTask.ServiceFabric/Utils.cs new file mode 100644 index 000000000..b0d234d62 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Utils.cs @@ -0,0 +1,88 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.ServiceFabric.Tracing; + + internal static class Utils + { + public static OrchestrationState BuildOrchestrationState(OrchestrationRuntimeState runtimeState) + { + return new OrchestrationState + { + OrchestrationInstance = runtimeState.OrchestrationInstance, + ParentInstance = runtimeState.ParentInstance, + Name = runtimeState.Name, + Version = runtimeState.Version, + Status = runtimeState.Status, + Tags = runtimeState.Tags, + OrchestrationStatus = runtimeState.OrchestrationStatus, + CreatedTime = runtimeState.CreatedTime, + CompletedTime = runtimeState.CompletedTime, + LastUpdatedTime = DateTime.UtcNow, + Size = runtimeState.Size, + CompressedSize = runtimeState.CompressedSize, + Input = runtimeState.Input, + Output = runtimeState.Output + }; + } + + public static TimeSpan Measure(Action action) + { + var timer = Stopwatch.StartNew(); + action(); + timer.Stop(); + return timer.Elapsed; + } + + public static async Task MeasureAsync(Func asyncAction) + { + var timer = Stopwatch.StartNew(); + await asyncAction(); + timer.Stop(); + return timer.Elapsed; + } + + public static async Task RunBackgroundJob(Func loopAction, TimeSpan initialDelay, TimeSpan delayOnSuccess, TimeSpan delayOnException, string actionName, CancellationToken token) + { + try + { + await Task.Delay(initialDelay, token); + } + catch(TaskCanceledException) + { + } + + while (!token.IsCancellationRequested) + { + try + { + await loopAction(); + await Task.Delay(delayOnSuccess).ConfigureAwait(false); + } + catch(Exception e) + { + ProviderEventSource.Log.ExceptionWhileRunningBackgroundJob(actionName, e.ToString()); + await Task.Delay(delayOnException).ConfigureAwait(false); + } + } + } + } +} From 0d08fd744283a9e6af1a4e0e73ad43b5f8e933fd Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Fri, 22 Feb 2019 11:23:46 -0800 Subject: [PATCH 02/12] Fixes: 1. Provide ability to store last N execution ids. 2. Provide ability to retun history (with the last event). 3. Fix Generic parameters for the stores. 4. Address code review comments. 5. Implement FabricService. --- .../DeploymentUtil/ApplicationInfoReader.cs | 5 +- .../Utilities.cs | 5 +- .../TimerFiredEventComparerTests.cs | 33 ++-- .../OwinCommunicationListener.cs | 136 ------------- .../Program.cs | 13 +- .../Startup.cs | 30 +-- .../TestFabricServiceSettings.cs | 96 +++++++++ .../TestStatefulService.cs | 171 ---------------- .../AsyncManualResetEvent.cs | 15 +- .../DurableTask.ServiceFabric.csproj | 5 + .../ExceptionUtilities.cs | 4 +- .../FabricOrchestrationInstanceStore.cs | 42 ++-- .../FabricOrchestrationProvider.cs | 12 -- .../FabricOrchestrationService.cs | 34 ++-- .../FabricOrchestrationServiceClient.cs | 10 +- .../FabricOrchestrationServiceController.cs | 16 +- .../FabricProviderClient.cs | 4 +- ...FabricOrchestrationServiceInstanceStore.cs | 2 +- .../IFabricProviderClient.cs | 6 +- ...vider.cs => IPartitionEndpointResolver.cs} | 8 +- .../RemoteOrchestrationServiceClient.cs | 29 +-- .../Service}/DefaultDependencyResolver.cs | 26 +-- .../FabricPartitionEndpointResolver.cs | 24 ++- .../Service/FabricService.cs | 182 ++++++++++++++++++ .../Service/IFabricServiceSettings.cs | 65 +++++++ .../Service}/IOwinAppBuilder.cs | 18 +- .../Service/OwinCommunicationListener.cs | 107 ++++++++++ .../Stores/ActivityProvider.cs | 14 +- .../Stores/MessageProviderBase.cs | 4 +- .../Stores/ScheduledMessageProvider.cs | 18 +- .../Stores/SessionMessagesProvider.cs | 10 +- .../Stores/SessionsProvider.cs | 58 +++--- .../TaskHelpers/RetryHelper.cs | 8 +- .../TimerFiredEventComparer.cs | 6 +- .../Tracing/ProviderEventSource.cs | 2 +- .../Tracing/ServiceEventSource.cs | 121 ++++++++++++ src/DurableTask.ServiceFabric/Utils.cs | 2 +- 37 files changed, 826 insertions(+), 515 deletions(-) delete mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs create mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs delete mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs rename src/DurableTask.ServiceFabric/{IPartitionProvider.cs => IPartitionEndpointResolver.cs} (83%) rename {Test/TestFabricApplication/TestApplication.StatefulService => src/DurableTask.ServiceFabric/Service}/DefaultDependencyResolver.cs (67%) rename Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs => src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs (62%) create mode 100644 src/DurableTask.ServiceFabric/Service/FabricService.cs create mode 100644 src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs rename {Test/TestFabricApplication/TestApplication.StatefulService => src/DurableTask.ServiceFabric/Service}/IOwinAppBuilder.cs (62%) create mode 100644 src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs create mode 100644 src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs index 6c21af580..9de909184 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs @@ -87,10 +87,7 @@ public NameValueCollection GetApplicationParameters() return parameters; } - public string ApplicationPackagePath - { - get { return this.applicationPackagePath; } - } + public string ApplicationPackagePath => this.applicationPackagePath; string GetSingleNodeAttributeValue(XmlElement root, XmlNamespaceManager namespaceManager, string queryPath, string attributeName) { diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs index 968b4b7dc..08a736396 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -18,15 +18,16 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Net.Http; using System.Threading.Tasks; using DurableTask.Core; + using DurableTask.ServiceFabric.Service; using Microsoft.VisualStudio.TestTools.UnitTesting; public static class Utilities { public static TaskHubClient CreateTaskHubClient() { - var partitionProvider = new SimplePartitionProvider(new Uri("http://localhost:30303/api/dtfx/")); + var partitionProvider = new FabricPartitionEndpointResolver(); var httpClient = new HttpClient(); - return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider, httpClient)); + return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider)); } public static async Task ThrowsException(Func action, string expectedMessage) where TException : Exception diff --git a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs index 7d8c77104..1c21de39f 100644 --- a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs +++ b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs @@ -15,6 +15,8 @@ namespace DurableTask.ServiceFabric.Tests { using System; using System.Collections.Immutable; + using System.Linq; + using DurableTask.Core; using DurableTask.Core.History; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,7 +27,7 @@ public class TimerFiredEventComparerTests [TestMethod] public void Messages_With_Equal_FiredAt_Value_With_Different_Key_Are_Treated_Separate_And_Sorted_On_Key() { - ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); var instance = new OrchestrationInstance() { @@ -36,10 +38,12 @@ public void Messages_With_Equal_FiredAt_Value_With_Different_Key_Are_Treated_Sep var builder = timerMessages.ToBuilder(); int numberOfMessages = 5; + var guids = Enumerable.Range(0, numberOfMessages).Select(x => Guid.NewGuid()).ToList(); + // Add them in reverse order to expected order just to make the test case more interesting. - for (int i = numberOfMessages; i >= 1; i--) + for (int i = numberOfMessages -1 ; i >= 0; i--) { - builder.Add(new Message($"Message{i}", new TaskMessageItem(new TaskMessage() + builder.Add(new Message(guids[i], new TaskMessageItem(new TaskMessage() { SequenceNumber = i, OrchestrationInstance = instance, @@ -51,12 +55,14 @@ public void Messages_With_Equal_FiredAt_Value_With_Different_Key_Are_Treated_Sep timerMessages = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); Assert.AreEqual(numberOfMessages, timerMessages.Count); + guids = guids.OrderBy(x => x).ToList(); + // Now enumerating the items should result in right order. - for (int i = 1; i <= numberOfMessages; i++) + for (int i = 0; i < numberOfMessages; i++) { var min = timerMessages.Min; timerMessages = timerMessages.Remove(min); - Assert.AreEqual($"Message{i}", min.Key, "Ordering seems to be broken"); + Assert.AreEqual(guids[i], min.Key, "Ordering seems to be broken"); } Assert.AreEqual(0, timerMessages.Count); @@ -65,7 +71,7 @@ public void Messages_With_Equal_FiredAt_Value_With_Different_Key_Are_Treated_Sep [TestMethod] public void Messages_With_Different_FiredAt_Value_Are_Sorted_Based_On_FiredAt() { - ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); var instance = new OrchestrationInstance() { @@ -76,10 +82,12 @@ public void Messages_With_Different_FiredAt_Value_Are_Sorted_Based_On_FiredAt() var builder = timerMessages.ToBuilder(); int numberOfMessages = 5; + var guids = Enumerable.Range(0, numberOfMessages).Select(x => Guid.NewGuid()).ToList(); + // Add them in reverse order to expected order just to make the test case more interesting. - for (int i = numberOfMessages; i >= 1; i--) + for (int i = numberOfMessages -1 ; i >= 0; i--) { - builder.Add(new Message($"Message{6-i}", new TaskMessageItem(new TaskMessage() //Let the key values be in reverse order to fired at values again to make test more interesting. + builder.Add(new Message(guids[i], new TaskMessageItem(new TaskMessage() //Let the key values be in reverse order to fired at values again to make test more interesting. { SequenceNumber = i, OrchestrationInstance = instance, @@ -92,13 +100,13 @@ public void Messages_With_Different_FiredAt_Value_Are_Sorted_Based_On_FiredAt() Assert.AreEqual(numberOfMessages, timerMessages.Count); // Now enumerating the items should result in right order. - for (int i = 1; i <= numberOfMessages; i++) + for (int i = 0; i < numberOfMessages; i++) { var min = timerMessages.Min; timerMessages = timerMessages.Remove(min); var firedAt = (min.Value.TaskMessage.Event as TimerFiredEvent)?.FireAt; Assert.AreEqual(currentTime + TimeSpan.FromSeconds(i), firedAt, "Ordering seems to be broken"); - Assert.AreEqual($"Message{6 - i}", min.Key); + Assert.AreEqual(guids[i], min.Key); } Assert.AreEqual(0, timerMessages.Count); @@ -107,7 +115,7 @@ public void Messages_With_Different_FiredAt_Value_Are_Sorted_Based_On_FiredAt() [TestMethod] public void Messages_With_Equal_FiredAt_Value_With_Same_Key_Are_Treated_Equal() { - ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + ImmutableSortedSet> timerMessages = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); var instance = new OrchestrationInstance() { @@ -119,9 +127,10 @@ public void Messages_With_Equal_FiredAt_Value_With_Same_Key_Are_Treated_Equal() int numberOfMessages = 3; // Add them in reverse order to expected order just to make the test case more interesting. + var guid = Guid.NewGuid(); for (int i = numberOfMessages; i >= 1; i--) { - builder.Add(new Message("Message", new TaskMessageItem(new TaskMessage() + builder.Add(new Message(guid, new TaskMessageItem(new TaskMessage() { SequenceNumber = i, OrchestrationInstance = instance, diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs b/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs deleted file mode 100644 index c6fe9f3b2..000000000 --- a/Test/TestFabricApplication/TestApplication.StatefulService/OwinCommunicationListener.cs +++ /dev/null @@ -1,136 +0,0 @@ -// ---------------------------------------------------------------------------------- -// 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 TestApplication.StatefulService -{ - using System; - using System.Diagnostics; - using System.Fabric; - using System.Fabric.Description; - using System.Globalization; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Owin.Hosting; - using Microsoft.ServiceFabric.Services.Communication.Runtime; - - public class OwinCommunicationListener : ICommunicationListener - { - private readonly ServiceContext serviceContext; - - /// - /// OWIN server handle. - /// - private IDisposable serverHandle; - - private IOwinAppBuilder startup; - private string publishAddress; - private string listeningAddress; - private string appRoot; - - public OwinCommunicationListener(IOwinAppBuilder startup, ServiceContext serviceContext) - : this(null, startup, serviceContext) - { - } - - public OwinCommunicationListener(string appRoot, IOwinAppBuilder startup, ServiceContext serviceContext) - { - this.startup = startup; - this.appRoot = appRoot; - this.serviceContext = serviceContext; - } - - public Task OpenAsync(CancellationToken cancellationToken) - { - Trace.WriteLine("Initialize"); - - EndpointResourceDescription serviceEndpoint = this.serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); - int port = serviceEndpoint.Port; - - if (this.serviceContext is StatefulServiceContext) - { - StatefulServiceContext statefulInitParams = (StatefulServiceContext)this.serviceContext; - - this.listeningAddress = String.Format( - CultureInfo.InvariantCulture, - "http://+:{0}/", - port); - } - else if (this.serviceContext is StatelessServiceContext) - { - this.listeningAddress = String.Format( - CultureInfo.InvariantCulture, - "http://+:{0}/{1}", - port, - string.IsNullOrWhiteSpace(this.appRoot) - ? "" - : this.appRoot.TrimEnd('/') + '/'); - } - else - { - throw new InvalidOperationException(); - } - - this.publishAddress = this.listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); - - Trace.WriteLine(String.Format("Opening on {0}", this.publishAddress)); - - try - { - Trace.WriteLine(String.Format("Starting web server on {0}", this.listeningAddress)); - - this.serverHandle = WebApp.Start(this.listeningAddress, appBuilder => this.startup.Configuration(appBuilder)); - - return Task.FromResult(this.publishAddress); - } - catch (Exception ex) - { - Trace.WriteLine(ex); - - this.StopWebServer(); - - throw; - } - } - - public Task CloseAsync(CancellationToken cancellationToken) - { - Trace.WriteLine("Close"); - - this.StopWebServer(); - - return Task.FromResult(true); - } - - public void Abort() - { - Trace.WriteLine("Abort"); - - this.StopWebServer(); - } - - private void StopWebServer() - { - if (this.serverHandle != null) - { - try - { - this.serverHandle.Dispose(); - } - catch (ObjectDisposedException) - { - // no-op - } - } - } - } -} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index bc4d922d4..b1703fde1 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -16,6 +16,7 @@ namespace TestApplication.StatefulService using System; using System.Diagnostics; using System.Threading; + using DurableTask.ServiceFabric.Service; using Microsoft.ServiceFabric.Services.Runtime; internal static class Program @@ -32,10 +33,16 @@ private static void Main() // When Service Fabric creates an instance of this service type, // an instance of the class is created in this host process. - ServiceRuntime.RegisterServiceAsync("StatefulServiceType", - context => new TestStatefulService(context)).GetAwaiter().GetResult(); + // ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => new TestStatefulService(context)).GetAwaiter().GetResult(); - ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(TestStatefulService).Name); + ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => + { + var settings = new TestFabricServiceSettings(); + var fabricService = new FabricService(context, settings); + return fabricService; + }).GetAwaiter().GetResult(); + + // ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(TestStatefulService).Name); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs index 32a283932..235651daf 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs @@ -18,19 +18,36 @@ namespace TestApplication.StatefulService using DurableTask.Core; using DurableTask.ServiceFabric; + using DurableTask.ServiceFabric.Service; using Microsoft.Extensions.DependencyInjection; using Owin; class Startup : IOwinAppBuilder { FabricOrchestrationProvider fabricOrchestrationProvider; + string listeningAddress; - public Startup(FabricOrchestrationProvider fabricOrchestrationProvider) + public Startup(string listeningAddress, FabricOrchestrationProvider fabricOrchestrationProvider) { + this.listeningAddress = listeningAddress; this.fabricOrchestrationProvider = fabricOrchestrationProvider; } - public void Configuration(IAppBuilder appBuilder) + public string GetListeningAddress() + { + return this.listeningAddress; + } + + private ServiceProvider GenerateServiceProvider() + { + var services = new ServiceCollection(); + services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); + services.AddTransient(); + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider; + } + + void IOwinAppBuilder.Startup(IAppBuilder appBuilder) { ServicePointManager.DefaultConnectionLimit = 256; HttpConfiguration config = new HttpConfiguration(); @@ -45,14 +62,5 @@ public void Configuration(IAppBuilder appBuilder) config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; appBuilder.UseWebApi(config); } - - private ServiceProvider GenerateServiceProvider() - { - var services = new ServiceCollection(); - services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); - services.AddTransient(); - var serviceProvider = services.BuildServiceProvider(); - return serviceProvider; - } } } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs new file mode 100644 index 000000000..dbdc6573d --- /dev/null +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs @@ -0,0 +1,96 @@ +// ---------------------------------------------------------------------------------- +// 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 TestApplication.StatefulService +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Globalization; + + using DurableTask.Core; + using DurableTask.ServiceFabric; + using DurableTask.ServiceFabric.Service; + using DurableTask.Test.Orchestrations.Performance; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using TestApplication.Common.Orchestrations; + + + /// + public class TestFabricServiceSettings : IFabricServiceSettings + { + + public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } + + /// + public int GetActivityDispatcherCount() + { + return 5; + } + + /// + public IEnumerable GetActivityTypes() + { + return new Type[] + { + typeof(GetUserTask), + typeof(GreetUserTask), + typeof(GenerationBasicTask), + typeof(RandomTimeWaitingTask), + typeof(ExceptionThrowingTask), + typeof(ExecutionCountingActivity) + }; + } + + /// + public int GetOrchestrationDispatcherCount() + { + return 5; + } + + /// + public IEnumerable GetOrchestrationTypes() + { + return new Type[] + { + typeof(SimpleOrchestrationWithTasks), + typeof(SimpleOrchestrationWithTimer), + typeof(GenerationBasicOrchestration), + typeof(SimpleOrchestrationWithSubOrchestration), + typeof(DriverOrchestration), + typeof(TestOrchestration), + typeof(ExecutionCountingOrchestration) + }; + } + + /// + public IEnumerable GetServiceReplicaListeners() + { + yield return new ServiceReplicaListener(context => + { + var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); + int port = serviceEndpoint.Port; + + string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://+:{0}/", port) + .Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); + return new OwinCommunicationListener(new Startup(listeningAddress, this.FabricOrchestrationProvider)); + }); + } + + /// + public IEnumerable> GetTaskOrchestrations() + { + yield return new KeyValuePair(typeof(OrchestrationRunningIntoRetry).Name, new OrchestrationRunningIntoRetry()); + } + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs deleted file mode 100644 index ac6ee6bb3..000000000 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestStatefulService.cs +++ /dev/null @@ -1,171 +0,0 @@ -// ---------------------------------------------------------------------------------- -// 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 TestApplication.StatefulService -{ - using System; - using System.Collections.Generic; - using System.Fabric; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using DurableTask.Core; - using DurableTask.ServiceFabric; - using DurableTask.Test.Orchestrations.Performance; - using Microsoft.ServiceFabric.Services.Communication.Runtime; - using Microsoft.ServiceFabric.Services.Runtime; - using TestApplication.Common.Orchestrations; - - /// - /// An instance of this class is created for each service replica by the Service Fabric runtime. - /// - internal sealed class TestStatefulService : StatefulService - { - readonly FabricOrchestrationProviderFactory fabricProviderFactory; - - FabricOrchestrationProvider fabricProvider; - TaskHubWorker worker; - TaskHubClient client; - ReplicaRole currentRole; - - public TestStatefulService(StatefulServiceContext context) : base(context) - { - var settings = new FabricOrchestrationProviderSettings(); - settings.TaskOrchestrationDispatcherSettings.DispatcherCount = 5; - settings.TaskActivityDispatcherSettings.DispatcherCount = 5; - - this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, settings); - this.fabricProvider = this.fabricProviderFactory.CreateProvider(); - this.client = new TaskHubClient(fabricProvider.OrchestrationServiceClient); - } - - /// - /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. - /// - /// - /// For more information on service communication, see http://aka.ms/servicefabricservicecommunication - /// - /// A collection of listeners. - protected override IEnumerable CreateServiceReplicaListeners() - { - return new[] - { - new ServiceReplicaListener(initParams => new OwinCommunicationListener("TestStatefulService", new Startup(this.fabricProvider), initParams)) - }; - } - - /// - /// This is the main entry point for your service replica. - /// This method executes when this replica of your service becomes primary and has write status. - /// - /// Canceled when Service Fabric needs to shut down this service replica. - protected override async Task RunAsync(CancellationToken cancellationToken) - { - if (this.fabricProvider == null) - { - this.fabricProvider = this.fabricProviderFactory.CreateProvider(); - this.client = new TaskHubClient(this.fabricProvider.OrchestrationServiceClient); - } - - this.worker = new TaskHubWorker(this.fabricProvider.OrchestrationService); - - await this.worker - .AddTaskOrchestrations(KnownOrchestrationInstances.Values.Select(instance => new DefaultObjectCreator(instance)).ToArray()) - .AddTaskOrchestrations(KnownOrchestrationTypeNames.Values.ToArray()) - .AddTaskActivities(KnownActivities) - .StartAsync(); - - this.worker.TaskActivityDispatcher.IncludeDetails = true; - } - - protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) - { - ServiceEventSource.Current.ServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); - if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) - { - await ShutdownAsync(); - } - this.currentRole = newRole; - ServiceEventSource.Current.ServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); - } - - protected override async Task OnCloseAsync(CancellationToken cancellationToken) - { - ServiceEventSource.Current.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); - await ShutdownAsync(); - } - - async Task ShutdownAsync() - { - try - { - if (this.worker != null) - { - ServiceEventSource.Current.ServiceMessage(this, "Stopping Taskhub Worker"); - await this.worker.StopAsync(isForced: true); - this.worker.Dispose(); - this.fabricProvider.Dispose(); - this.fabricProvider = null; - this.worker = null; - ServiceEventSource.Current.ServiceMessage(this, "Stopped Taskhub Worker"); - } - } - catch (Exception e) - { - ServiceEventSource.Current.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); - throw; - } - } - - Type GetOrchestrationType(string typeName) - { - if (KnownOrchestrationInstances.ContainsKey(typeName)) - { - return KnownOrchestrationInstances[typeName].GetType(); - } - - if (!KnownOrchestrationTypeNames.ContainsKey(typeName)) - { - throw new Exception($"Unknown Orchestration Type Name : {typeName}"); - } - - return KnownOrchestrationTypeNames[typeName]; - } - - static Dictionary KnownOrchestrationInstances = new Dictionary - { - { typeof(OrchestrationRunningIntoRetry).Name, new OrchestrationRunningIntoRetry() }, - }; - - static Dictionary KnownOrchestrationTypeNames = new Dictionary - { - { typeof(SimpleOrchestrationWithTasks).Name, typeof(SimpleOrchestrationWithTasks) }, - { typeof(SimpleOrchestrationWithTimer).Name, typeof(SimpleOrchestrationWithTimer) }, - { typeof(GenerationBasicOrchestration).Name, typeof(GenerationBasicOrchestration) }, - { typeof(SimpleOrchestrationWithSubOrchestration).Name, typeof(SimpleOrchestrationWithSubOrchestration) }, - { typeof(DriverOrchestration).Name, typeof(DriverOrchestration) }, - { typeof(TestOrchestration).Name, typeof(TestOrchestration) }, - { typeof(ExecutionCountingOrchestration).Name, typeof(ExecutionCountingOrchestration) }, - }; - - static Type[] KnownActivities = - { - typeof(GetUserTask), - typeof(GreetUserTask), - typeof(GenerationBasicTask), - typeof(RandomTimeWaitingTask), - typeof(ExceptionThrowingTask), - typeof(ExecutionCountingActivity), - }; - } -} diff --git a/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs b/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs index 36cdf3844..a26fd0966 100644 --- a/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs +++ b/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs @@ -19,12 +19,7 @@ namespace DurableTask.ServiceFabric sealed class AsyncManualResetEvent { - TaskCompletionSource tcs; - - public AsyncManualResetEvent() - { - this.tcs = new TaskCompletionSource(); - } + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { @@ -34,22 +29,22 @@ public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellati } var delayTask = Task.Delay(timeout, cancellationToken); - var resultTask = await Task.WhenAny(delayTask, this.tcs.Task); + var resultTask = await Task.WhenAny(delayTask, this.taskCompletionSource.Task); return delayTask != resultTask; } public void Set() { - this.tcs.TrySetResult(true); + this.taskCompletionSource.TrySetResult(true); } public void Reset() { while (true) { - var thisTcs = this.tcs; + var thisTcs = this.taskCompletionSource; - if (!thisTcs.Task.IsCompleted || Interlocked.CompareExchange(ref this.tcs, new TaskCompletionSource(), thisTcs) == thisTcs) + if (!thisTcs.Task.IsCompleted || Interlocked.CompareExchange(ref this.taskCompletionSource, new TaskCompletionSource(), thisTcs) == thisTcs) { return; } diff --git a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj index 6516e3020..17b02313b 100644 --- a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj +++ b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj @@ -9,8 +9,13 @@ + + + + + diff --git a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs index 7b6117548..798691785 100644 --- a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs +++ b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs @@ -29,11 +29,11 @@ public static void LogReliableCollectionException(string uniqueIdentifier, int a { if (isTransient) { - ProviderEventSource.Log.RetryableFabricException(uniqueIdentifier, attemptNumber, e.ToString()); + ProviderEventSource.Tracing.RetryableFabricException(uniqueIdentifier, attemptNumber, e.ToString()); } else { - ProviderEventSource.Log.ExceptionInReliableCollectionOperations(uniqueIdentifier, e.ToString()); + ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations(uniqueIdentifier, e.ToString()); } } } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs index 9fb02df59..204455b23 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs @@ -18,6 +18,7 @@ namespace DurableTask.ServiceFabric using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; + using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,7 +43,7 @@ class FabricOrchestrationInstanceStore : IFabricOrchestrationServiceInstanceStor readonly ConcurrentDictionary orchestrationWaiters = new ConcurrentDictionary(OrchestrationInstanceComparer.Default); IReliableDictionary instanceStore; - IReliableDictionary executionIdStore; + IReliableDictionary> executionIdStore; public FabricOrchestrationInstanceStore(IReliableStateManager stateManager, CancellationToken token) { @@ -50,6 +51,8 @@ public FabricOrchestrationInstanceStore(IReliableStateManager stateManager, Canc this.cancellationToken = token; } + public int MaxExecutionIdsLength { get; set; } = 100; + public Task InitializeStoreAsync(bool recreate) { if (recreate) @@ -87,7 +90,18 @@ public async Task WriteEntitesAsync(ITransaction transaction, IEnumerable instance.ExecutionId); + await this.executionIdStore.AddOrUpdateAsync(transaction, instance.InstanceId, new List { instance.ExecutionId }, + (k, old) => + { + old.Add(instance.ExecutionId); + if (old.Count > this.MaxExecutionIdsLength) + { + int newLength = (int)(this.MaxExecutionIdsLength * 0.9); + old = old.Take(newLength).ToList(); + } + + return old; + }); } await this.instanceStore.AddOrUpdateAsync(transaction, key, state.State, (k, oldValue) => state.State); @@ -120,7 +134,7 @@ public async Task> GetOrchestrationState await EnsureStoreInitialized(); - string latestExecutionId = await GetLatestExecutionId(instanceId); + string latestExecutionId = (await GetExecutionIds(instanceId))?.Last(); if (latestExecutionId != null) { @@ -134,9 +148,9 @@ public async Task> GetOrchestrationState return ImmutableList.Empty; } - public async Task GetLatestExecutionId(string instanceId) + public async Task> GetExecutionIds(string instanceId) { - string latestExecutionId = null; + List latestExecutionId = null; await RetryHelper.ExecuteWithRetryOnTransient(async () => { @@ -148,7 +162,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => latestExecutionId = executionIdValue.Value; } } - }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetLatestExecutionId)}"); + }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetExecutionIds)}"); return latestExecutionId; } @@ -279,7 +293,8 @@ Task CleanupOldDictionaries() if (storeName.StartsWith(InstanceStoreCollectionNamePrefix)) { DateTime storeTime; - if (DateTime.TryParseExact(storeName.Substring(InstanceStoreCollectionNamePrefix.Length), TimeFormatString, CultureInfo.InvariantCulture, DateTimeStyles.None, out storeTime) + var stringDate = storeName.Substring(InstanceStoreCollectionNamePrefix.Length); + if (DateTime.TryParseExact(stringDate, TimeFormatString, CultureInfo.InvariantCulture, DateTimeStyles.None, out storeTime) && (currentTime - storeTime > ttl)) { toDelete.Add(storeName); @@ -290,10 +305,10 @@ Task CleanupOldDictionaries() toKeep.Add(storeName); } }); - ProviderEventSource.Log.LogTimeTaken($"Enumerating all reliable states (count: {toDelete.Count + toKeep.Count})", enumerationTime.TotalMilliseconds); + ProviderEventSource.Tracing.LogTimeTaken($"Enumerating all reliable states (count: {toDelete.Count + toKeep.Count})", enumerationTime.TotalMilliseconds); - ProviderEventSource.Log.ReliableStateManagement($"Deleting {toDelete.Count} stores", String.Join(",", toDelete)); - ProviderEventSource.Log.ReliableStateManagement($"All remaining {toKeep.Count} stores", String.Join(",", toKeep)); + ProviderEventSource.Tracing.ReliableStateManagement($"Deleting {toDelete.Count} stores", String.Join(",", toDelete)); + ProviderEventSource.Tracing.ReliableStateManagement($"All remaining {toKeep.Count} stores", String.Join(",", toKeep)); foreach (var storeName in toDelete) { @@ -301,7 +316,7 @@ Task CleanupOldDictionaries() { await this.stateManager.RemoveAsync(storeName); }); - ProviderEventSource.Log.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); + ProviderEventSource.Tracing.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); } }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(1), delayOnException: TimeSpan.FromMinutes(10), actionName: $"{nameof(CleanupOldDictionaries)}", token: this.cancellationToken); } @@ -314,7 +329,7 @@ async Task EnsureStoreInitialized() } if (this.executionIdStore == null) { - this.executionIdStore = await this.stateManager.GetOrAddAsync>(Constants.ExecutionStoreDictionaryName); + this.executionIdStore = await this.stateManager.GetOrAddAsync>>(Constants.ExecutionStoreDictionaryName); } } @@ -352,8 +367,7 @@ public async Task WaitForOrchestrationAsync(Or public void OnOrchestrationCompleted(OrchestrationInstance instance) { - AsyncManualResetEvent resetEvent; - if (this.orchestrationWaiters.TryRemove(instance, out resetEvent)) + if (this.orchestrationWaiters.TryRemove(instance, out AsyncManualResetEvent resetEvent)) { resetEvent.Set(); } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs index 07b03d7a8..b4b8652b7 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs @@ -71,18 +71,6 @@ public IOrchestrationServiceClient OrchestrationServiceClient } } - /// - /// An instance of . - /// - public IFabricProviderClient FabricProviderClient - { - get - { - EnsureValidInstance(); - return this.fabricProviderClient; - } - } - /// /// Disposes the object. The object should be disposed after /// is invoked on the created with the . diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs index 78b92f23c..cc6ad17c8 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs @@ -34,7 +34,7 @@ class FabricOrchestrationService : IOrchestrationService readonly IReliableStateManager stateManager; readonly IFabricOrchestrationServiceInstanceStore instanceStore; readonly SessionsProvider orchestrationProvider; - readonly ActivityProvider activitiesProvider; + readonly ActivityProvider activitiesProvider; readonly ScheduledMessageProvider scheduledMessagesProvider; readonly FabricOrchestrationProviderSettings settings; readonly CancellationTokenSource cancellationTokenSource; @@ -52,7 +52,7 @@ public FabricOrchestrationService(IReliableStateManager stateManager, this.instanceStore = instanceStore; this.settings = settings; this.cancellationTokenSource = cancellationTokenSource; - this.activitiesProvider = new ActivityProvider(this.stateManager, Constants.ActivitiesQueueName, cancellationTokenSource.Token); + this.activitiesProvider = new ActivityProvider(this.stateManager, Constants.ActivitiesQueueName, cancellationTokenSource.Token); this.scheduledMessagesProvider = new ScheduledMessageProvider(this.stateManager, Constants.ScheduledMessagesDictionaryName, orchestrationProvider, cancellationTokenSource.Token); } @@ -160,7 +160,7 @@ public async Task LockNextTaskOrchestrationWorkItemAs { if (currentRuntimeState.ExecutionStartedEvent == null) { - ProviderEventSource.Log.UnexpectedCodeCondition($"Orchestration with no execution started event found: {currentSession.SessionId}"); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"Orchestration with no execution started event found: {currentSession.SessionId}"); return null; } @@ -182,7 +182,7 @@ public async Task LockNextTaskOrchestrationWorkItemAs if (!this.sessionInfos.TryAdd(workItem.InstanceId, sessionInfo)) { - ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(FabricOrchestrationService)}.{nameof(LockNextTaskOrchestrationWorkItemAsync)} : Multiple receivers processing the same session : {currentSession.SessionId.InstanceId}?"); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(FabricOrchestrationService)}.{nameof(LockNextTaskOrchestrationWorkItemAsync)} : Multiple receivers processing the same session : {currentSession.SessionId.InstanceId}?"); } return workItem; @@ -218,7 +218,7 @@ public async Task CompleteTaskOrchestrationWorkItemAsync( bool isComplete = workItem.OrchestrationRuntimeState.OrchestrationStatus.IsTerminalState(); IList sessionsToEnqueue = null; - List> scheduledMessages = null; + List> scheduledMessages = null; List> activityMessages = null; await RetryHelper.ExecuteWithRetryOnTransient(async () => @@ -243,7 +243,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => if (timerMessages?.Count > 0) { - scheduledMessages = timerMessages.Select(m => new Message(Guid.NewGuid().ToString(), new TaskMessageItem(m))).ToList(); + scheduledMessages = timerMessages.Select(m => new Message(Guid.NewGuid(), new TaskMessageItem(m))).ToList(); await this.scheduledMessagesProvider.SendBatchBeginAsync(txn, scheduledMessages); } @@ -296,7 +296,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (FabricReplicationOperationTooLargeException ex) { - ProviderEventSource.Log.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {sessionInfo.Instance}, Action = {nameof(CompleteTaskOrchestrationWorkItemAsync)}", ex.ToString()); + ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {sessionInfo.Instance}, Action = {nameof(CompleteTaskOrchestrationWorkItemAsync)}", ex.ToString()); retryOnException = true; newOrchestrationRuntimeState = null; outboundMessages = null; @@ -359,7 +359,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => this.instanceStore.OnOrchestrationCompleted(workItem.OrchestrationRuntimeState.OrchestrationInstance); - ProviderEventSource.Log.OrchestrationFinished(workItem.InstanceId, + ProviderEventSource.Tracing.OrchestrationFinished(workItem.InstanceId, workItem.OrchestrationRuntimeState.OrchestrationStatus.ToString(), (workItem.OrchestrationRuntimeState.CompletedTime - workItem.OrchestrationRuntimeState.CreatedTime).TotalSeconds, workItem.OrchestrationRuntimeState.Output, @@ -371,7 +371,7 @@ public Task AbandonTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem work SessionInformation sessionInfo = TryRemoveSessionInfo(workItem.InstanceId); if (sessionInfo == null) { - ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(AbandonTaskOrchestrationWorkItemAsync)} : Could not get a session info object while trying to abandon session {workItem.InstanceId}"); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(AbandonTaskOrchestrationWorkItemAsync)} : Could not get a session info object while trying to abandon session {workItem.InstanceId}"); } else { @@ -396,6 +396,8 @@ public Task ReleaseTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem work public int TaskActivityDispatcherCount => this.settings.TaskActivityDispatcherSettings.DispatcherCount; public int MaxConcurrentTaskActivityWorkItems => this.settings.TaskActivityDispatcherSettings.MaxConcurrentActivities; + public BehaviorOnContinueAsNew EventBehaviourForContinueAsNew { get; } + // Note: Do not rely on cancellationToken parameter to this method because the top layer does not yet implement any cancellation. public async Task LockNextTaskActivityWorkItem(TimeSpan receiveTimeout, CancellationToken cancellationToken) { @@ -436,7 +438,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (FabricReplicationOperationTooLargeException ex) { - ProviderEventSource.Log.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {responseMessage.OrchestrationInstance}, ActivityId = {workItem.Id}, Action = {nameof(CompleteTaskActivityWorkItemAsync)}", ex.ToString()); + ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {responseMessage.OrchestrationInstance}, ActivityId = {workItem.Id}, Action = {nameof(CompleteTaskActivityWorkItemAsync)}", ex.ToString()); retryOnException = true; var originalEvent = responseMessage.Event; int taskScheduledId = GetTaskScheduledId(originalEvent); @@ -498,12 +500,11 @@ int GetDelayForFetchOrProcessException(Exception exception) SessionInformation GetSessionInfo(string sessionId) { - ProviderEventSource.Log.TraceMessage(sessionId, $"{nameof(GetSessionInfo)} - Getting session info"); - SessionInformation sessionInfo; - if (!this.sessionInfos.TryGetValue(sessionId, out sessionInfo)) + ProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(GetSessionInfo)} - Getting session info"); + if (!this.sessionInfos.TryGetValue(sessionId, out SessionInformation sessionInfo)) { var message = $"{nameof(GetSessionInfo)}. Trying to get a session that's not in locked sessions {sessionId}"; - ProviderEventSource.Log.UnexpectedCodeCondition(message); + ProviderEventSource.Tracing.UnexpectedCodeCondition(message); throw new Exception(message); } @@ -512,9 +513,8 @@ SessionInformation GetSessionInfo(string sessionId) SessionInformation TryRemoveSessionInfo(string sessionId) { - SessionInformation sessionInfo; - var removed = this.sessionInfos.TryRemove(sessionId, out sessionInfo); - ProviderEventSource.Log.TraceMessage(sessionId, $"{nameof(TryRemoveSessionInfo)}: Removed = {removed}"); + var removed = this.sessionInfos.TryRemove(sessionId, out SessionInformation sessionInfo); + ProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(TryRemoveSessionInfo)}: Removed = {removed}"); return sessionInfo; } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index 338d17f5e..057924d34 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -15,6 +15,7 @@ namespace DurableTask.ServiceFabric { using System; using System.Collections.Generic; + using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -68,7 +69,7 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) if (added) { - ProviderEventSource.Log.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); + ProviderEventSource.Tracing.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); this.orchestrationProvider.TryEnqueueSession(creationMessage.OrchestrationInstance); } else @@ -103,7 +104,7 @@ public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] me public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) { - var latestExecutionId = await this.instanceStore.GetLatestExecutionId(instanceId); + var latestExecutionId = (await this.instanceStore.GetExecutionIds(instanceId)).Last(); if (latestExecutionId == null) { @@ -142,7 +143,10 @@ public async Task GetOrchestrationStateAsync(string instance public Task GetOrchestrationHistoryAsync(string instanceId, string executionId) { - throw new NotImplementedException(); + // Other implementations returns full history for the execution. + // This implementation returns just the final history, i.e., state. + var result = Newtonsoft.Json.JsonConvert.SerializeObject(this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId)); + return Task.FromResult(result); } public Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs index fe168fb64..242dd6f7d 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -50,7 +50,7 @@ public FabricOrchestrationServiceController(IOrchestrationServiceClient orchestr [HttpPut] public async Task CreateTaskOrchestration([FromBody] CreateTaskOrchestrationParameters parameters) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(CreateTaskOrchestration)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(CreateTaskOrchestration)); try { if (parameters.DedupeStatuses == null) @@ -78,7 +78,7 @@ public async Task CreateTaskOrchestration([FromBody] CreateTa [HttpPost] public async Task SendTaskOrchestrationMessage([FromBody] TaskMessage message) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(SendTaskOrchestrationMessage)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessage)); await this.orchestrationServiceClient.SendTaskOrchestrationMessageAsync(message); return new OkResult(this); } @@ -91,7 +91,7 @@ public async Task SendTaskOrchestrationMessage([FromBody] Tas [HttpPost] public async Task SendTaskOrchestrationMessageBatch([FromBody] TaskMessage[] messages) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(SendTaskOrchestrationMessageBatch)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessageBatch)); await this.orchestrationServiceClient.SendTaskOrchestrationMessageBatchAsync(messages); return new OkResult(this); } @@ -105,7 +105,7 @@ public async Task SendTaskOrchestrationMessageBatch([FromBody [HttpGet] public async Task GetOrchestrationState(string instanceId, string executionId) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationState)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, executionId); return state; } @@ -119,7 +119,7 @@ public async Task GetOrchestrationState(string instanceId, s [HttpGet] public async Task> GetOrchestrationState(string instanceId, bool allExecutions) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationState)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, allExecutions); return state; } @@ -133,7 +133,7 @@ public async Task> GetOrchestrationState(string instan [HttpDelete] public async Task ForceTerminateTaskOrchestration(string instanceId, string reason) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(ForceTerminateTaskOrchestration)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(ForceTerminateTaskOrchestration)); await this.orchestrationServiceClient.ForceTerminateTaskOrchestrationAsync(instanceId, reason); return new OkResult(this); } @@ -147,7 +147,7 @@ public async Task ForceTerminateTaskOrchestration(string inst [HttpGet] public async Task GetOrchestrationHistory(string instanceId, string executionId) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(GetOrchestrationHistory)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationHistory)); var result = await this.orchestrationServiceClient.GetOrchestrationHistoryAsync(instanceId, executionId); return result; } @@ -160,7 +160,7 @@ public async Task GetOrchestrationHistory(string instanceId, string exec [HttpPost] public async Task PurgeOrchestrationHistory(PurgeOrchestrationHistoryParameters purgeParameters) { - ProviderEventSource.Log.LogServingNetworkAction(nameof(PurgeOrchestrationHistory)); + ProviderEventSource.Tracing.LogServingNetworkAction(nameof(PurgeOrchestrationHistory)); await this.orchestrationServiceClient.PurgeOrchestrationHistoryAsync(purgeParameters.ThresholdDateTimeUtc, purgeParameters.TimeRangeFilterType); return new OkResult(this); } diff --git a/src/DurableTask.ServiceFabric/FabricProviderClient.cs b/src/DurableTask.ServiceFabric/FabricProviderClient.cs index 36611f607..e7eb78c7a 100644 --- a/src/DurableTask.ServiceFabric/FabricProviderClient.cs +++ b/src/DurableTask.ServiceFabric/FabricProviderClient.cs @@ -36,13 +36,13 @@ public FabricProviderClient(IReliableStateManager stateManager, SessionsProvider this.orchestrationProvider = orchestrationProvider ?? throw new ArgumentNullException(nameof(orchestrationProvider)); } - public async Task> GetRunningOrchestrations() + public async Task> GetRunningOrchestrationsAsync() { var sessions = await this.orchestrationProvider.GetSessions(); return sessions.Select(s => s.SessionId); } - public async Task GetOrchestrationRuntimeState(string instanceId) + public async Task GetOrchestrationRuntimeStateAsync(string instanceId) { var session = await this.orchestrationProvider.GetSession(instanceId); if (session == null) diff --git a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs index 75d01e1c7..82a09f08a 100644 --- a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs @@ -82,6 +82,6 @@ internal interface IFabricOrchestrationServiceInstanceStore Task WaitForOrchestrationAsync(OrchestrationInstance instance, TimeSpan timeout); - Task GetLatestExecutionId(string instanceId); + Task> GetExecutionIds(string instanceId); } } diff --git a/src/DurableTask.ServiceFabric/IFabricProviderClient.cs b/src/DurableTask.ServiceFabric/IFabricProviderClient.cs index bad4a2277..58803fe8d 100644 --- a/src/DurableTask.ServiceFabric/IFabricProviderClient.cs +++ b/src/DurableTask.ServiceFabric/IFabricProviderClient.cs @@ -22,13 +22,13 @@ namespace DurableTask.ServiceFabric /// /// Provides additional useful functionality which is not available through . /// - public interface IFabricProviderClient + internal interface IFabricProviderClient { /// /// Gets all the orchestration instances which are currently running or pending. /// /// - Task> GetRunningOrchestrations(); + Task> GetRunningOrchestrationsAsync(); /// /// Gets runtime state of a running or pending orchestration which includes the history events. @@ -40,6 +40,6 @@ public interface IFabricProviderClient /// The API is intended for diagnostics purpose, so current implementation returns /// a formatted json serialized string which can be very large. /// When the orchestration already completed or was never started. - Task GetOrchestrationRuntimeState(string instanceId); + Task GetOrchestrationRuntimeStateAsync(string instanceId); } } diff --git a/src/DurableTask.ServiceFabric/IPartitionProvider.cs b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs similarity index 83% rename from src/DurableTask.ServiceFabric/IPartitionProvider.cs rename to src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs index d38949962..6b3b39af4 100644 --- a/src/DurableTask.ServiceFabric/IPartitionProvider.cs +++ b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs @@ -14,22 +14,24 @@ namespace DurableTask.ServiceFabric { using System; + using System.Collections.Generic; + using System.Threading.Tasks; /// /// Provides partition related information. /// - public interface IPartitionProvider + public interface IPartitionEndpointResolver { /// /// Gets end points for all the partitions. /// /// All the end points. - Uri[] GetAllPartitions(); + Task> GetPartitionEndpointsAsync(); /// /// Gets partition end point for given instanceId. /// /// Partition end point - Uri GetPartitionForInstance(string instanceId); + Task GetParitionEndpointAsync(string instanceId); } } diff --git a/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs index e0ae95b02..e6638e489 100644 --- a/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs @@ -31,19 +31,22 @@ namespace DurableTask.ServiceFabric /// public class RemoteOrchestrationServiceClient : IOrchestrationServiceClient { - private readonly IPartitionProvider partitionProvider; - private readonly HttpClient httpClient; + private readonly IPartitionEndpointResolver partitionProvider; private readonly TimeSpan pollDelay = TimeSpan.FromSeconds(10); /// /// Creates a new instance. /// - public RemoteOrchestrationServiceClient(IPartitionProvider partitionProvider, HttpClient httpClient) + public RemoteOrchestrationServiceClient(IPartitionEndpointResolver partitionProvider) { this.partitionProvider = partitionProvider; - this.httpClient = httpClient; } + /// + /// HttpClient for making REST calls. + /// + public HttpClient HttpClient { get; set; } = new HttpClient(); + /// /// Creates a new orchestration /// @@ -63,7 +66,7 @@ private async Task PutJsonAsync(Uri uri, object @object) SerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } }; - var result = await this.httpClient.PutAsync(uri, @object, mediaFormatter); + var result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); if (!result.IsSuccessStatusCode) { throw new Exception("CreateTaskOrchestrationAsync failed"); @@ -92,7 +95,7 @@ public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string { var uri = ConstructEndpointUri(instanceId, "ForceTerminateTaskOrchestration"); var builder = new UriBuilder($"{uri}?instanceId={instanceId}&reason={HttpUtility.UrlEncode(reason)}"); - var response = await this.httpClient.DeleteAsync(builder.Uri); + var response = await this.HttpClient.DeleteAsync(builder.Uri); if (!response.IsSuccessStatusCode) { throw new Exception("Unable to terminate task instance"); @@ -109,7 +112,7 @@ public async Task GetOrchestrationHistoryAsync(string instanceId, string { var uri = ConstructEndpointUri(instanceId, "GetOrchestrationHistory"); var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); - var history = await this.httpClient.GetStringAsync(builder.Uri); + var history = await this.HttpClient.GetStringAsync(builder.Uri); return history; } @@ -123,7 +126,7 @@ public async Task> GetOrchestrationStateAsync(string i { var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); var builder = new UriBuilder($"{uri}?instanceId={instanceId}&allExecutions={allExecutions}"); - var stateString = await this.httpClient.GetStringAsync(builder.Uri); + var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var states = JsonConvert.DeserializeObject>(stateString); return states; } @@ -138,7 +141,7 @@ public async Task GetOrchestrationStateAsync(string instance { var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); - var stateString = await this.httpClient.GetStringAsync(builder.Uri); + var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var state = JsonConvert.DeserializeObject(stateString); return state; } @@ -152,10 +155,10 @@ public async Task GetOrchestrationStateAsync(string instance public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) { List allTasks = new List(); - foreach(var endpoint in this.partitionProvider.GetAllPartitions()) + foreach(var endpoint in await this.partitionProvider.GetPartitionEndpointsAsync()) { var uri = $"{endpoint.ToString()}/PurgeOrchestrationHistory"; - var task = this.httpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); + var task = this.HttpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); allTasks.Add(task); } @@ -214,12 +217,12 @@ public async Task WaitForOrchestrationAsync(string instanceI private Uri ConstructEndpointUri(OrchestrationInstance instance, string action) { - return new Uri($"{this.partitionProvider.GetPartitionForInstance(instance.InstanceId)}/{action}"); + return new Uri($"{this.partitionProvider.GetParitionEndpointAsync(instance.InstanceId)}/{action}"); } private Uri ConstructEndpointUri(string instanceId, string action) { - return new Uri($"{this.partitionProvider.GetPartitionForInstance(instanceId)}/{action}"); + return new Uri($"{this.partitionProvider.GetParitionEndpointAsync(instanceId)}/{action}"); } } } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs similarity index 67% rename from Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs rename to src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs index 8352bbbf5..f63b6a332 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/DefaultDependencyResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs @@ -11,46 +11,50 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace TestApplication.StatefulService +namespace DurableTask.ServiceFabric.Service { using System; using System.Collections.Generic; - using System.Linq; using System.Web.Http.Dependencies; using Microsoft.Extensions.DependencyInjection; - + /// public class DefaultDependencyResolver : IDependencyResolver { - private readonly IServiceProvider provider; + private IServiceProvider provider; + /// + /// Creates an instance of . + /// + /// An instance of public DefaultDependencyResolver(IServiceProvider provider) { - this.provider = provider; + this.provider = provider ?? throw new ArgumentNullException(nameof(provider)); } + /// public object GetService(Type serviceType) { - var obj = provider.GetService(serviceType); - Console.WriteLine($"{serviceType.Name} and {obj?.GetType().Name}"); - return obj; + return provider.GetService(serviceType); } + /// public IEnumerable GetServices(Type serviceType) { - var objs = provider.GetServices(serviceType); - Console.WriteLine($"{serviceType.Name} and {objs?.Count()}"); - return objs; + return provider.GetServices(serviceType); } + /// public IDependencyScope BeginScope() { return this; } + /// public void Dispose() { + this.provider = null; } } } diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs similarity index 62% rename from Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs rename to src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs index adf4a818c..87327d209 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/SimplePartitionProvider.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -11,27 +11,25 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.ServiceFabric.Service { using System; + using System.Collections.Generic; + using System.Threading.Tasks; - class SimplePartitionProvider : IPartitionProvider + /// + public class FabricPartitionEndpointResolver : IPartitionEndpointResolver { - Uri uri; - - public SimplePartitionProvider(Uri uri) - { - this.uri = uri; - } - - public Uri[] GetAllPartitions() + /// + public Task GetParitionEndpointAsync(string instanceId) { - return new Uri[] { this.uri }; + throw new NotImplementedException(); } - public Uri GetPartitionForInstance(string instanceId) + /// + public Task> GetPartitionEndpointsAsync() { - return this.uri; + throw new NotImplementedException(); } } } diff --git a/src/DurableTask.ServiceFabric/Service/FabricService.cs b/src/DurableTask.ServiceFabric/Service/FabricService.cs new file mode 100644 index 000000000..800c7be32 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/FabricService.cs @@ -0,0 +1,182 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.Core; + using DurableTask.ServiceFabric; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// An instance of this class is created for each service replica by the Service Fabric runtime. + /// + public sealed class FabricService : StatefulService + { + readonly FabricOrchestrationProviderFactory fabricProviderFactory; + readonly Dictionary knownOrchestrationTypeNames + = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + readonly Dictionary knownOrchestrationInstances + = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + /// + /// + /// + public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } + + IFabricServiceSettings serviceSettings; + + TaskHubWorker worker; + TaskHubClient client; + ReplicaRole currentRole; + + /// + /// + /// + /// + /// + public FabricService(StatefulServiceContext context, IFabricServiceSettings serviceSettings) : base(context) + { + this.serviceSettings = serviceSettings ?? throw new ArgumentNullException(nameof(serviceSettings)); + + var providerSettings = new FabricOrchestrationProviderSettings(); + providerSettings.TaskOrchestrationDispatcherSettings.DispatcherCount = this.serviceSettings.GetOrchestrationDispatcherCount(); + providerSettings.TaskActivityDispatcherSettings.DispatcherCount = this.serviceSettings.GetOrchestrationDispatcherCount(); + + this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, providerSettings); + this.FabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); + this.client = new TaskHubClient(this.FabricOrchestrationProvider.OrchestrationServiceClient); + } + + /// + /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. + /// + /// + /// For more information on service communication, see http://aka.ms/servicefabricservicecommunication + /// + /// A collection of listeners. + protected override IEnumerable CreateServiceReplicaListeners() + { + return this.serviceSettings.GetServiceReplicaListeners(); + } + + /// + /// This is the main entry point for your service replica. + /// This method executes when this replica of your service becomes primary and has write status. + /// + /// Canceled when Service Fabric needs to shut down this service replica. + protected override async Task RunAsync(CancellationToken cancellationToken) + { + if (this.FabricOrchestrationProvider == null) + { + this.FabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); + this.client = new TaskHubClient(this.FabricOrchestrationProvider.OrchestrationServiceClient); + } + + foreach (var type in this.serviceSettings.GetOrchestrationTypes()) + { + this.knownOrchestrationTypeNames.Add(type.FullName, type); + } + + foreach (var keyValuePair in this.serviceSettings.GetTaskOrchestrations()) + { + this.knownOrchestrationInstances.Add(keyValuePair.Key, keyValuePair.Value); + } + + this.worker = new TaskHubWorker(this.FabricOrchestrationProvider.OrchestrationService); + + await this.worker + .AddTaskOrchestrations(this.knownOrchestrationInstances.Values.Select(instance => new DefaultObjectCreator(instance)).ToArray()) + .AddTaskOrchestrations(this.knownOrchestrationTypeNames.Values.ToArray()) + .AddTaskActivities(this.serviceSettings.GetActivityTypes().ToArray()) + .StartAsync(); + + this.worker.TaskActivityDispatcher.IncludeDetails = true; + } + + /// + /// TODO: Add documentation. + /// + /// + /// + /// + protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + ServiceEventSource.Tracing.ServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) + { + await ShutdownAsync(); + } + this.currentRole = newRole; + ServiceEventSource.Tracing.ServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); + } + + /// + /// TODO: Add documentaiton. + /// + /// + /// + protected override async Task OnCloseAsync(CancellationToken cancellationToken) + { + ServiceEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); + await ShutdownAsync(); + } + + async Task ShutdownAsync() + { + try + { + if (this.worker != null) + { + ServiceEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); + await this.worker.StopAsync(isForced: true); + this.worker.Dispose(); + this.FabricOrchestrationProvider.Dispose(); + this.FabricOrchestrationProvider = null; + this.worker = null; + ServiceEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); + } + } + catch (Exception e) + { + ServiceEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); + throw; + } + } + + // TODO: Delete? + Type GetOrchestrationType(string typeName) + { + if (this.knownOrchestrationInstances.ContainsKey(typeName)) + { + return this.knownOrchestrationInstances[typeName].GetType(); + } + + if (this.knownOrchestrationTypeNames.ContainsKey(typeName)) + { + return this.knownOrchestrationTypeNames[typeName]; + + } + + throw new Exception($"Unknown Orchestration Type Name : {typeName}"); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs b/src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs new file mode 100644 index 000000000..a61faf1fc --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Collections.Generic; + + using DurableTask.Core; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + + + /// + /// Fabric Service settings. + /// + public interface IFabricServiceSettings + { + /// + /// + /// + /// + IEnumerable GetActivityTypes(); + + /// + /// + /// + /// + IEnumerable> GetTaskOrchestrations(); + + + /// + /// + /// + /// + IEnumerable GetOrchestrationTypes(); + + /// + /// + /// + /// + int GetActivityDispatcherCount(); + + /// + /// + /// + /// + int GetOrchestrationDispatcherCount(); + + /// + /// + /// + /// + IEnumerable GetServiceReplicaListeners(); + } +} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs b/src/DurableTask.ServiceFabric/Service/IOwinAppBuilder.cs similarity index 62% rename from Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs rename to src/DurableTask.ServiceFabric/Service/IOwinAppBuilder.cs index 4dd992911..e60f8932b 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/IOwinAppBuilder.cs +++ b/src/DurableTask.ServiceFabric/Service/IOwinAppBuilder.cs @@ -11,12 +11,26 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace TestApplication.StatefulService +namespace DurableTask.ServiceFabric.Service { using Owin; + /// + /// Owin application startup interface. + /// public interface IOwinAppBuilder { - void Configuration(IAppBuilder appBuilder); + /// + /// Gets the listening Address. + /// + /// + string GetListeningAddress(); + + + /// + /// Starts up an Owin App. + /// + /// an instance of + void Startup(IAppBuilder appBuilder); } } diff --git a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs new file mode 100644 index 000000000..f88358de4 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.Owin.Hosting; + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Owin; + + /// + /// Provides with support for Owin. + /// + public class OwinCommunicationListener : ICommunicationListener + { + /// + /// OWIN server handle. + /// + private IDisposable serverHandle; + + private IOwinAppBuilder owinAppBuilder; + + /// + /// + /// + /// Owin Application builder + public OwinCommunicationListener(IOwinAppBuilder owinAppBuilder) + { + this.owinAppBuilder = owinAppBuilder; + } + + /// + /// + /// + /// + /// + public Task OpenAsync(CancellationToken cancellationToken) + { + Trace.WriteLine("Initialize"); + var listeningAddress = this.owinAppBuilder.GetListeningAddress(); + Trace.WriteLine(String.Format("Opening on {0}", listeningAddress)); + + try + { + Trace.WriteLine(String.Format("Starting web server on {0}", listeningAddress)); + this.serverHandle = WebApp.Start(listeningAddress, appBuilder => this.owinAppBuilder.Startup(appBuilder)); + return Task.FromResult(listeningAddress); + } + catch (Exception ex) + { + Trace.WriteLine(ex); + this.StopWebServer(); + throw; + } + } + + /// + /// Close Owin service. + /// + /// + /// + public Task CloseAsync(CancellationToken cancellationToken) + { + Trace.WriteLine("Close"); + this.StopWebServer(); + return Task.CompletedTask; + } + + /// + /// Abort service. + /// + public void Abort() + { + Trace.WriteLine("Abort"); + this.StopWebServer(); + } + + private void StopWebServer() + { + if (this.serverHandle != null) + { + try + { + this.serverHandle.Dispose(); + } + catch (ObjectDisposedException) + { + // no-op + } + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs b/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs index 2e5fd82ee..f12ae9341 100644 --- a/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs @@ -17,30 +17,30 @@ namespace DurableTask.ServiceFabric.Stores using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; + using Microsoft.ServiceFabric.Data; - class ActivityProvider : MessageProviderBase where TKey : IComparable, IEquatable + class ActivityProvider : MessageProviderBase { - readonly ConcurrentQueue inMemoryQueue = new ConcurrentQueue(); + readonly ConcurrentQueue inMemoryQueue = new ConcurrentQueue(); public ActivityProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) : base(stateManager, storeName, token) { } - protected override void AddItemInMemory(TKey key, TValue value) + protected override void AddItemInMemory(string key, TaskMessageItem value) { this.inMemoryQueue.Enqueue(key); } - public async Task> ReceiveAsync(TimeSpan receiveTimeout) + public async Task> ReceiveAsync(TimeSpan receiveTimeout) { if (!IsStopped()) { - TKey key; bool newItemsBeforeTimeout = true; while (newItemsBeforeTimeout) { - if (this.inMemoryQueue.TryDequeue(out key)) + if (this.inMemoryQueue.TryDequeue(out string key)) { try { @@ -59,7 +59,7 @@ public async Task> ReceiveAsync(TimeSpan receiveTimeout) return null; } - public void Abandon(TKey key) + public void Abandon(string key) { this.inMemoryQueue.Enqueue(key); SetWaiterForNewItems(); diff --git a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs index 0394b5331..3bba5a94a 100644 --- a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs +++ b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs @@ -116,7 +116,7 @@ protected Task> GetValueAsync(TKey key) return new Message(key, result.Value); } var errorMessage = $"Internal Server Error: Did not find an item in reliable dictionary while having the item key {key} in memory"; - ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } }, uniqueActionIdentifier: $"Key = {key}, Action = MessageProviderBase.GetValueAsync, StoreName : {this.storeName}"); @@ -183,7 +183,7 @@ protected Task LogMetrics() count = await this.Store.GetCountAsync(tx); } - ProviderEventSource.Log.LogStoreCount(this.storeName, count); + ProviderEventSource.Tracing.LogStoreCount(this.storeName, count); }, initialDelay: metricsInterval, delayOnSuccess: metricsInterval, delayOnException: metricsInterval, actionName: $"Log Store Count of {this.storeName}", token: this.CancellationToken); } } diff --git a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs index de1986278..dd0f53768 100644 --- a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs @@ -26,12 +26,12 @@ namespace DurableTask.ServiceFabric.Stores using DurableTask.ServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; - class ScheduledMessageProvider : MessageProviderBase + class ScheduledMessageProvider : MessageProviderBase { readonly SessionsProvider sessionsProvider; readonly object @lock = new object(); - ImmutableSortedSet> inMemorySet = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); + ImmutableSortedSet> inMemorySet = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); DateTime nextActivationCheck; public ScheduledMessageProvider(IReliableStateManager stateManager, string storeName, SessionsProvider sessionsProvider, CancellationToken token) : base(stateManager, storeName, token) @@ -50,11 +50,11 @@ await this.EnumerateItems(kvp => var timerEvent = kvp.Value?.TaskMessage?.Event as TimerFiredEvent; if (timerEvent == null) { - ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Seeing a non timer event in scheduled messages while filling the pending items collection in role start"); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Seeing a non timer event in scheduled messages while filling the pending items collection in role start"); } else { - builder.Add(new Message(kvp.Key, kvp.Value)); + builder.Add(new Message(kvp.Key, kvp.Value)); } }); @@ -62,7 +62,7 @@ await this.EnumerateItems(kvp => { if (this.inMemorySet.Count > 0) { - ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Before we set the In memory set from the builder, there are items in it which should not happen."); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Before we set the In memory set from the builder, there are items in it which should not happen."); } this.inMemorySet = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); } @@ -71,11 +71,11 @@ await this.EnumerateItems(kvp => var nowait = ProcessScheduledMessages(); } - protected override void AddItemInMemory(string key, TaskMessageItem value) + protected override void AddItemInMemory(Guid key, TaskMessageItem value) { lock (@lock) { - this.inMemorySet = this.inMemorySet.Add(new Message(key, value)); + this.inMemorySet = this.inMemorySet.Add(new Message(key, value)); } var timerEvent = value.TaskMessage.Event as TimerFiredEvent; @@ -98,7 +98,7 @@ async Task ProcessScheduledMessages() var nextCheck = currentTime + TimeSpan.FromSeconds(1); var builder = this.inMemorySet.ToBuilder(); - List> activatedMessages = new List>(); + List> activatedMessages = new List>(); while (builder.Count > 0) { @@ -163,7 +163,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (Exception e) { - ProviderEventSource.Log.ExceptionWhileRunningBackgroundJob($"{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}", e.ToString()); + ProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob($"{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}", e.ToString()); await Task.Delay(TimeSpan.FromMilliseconds(100)); } } diff --git a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs index bafa504cc..322c2dace 100644 --- a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs @@ -20,14 +20,14 @@ namespace DurableTask.ServiceFabric.Stores using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; - class SessionMessagesProvider : MessageProviderBase where TKey : IComparable, IEquatable + class SessionMessagesProvider : MessageProviderBase { public SessionMessagesProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) : base(stateManager, storeName, token) { } - protected override void AddItemInMemory(TKey key, TValue value) + protected override void AddItemInMemory(Guid key, TaskMessageItem value) { throw new NotSupportedException(); } @@ -37,12 +37,12 @@ public override Task StartAsync() return InitializeStore(); } - public async Task>> ReceiveBatchAsync() + public async Task>> ReceiveBatchAsync() { - List> result = new List>(); + List> result = new List>(); if (!IsStopped()) { - await this.EnumerateItems(kvp => result.Add(new Message(kvp.Key, kvp.Value))); + await this.EnumerateItems(kvp => result.Add(new Message(kvp.Key, kvp.Value))); } return result; } diff --git a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs index 09d541957..4282268da 100644 --- a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs @@ -31,26 +31,26 @@ namespace DurableTask.ServiceFabric.Stores /// 'AcceptSession' : Intent is to fetch the next available session and hand it out to consumer. /// 'TryUnlockSession' : Intent is to indicate that the above consumer is done processing the session. /// 'TryEnqueueSession' : Intent is to make the session available for a consumer if needed. - /// + /// /// 'AcceptSession' should give a session to only one consumer at a time (strict requirement). /// We should also give out a session with messages in it but this is somewhat loose requirement because consumer /// can handle the scenario where there are no messages. But we don't want to keep making the session available - /// to pick up when there are no incoming messages. (So giving a session with no messages once or even twice is ok + /// to pick up when there are no incoming messages. (So giving a session with no messages once or even twice is ok /// but that should not happening recurringly) - /// + /// /// Incoming messages can come at anytime, before the session picked up, /// after 'AcceptSession' gave out a session but the consumer read messages in it, after consumer read messages in it, or /// after the session finished processing by consumer. - /// + /// /// 'AcceptSession; will fetch from 'fetchQueue' and returns it (so 'fetchQueueu' should never have a given session twice in /// the queue otherwise 'one consumer at a time' is violated. Also, we should not keep adding to 'fetchQueue' unless the session /// is interesting i.e., it needs processing). - /// + /// /// lockedSessions with 'LockState' is used to achieve the above. - /// + /// /// 'AcceptSession' fetches from 'fetchQueue' and transitions the session from 'InFetchQueue' to 'Locked'. /// (The implementation contract is such that if an item is in 'fetchQueue', it should be in 'InFetchQueue' state in lockedSessions) - /// + /// /// 'TryEnqueuSession' will try to add to lockedSessions with 'InFetchQueue' and only first such call after the session is unlocked will be /// able to add and that call will add the session back to 'fetchQueue'. /// If there is an entry in lockedSessions, 'TryEnqueuSession' does only one thing - @@ -58,11 +58,11 @@ namespace DurableTask.ServiceFabric.Stores /// it will just transition the state to 'NewMessagesWhileLocked' (so that session could be enqueued back again as it gets unlocked). /// Note that once the session is in this state, subsequent calls to 'TryEnqueueSession' before unlock will keep it in this state. /// Also Note that if session is in 'InFetchQueue' state, TryEnqueuSession does not need to (and should not) change the state. - /// + /// /// 'TryUnlockSession' first removes the session from lockedSessions [unlock complete at this point] /// but if the removed state is 'NewMessagesWhileLocked', it attempts to put it back on fetchQueue by calling 'TryEnqueueSession'. /// Note that at this point if another new incoming message calls 'TryEnqueueSession' only one of these should be able to add to 'fetchQueue'. - /// + /// /// Note that if new messages were sent after 'AcceptSession' but before consumer reads messages from session, we /// will hand out those messages but still add the session to fetchQueue (as part of unlock) and so the next fetch sees the session but /// with no messages. Though this can be made perfect by introducing one more state to 'LockedState', since consumer @@ -74,7 +74,8 @@ class SessionsProvider : MessageProviderBase ConcurrentQueue fetchQueue = new ConcurrentQueue(); ConcurrentDictionary lockedSessions = new ConcurrentDictionary(); - ConcurrentDictionary> sessionMessageProviders = new ConcurrentDictionary>(OrchestrationInstanceComparer.Default); + ConcurrentDictionary sessionMessageProviders + = new ConcurrentDictionary(OrchestrationInstanceComparer.Default); public SessionsProvider(IReliableStateManager stateManager, CancellationToken token) : base(stateManager, Constants.OrchestrationDictionaryName, token) { @@ -84,11 +85,10 @@ public async Task AcceptSessionAsync(TimeSpan receiveTimeout) { if (!IsStopped()) { - string returnInstanceId; bool newItemsBeforeTimeout = true; while (newItemsBeforeTimeout) { - if (this.fetchQueue.TryDequeue(out returnInstanceId)) + if (this.fetchQueue.TryDequeue(out string returnInstanceId)) { try { @@ -102,20 +102,20 @@ public async Task AcceptSessionAsync(TimeSpan receiveTimeout) { if (this.lockedSessions.TryUpdate(returnInstanceId, newValue: LockState.Locked, comparisonValue: LockState.InFetchQueue)) { - ProviderEventSource.Log.TraceMessage(returnInstanceId, "Session Locked Accepted"); + ProviderEventSource.Tracing.TraceMessage(returnInstanceId, "Session Locked Accepted"); return existingValue.Value; } else { var errorMessage = $"Internal Server Error : Unexpected to dequeue the session {returnInstanceId} which was already locked before"; - ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } } else { var errorMessage = $"Internal Server Error: Did not find the session object in reliable dictionary while having the session {returnInstanceId} in memory"; - ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } } @@ -143,21 +143,20 @@ public async Task>> ReceiveSessionMessagesAs { var sessionMessageProvider = await GetOrAddSessionMessagesInstance(session.SessionId); var messages = await sessionMessageProvider.ReceiveBatchAsync(); - ProviderEventSource.Log.TraceMessage(session.SessionId.InstanceId, $"Number of received messages {messages.Count}"); + ProviderEventSource.Tracing.TraceMessage(session.SessionId.InstanceId, $"Number of received messages {messages.Count}"); return messages; } public async Task CompleteMessages(ITransaction transaction, OrchestrationInstance instance, List lockTokens) { - SessionMessagesProvider sessionMessageProvider; - if (this.sessionMessageProviders.TryGetValue(instance, out sessionMessageProvider)) + if (this.sessionMessageProviders.TryGetValue(instance, out SessionMessagesProvider sessionMessageProvider)) { - ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); + ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); await sessionMessageProvider.CompleteBatchAsync(transaction, lockTokens); } else { - ProviderEventSource.Log.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); + ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); } } @@ -246,12 +245,11 @@ public async Task> GetSessions() public void TryUnlockSession(OrchestrationInstance instance, bool abandon = false, bool isComplete = false) { - ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); - LockState lockState; - if (!this.lockedSessions.TryRemove(instance.InstanceId, out lockState) || lockState == LockState.InFetchQueue) + ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); + if (!this.lockedSessions.TryRemove(instance.InstanceId, out LockState lockState) || lockState == LockState.InFetchQueue) { var errorMessage = $"{nameof(SessionsProvider)}.{nameof(TryUnlockSession)} : Trying to unlock the session {instance.InstanceId} which was not locked."; - ProviderEventSource.Log.UnexpectedCodeCondition(errorMessage); + ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } @@ -259,7 +257,8 @@ public void TryUnlockSession(OrchestrationInstance instance, bool abandon = fals { this.TryEnqueueSession(instance); } - ProviderEventSource.Log.TraceMessage(instance.InstanceId, $"Session Unlock End, Abandon = {abandon}, removed lock state = {lockState}"); + + ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock End, Abandon = {abandon}, removed lock state = {lockState}"); } public async Task TryAddSession(ITransaction transaction, TaskMessageItem newMessage) @@ -304,7 +303,7 @@ void TryEnqueueSession(string instanceId) { if (this.lockedSessions.TryAdd(instanceId, LockState.InFetchQueue)) { - ProviderEventSource.Log.TraceMessage(instanceId, "Session Getting Enqueued"); + ProviderEventSource.Tracing.TraceMessage(instanceId, "Session Getting Enqueued"); this.fetchQueue.Enqueue(instanceId); SetWaiterForNewItems(); } @@ -323,16 +322,15 @@ public async Task DropSession(ITransaction txn, OrchestrationInstance instance) await this.Store.TryRemoveAsync(txn, instance.InstanceId); - SessionMessagesProvider sessionMessagesProvider; - this.sessionMessageProviders.TryRemove(instance, out sessionMessagesProvider); + this.sessionMessageProviders.TryRemove(instance, out SessionMessagesProvider _); var noWait = RetryHelper.ExecuteWithRetryOnTransient(() => this.StateManager.RemoveAsync(GetSessionMessagesDictionaryName(instance)), uniqueActionIdentifier: $"Orchestration = '{instance}', Action = 'DropSessionMessagesDictionaryBackgroundTask'"); } - async Task> GetOrAddSessionMessagesInstance(OrchestrationInstance instance) + async Task GetOrAddSessionMessagesInstance(OrchestrationInstance instance) { - var newInstance = new SessionMessagesProvider(this.StateManager, GetSessionMessagesDictionaryName(instance), this.CancellationToken); + var newInstance = new SessionMessagesProvider(this.StateManager, GetSessionMessagesDictionaryName(instance), this.CancellationToken); var sessionMessageProvider = this.sessionMessageProviders.GetOrAdd(instance, newInstance); await sessionMessageProvider.EnsureStoreInitialized(); return sessionMessageProvider; diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs index b8f15bb52..94205b9a3 100644 --- a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs +++ b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs @@ -39,7 +39,7 @@ static async Task ExecuteWithRetryOnTransient(Func action, RetryPolicy ret { await action(); timer.Stop(); - ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); + ProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); return; } catch (Exception e) @@ -49,7 +49,7 @@ static async Task ExecuteWithRetryOnTransient(Func action, RetryPolicy ret bool shouldRetry = ExceptionUtilities.IsRetryableFabricException(e); ExceptionUtilities.LogReliableCollectionException(uniqueActionIdentifier, attemptNumber, e, shouldRetry); - ProviderEventSource.Log.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); + ProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); if (shouldRetry) { @@ -83,7 +83,7 @@ static async Task ExecuteWithRetryOnTransient(Func ExecuteWithRetryOnTransient(Func> + sealed class TimerFiredEventComparer : IComparer> { public static readonly TimerFiredEventComparer Instance = new TimerFiredEventComparer(); - public int Compare(Message first, Message second) + public int Compare(Message first, Message second) { if (first == null) { @@ -51,7 +51,7 @@ public int Compare(Message first, Message /// Singleton instance of the event source. /// - public static readonly ProviderEventSource Log = new ProviderEventSource(); + public static readonly ProviderEventSource Tracing = new ProviderEventSource(); static ProviderEventSource() { diff --git a/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs new file mode 100644 index 000000000..b593204e9 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs @@ -0,0 +1,121 @@ +namespace DurableTask.ServiceFabric.Tracing +{ + using System; + using System.Diagnostics.Tracing; + using System.Threading.Tasks; + using Microsoft.ServiceFabric.Services.Runtime; + + [EventSource(Name = "DurableTask-FabricService", Guid = "E8FFF9F2-BD27-41A7-B354-991AC4E4EBD9")] + internal sealed class ServiceEventSource : EventSource + { + /// + /// Singleton instance of the event source. + /// + public static readonly ServiceEventSource Tracing = new ServiceEventSource(); + + static ServiceEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + + #region Keywords + // Event keywords can be used to categorize events. + // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). + // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. + public static class Keywords + { + public const EventKeywords Requests = (EventKeywords)0x1L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; + } + #endregion + + [NonEvent] + public void ServiceMessage(StatefulService service, string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + ServiceMessage( + service.Context.ServiceName.ToString(), + service.Context.ServiceTypeName, + service.Context.ReplicaId, + service.Context.PartitionId, + service.Context.CodePackageActivationContext.ApplicationName, + service.Context.CodePackageActivationContext.ApplicationTypeName, + service.Context.NodeContext.NodeName, + finalMessage); + } + } + + + private const int ServiceRequestStopEventId = 6; + [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] + public void ServiceRequestStop(string requestTypeName) + { + WriteEvent(ServiceRequestStopEventId, requestTypeName); + } + + private const int ServiceRequestFailedEventId = 7; + [Event(ServiceRequestFailedEventId, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] + public void ServiceRequestFailed(string requestTypeName, string exception) + { + WriteEvent(ServiceRequestFailedEventId, exception); + } + + + // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. + // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. + // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. + private const int ServiceMessageEventId = 2; + [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] + private +#if UNSAFE + unsafe +#endif + void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { +#if !UNSAFE + WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); +#else + const int numArgs = 8; + fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) + { + EventData* eventData = stackalloc EventData[numArgs]; + eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; + eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; + eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; + eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; + eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; + eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; + eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; + eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; + + WriteEventCore(ServiceMessageEventId, numArgs, eventData); + } +#endif + } + + // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. + // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, + // and other statistics. + private const int ServiceRequestStartEventId = 5; + [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] + public void ServiceRequestStart(string requestTypeName) + { + WriteEvent(ServiceRequestStartEventId, requestTypeName); + } + + } + +} diff --git a/src/DurableTask.ServiceFabric/Utils.cs b/src/DurableTask.ServiceFabric/Utils.cs index b0d234d62..1493c14cd 100644 --- a/src/DurableTask.ServiceFabric/Utils.cs +++ b/src/DurableTask.ServiceFabric/Utils.cs @@ -79,7 +79,7 @@ public static async Task RunBackgroundJob(Func loopAction, TimeSpan initia } catch(Exception e) { - ProviderEventSource.Log.ExceptionWhileRunningBackgroundJob(actionName, e.ToString()); + ProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob(actionName, e.ToString()); await Task.Delay(delayOnException).ConfigureAwait(false); } } From 3ddbcb44ddb0f52f89be2b3584363f51ea9243d1 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Mon, 25 Feb 2019 10:46:46 -0800 Subject: [PATCH 03/12] Provider IHasher interface to abstract hashing strategy. Create Register method. --- .../FunctionalTests.cs | 6 -- .../Utilities.cs | 3 +- .../WaitForOrchestrationTests.cs | 3 - .../SerializationPerformanceExperiments.cs | 1 - .../Program.cs | 10 +-- ...ettings.cs => TestFabricServiceContext.cs} | 17 +++- .../FabricOrchestrationInstanceStore.cs | 14 +-- .../FabricOrchestrationService.cs | 4 +- .../FabricOrchestrationServiceClient.cs | 2 +- .../FabricOrchestrationServiceController.cs | 1 - ...FabricOrchestrationServiceInstanceStore.cs | 10 ++- .../CreateTaskOrchestrationParameters.cs | 0 .../PurgeOrchestrationHistoryParameters.cs | 0 .../RemoteOrchestrationServiceClient.cs | 2 +- .../Service/DefaultStringHasher.cs | 45 ++++++++++ .../FabricPartitionEndpointResolver.cs | 90 ++++++++++++++++++- .../Service/FabricService.cs | 72 ++++----------- ...ceSettings.cs => IFabricServiceContext.cs} | 20 +---- .../Service/IHasher.cs | 29 ++++++ .../Service/OwinCommunicationListener.cs | 1 - .../Stores/SessionMessagesProvider.cs | 1 - 21 files changed, 217 insertions(+), 114 deletions(-) rename Test/TestFabricApplication/TestApplication.StatefulService/{TestFabricServiceSettings.cs => TestFabricServiceContext.cs} (82%) rename src/DurableTask.ServiceFabric/{Models => Remote}/CreateTaskOrchestrationParameters.cs (100%) rename src/DurableTask.ServiceFabric/{Models => Remote}/PurgeOrchestrationHistoryParameters.cs (100%) rename src/DurableTask.ServiceFabric/{ => Remote}/RemoteOrchestrationServiceClient.cs (99%) create mode 100644 src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs rename src/DurableTask.ServiceFabric/Service/{IFabricServiceSettings.cs => IFabricServiceContext.cs} (78%) create mode 100644 src/DurableTask.ServiceFabric/Service/IHasher.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs index dd194dda0..b83093aed 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs @@ -15,16 +15,10 @@ namespace DurableTask.ServiceFabric.Integration.Tests { using System; using System.Collections.Generic; - using System.Fabric; - using System.Linq; - using System.Net.Http; using System.Threading.Tasks; using DurableTask.Core; using DurableTask.Test.Orchestrations.Performance; - using Microsoft.ServiceFabric.Services.Client; - using Microsoft.ServiceFabric.Services.Remoting.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; - using TestApplication.Common; using TestApplication.Common.Orchestrations; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs index 08a736396..85b71d869 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -18,6 +18,7 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Net.Http; using System.Threading.Tasks; using DurableTask.Core; + using DurableTask.ServiceFabric.Remote; using DurableTask.ServiceFabric.Service; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,7 +26,7 @@ public static class Utilities { public static TaskHubClient CreateTaskHubClient() { - var partitionProvider = new FabricPartitionEndpointResolver(); + var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestApplication/StatefulService"), new DefaultStringHasher()); var httpClient = new HttpClient(); return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider)); } diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs index 3a14f1022..9e555a56e 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs @@ -17,10 +17,7 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Threading.Tasks; using DurableTask.Core; using DurableTask.Test.Orchestrations.Performance; - using Microsoft.ServiceFabric.Services.Client; - using Microsoft.ServiceFabric.Services.Remoting.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; - using TestApplication.Common; [TestClass] public class WaitForOrchestrationTests diff --git a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs index 91e34b848..ade1f39dd 100644 --- a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs +++ b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs @@ -15,7 +15,6 @@ namespace DurableTask.ServiceFabric.Tests { using System; using System.Collections.Generic; - using System.IO; using System.Runtime.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index b1703fde1..e881bf77b 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -14,7 +14,6 @@ namespace TestApplication.StatefulService { using System; - using System.Diagnostics; using System.Threading; using DurableTask.ServiceFabric.Service; using Microsoft.ServiceFabric.Services.Runtime; @@ -32,18 +31,11 @@ private static void Main() // Registering a service maps a service type name to a .NET type. // When Service Fabric creates an instance of this service type, // an instance of the class is created in this host process. - - // ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => new TestStatefulService(context)).GetAwaiter().GetResult(); - ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => { - var settings = new TestFabricServiceSettings(); - var fabricService = new FabricService(context, settings); - return fabricService; + return new FabricService(context, new TestFabricServiceContext()); }).GetAwaiter().GetResult(); - // ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(TestStatefulService).Name); - // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs similarity index 82% rename from Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs rename to Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs index dbdc6573d..2234e7ab8 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceSettings.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs @@ -17,6 +17,8 @@ namespace TestApplication.StatefulService using System.Collections.Generic; using System.Fabric; using System.Globalization; + using System.Linq; + using System.Threading.Tasks; using DurableTask.Core; using DurableTask.ServiceFabric; @@ -27,7 +29,7 @@ namespace TestApplication.StatefulService /// - public class TestFabricServiceSettings : IFabricServiceSettings + public class TestFabricServiceContext : IFabricServiceContext { public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } @@ -78,7 +80,8 @@ public IEnumerable GetServiceReplicaListeners() { yield return new ServiceReplicaListener(context => { - var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); + var activationContext = FabricRuntime.GetActivationContext(); + var serviceEndpoint = activationContext.GetEndpoint("ServiceEndpoint"); int port = serviceEndpoint.Port; string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://+:{0}/", port) @@ -92,5 +95,15 @@ public IEnumerable> GetTaskOrchestration { yield return new KeyValuePair(typeof(OrchestrationRunningIntoRetry).Name, new OrchestrationRunningIntoRetry()); } + + public Task Register(TaskHubWorker taskHubWorker) + { + taskHubWorker + .AddTaskOrchestrations(this.GetOrchestrationTypes().ToArray()) + .AddTaskOrchestrations(this.GetTaskOrchestrations().Select(instance => new DefaultObjectCreator(instance.Value)).ToArray()) + .AddTaskActivities(GetActivityTypes().ToArray()); + + return Task.CompletedTask; + } } } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs index 204455b23..3b23e10e2 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs @@ -75,7 +75,7 @@ public async Task DeleteStoreAsync() await this.stateManager.RemoveAsync(Constants.ExecutionStoreDictionaryName); } - public async Task WriteEntitesAsync(ITransaction transaction, IEnumerable entities) + public async Task WriteEntitiesAsync(ITransaction transaction, IEnumerable entities) { await EnsureStoreInitialized(); foreach (var entity in entities) @@ -150,21 +150,21 @@ public async Task> GetOrchestrationState public async Task> GetExecutionIds(string instanceId) { - List latestExecutionId = null; + List executionIds = null; await RetryHelper.ExecuteWithRetryOnTransient(async () => { using (var tx = this.stateManager.CreateTransaction()) { - var executionIdValue = await this.executionIdStore.TryGetValueAsync(tx, instanceId); - if (executionIdValue.HasValue) + var executionIdsValue = await this.executionIdStore.TryGetValueAsync(tx, instanceId); + if (executionIdsValue.HasValue) { - latestExecutionId = executionIdValue.Value; + executionIds = executionIdsValue.Value; } } }, uniqueActionIdentifier: $"Orchestration Instance Id = {instanceId}, Action = {nameof(FabricOrchestrationInstanceStore)}.{nameof(GetExecutionIds)}"); - return latestExecutionId; + return executionIds; } public async Task GetOrchestrationStateAsync(string instanceId, string executionId) @@ -233,7 +233,7 @@ public Task> GetOrchestrationHi throw new NotImplementedException(); } - // Todo: This is incomplete and inaccurate implemenation done for testing purposes. + // Todo: This is incomplete and inaccurate implementation done for testing purposes. // The method will cleanup state for every orchestration happening in the hour time window of given time, // for example, if the given time is 9.35, it will delete state for all orchestrations that // are completed between 9.00 to 9.59!!! diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs index cc6ad17c8..e36687a31 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs @@ -283,7 +283,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => // session as part of the next atomic transaction. if (this.instanceStore != null && orchestrationState != null && !isComplete) { - await this.instanceStore.WriteEntitesAsync(txn, new InstanceEntityBase[] + await this.instanceStore.WriteEntitiesAsync(txn, new InstanceEntityBase[] { new OrchestrationStateInstanceEntity() { @@ -340,7 +340,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => { using (var txn = this.stateManager.CreateTransaction()) { - await this.instanceStore.WriteEntitesAsync(txn, new InstanceEntityBase[] + await this.instanceStore.WriteEntitiesAsync(txn, new InstanceEntityBase[] { new OrchestrationStateInstanceEntity() { diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index 057924d34..4f287ca13 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -182,7 +182,7 @@ Task WriteExecutionStartedEventToInstanceStore(ITransaction tx, ExecutionStarted LastUpdatedTime = createdTime }; - return this.instanceStore.WriteEntitesAsync(tx, new InstanceEntityBase[] + return this.instanceStore.WriteEntitiesAsync(tx, new InstanceEntityBase[] { new OrchestrationStateInstanceEntity() { diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs index 242dd6f7d..c0209b836 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -13,7 +13,6 @@ namespace DurableTask.ServiceFabric { - using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Web.Http; diff --git a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs index 82a09f08a..63bf21062 100644 --- a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs @@ -16,12 +16,14 @@ namespace DurableTask.ServiceFabric using System; using System.Collections.Generic; using System.Threading.Tasks; + using DurableTask.Core; using DurableTask.Core.Tracking; using Microsoft.ServiceFabric.Data; - // Todo: Figure out a way to not include internal XML documentation in the generated documentation file - // or change all the below comments to non-xml style. + /// + /// A store for supporting orchestration service in service fabric world. + /// internal interface IFabricOrchestrationServiceInstanceStore { /// @@ -46,13 +48,13 @@ internal interface IFabricOrchestrationServiceInstanceStore /// /// Service fabric transaction in which this operation is executed. /// List of history events to write - Task WriteEntitesAsync(ITransaction transaction, IEnumerable entities); + Task WriteEntitiesAsync(ITransaction transaction, IEnumerable entities); /// /// Gets a list of orchestration states for a given instance /// /// The instance id to return state for - /// Flag indiciation whether to get all history execution ids or just the most recent + /// Flag indication whether to get all history execution ids or just the most recent /// List of matching orchestration states Task> GetOrchestrationStateAsync(string instanceId, bool allInstances); diff --git a/src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs b/src/DurableTask.ServiceFabric/Remote/CreateTaskOrchestrationParameters.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Models/CreateTaskOrchestrationParameters.cs rename to src/DurableTask.ServiceFabric/Remote/CreateTaskOrchestrationParameters.cs diff --git a/src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs b/src/DurableTask.ServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Models/PurgeOrchestrationHistoryParameters.cs rename to src/DurableTask.ServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs diff --git a/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs similarity index 99% rename from src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs rename to src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index e6638e489..cf8b0c5b7 100644 --- a/src/DurableTask.ServiceFabric/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.ServiceFabric.Remote { using System; using System.Collections.Generic; diff --git a/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs b/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs new file mode 100644 index 000000000..78f359cc9 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Security.Cryptography; + using System.Text; + + /// + /// Implements for string elements. + /// + public class DefaultStringHasher : IHasher + { + /// + public long GetLongHashCode(string input) + { + if (string.IsNullOrEmpty(input)) + { + return 0; + } + using (var sha256 = SHA256Managed.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha256.ComputeHash(bytes); + var long1 = BitConverter.ToInt64(bytes, 0); + var long2 = BitConverter.ToInt64(bytes, 8); + var long3 = BitConverter.ToInt64(bytes, 16); + var long4 = BitConverter.ToInt64(bytes, 24); + + return long1 ^ long2 ^ long3 ^ long4; + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs index 87327d209..ba4049ed1 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -14,22 +14,104 @@ namespace DurableTask.ServiceFabric.Service { using System; + using System.Collections.Concurrent; using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Threading; using System.Threading.Tasks; + using Microsoft.ServiceFabric.Services.Client; + /// public class FabricPartitionEndpointResolver : IPartitionEndpointResolver { + readonly Uri service; + readonly FabricClient fabricClient; + Int64RangePartitionInformation[] partitionInfos; + ConcurrentDictionary endPointsLookup; + ServicePartitionResolver partitionResolver; + IHasher instanceIdHasher; + + /// + /// + /// + /// + /// + public FabricPartitionEndpointResolver(Uri service, IHasher instanceIdHasher) + { + this.service = service ?? throw new ArgumentNullException(nameof(service)); + this.instanceIdHasher = instanceIdHasher ?? throw new ArgumentNullException(nameof(instanceIdHasher)); + this.fabricClient = new FabricClient(); + this.endPointsLookup = new ConcurrentDictionary(); + this.partitionResolver = ServicePartitionResolver.GetDefault(); + } + /// - public Task GetParitionEndpointAsync(string instanceId) + public async Task GetParitionEndpointAsync(string instanceId) { - throw new NotImplementedException(); + await BuildPartitionInfos(false); + var hash = this.instanceIdHasher.GetLongHashCode(instanceId); + var partition = this.BinarySearch(partitionInfos, hash); + return new Uri(endPointsLookup[partition.LowKey].Address); } /// - public Task> GetPartitionEndpointsAsync() + public async Task> GetPartitionEndpointsAsync() { - throw new NotImplementedException(); + await BuildPartitionInfos(false); + return this.endPointsLookup.Values.Select(x => new Uri(x.Address)); + } + + private async Task BuildPartitionInfos(bool force) + { + if (force || this.partitionInfos == null) + { + CancellationToken cancellationToken = new CancellationToken(); + var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.service); + List partitions = new List(); + foreach (var partition in partitionList) + { + var info = (Int64RangePartitionInformation)partition.PartitionInformation; + if (info == null) + { + throw new NotSupportedException($"{partition.PartitionInformation.Kind} is not supported"); + } + + partitions.Add(info); + var resolvedServicePartition = await this.partitionResolver.ResolveAsync(this.service, new ServicePartitionKey(info.LowKey), cancellationToken); + var endpoint = resolvedServicePartition.GetEndpoint(); + this.endPointsLookup.AddOrUpdate(info.LowKey, endpoint, (_, __) => endpoint); + } + + this.partitionInfos = partitions.OrderBy(x => x.LowKey).ToArray(); + } + } + + private Int64RangePartitionInformation BinarySearch(Int64RangePartitionInformation[] array, long hash) + { + int start = 0, end = array.Length - 1; + + do + { + int middle = (start + end) / 2; + + if (hash >= array[middle].LowKey && hash <= array[middle].HighKey) + { + return array[middle]; + } + + else if (hash < array[middle].LowKey) + { + end = middle - 1; + } + else + { + start = middle + 1; + } + } while (start > end); + + throw new Exception($"{hash} is out of partition range"); } } } diff --git a/src/DurableTask.ServiceFabric/Service/FabricService.cs b/src/DurableTask.ServiceFabric/Service/FabricService.cs index 800c7be32..8051d799d 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricService.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricService.cs @@ -16,7 +16,6 @@ namespace DurableTask.ServiceFabric.Service using System; using System.Collections.Generic; using System.Fabric; - using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -32,34 +31,30 @@ namespace DurableTask.ServiceFabric.Service public sealed class FabricService : StatefulService { readonly FabricOrchestrationProviderFactory fabricProviderFactory; - readonly Dictionary knownOrchestrationTypeNames - = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - readonly Dictionary knownOrchestrationInstances - = new Dictionary(StringComparer.InvariantCultureIgnoreCase); /// - /// + /// /// public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } - IFabricServiceSettings serviceSettings; + IFabricServiceContext serviceContext; TaskHubWorker worker; TaskHubClient client; ReplicaRole currentRole; /// - /// + /// /// /// - /// - public FabricService(StatefulServiceContext context, IFabricServiceSettings serviceSettings) : base(context) + /// + public FabricService(StatefulServiceContext context, IFabricServiceContext serviceContext) : base(context) { - this.serviceSettings = serviceSettings ?? throw new ArgumentNullException(nameof(serviceSettings)); + this.serviceContext = serviceContext ?? throw new ArgumentNullException(nameof(serviceContext)); var providerSettings = new FabricOrchestrationProviderSettings(); - providerSettings.TaskOrchestrationDispatcherSettings.DispatcherCount = this.serviceSettings.GetOrchestrationDispatcherCount(); - providerSettings.TaskActivityDispatcherSettings.DispatcherCount = this.serviceSettings.GetOrchestrationDispatcherCount(); + providerSettings.TaskOrchestrationDispatcherSettings.DispatcherCount = this.serviceContext.GetOrchestrationDispatcherCount(); + providerSettings.TaskActivityDispatcherSettings.DispatcherCount = this.serviceContext.GetOrchestrationDispatcherCount(); this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, providerSettings); this.FabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); @@ -75,7 +70,7 @@ public FabricService(StatefulServiceContext context, IFabricServiceSettings serv /// A collection of listeners. protected override IEnumerable CreateServiceReplicaListeners() { - return this.serviceSettings.GetServiceReplicaListeners(); + return this.serviceContext.GetServiceReplicaListeners(); } /// @@ -91,33 +86,21 @@ protected override async Task RunAsync(CancellationToken cancellationToken) this.client = new TaskHubClient(this.FabricOrchestrationProvider.OrchestrationServiceClient); } - foreach (var type in this.serviceSettings.GetOrchestrationTypes()) - { - this.knownOrchestrationTypeNames.Add(type.FullName, type); - } - - foreach (var keyValuePair in this.serviceSettings.GetTaskOrchestrations()) - { - this.knownOrchestrationInstances.Add(keyValuePair.Key, keyValuePair.Value); - } - this.worker = new TaskHubWorker(this.FabricOrchestrationProvider.OrchestrationService); - await this.worker - .AddTaskOrchestrations(this.knownOrchestrationInstances.Values.Select(instance => new DefaultObjectCreator(instance)).ToArray()) - .AddTaskOrchestrations(this.knownOrchestrationTypeNames.Values.ToArray()) - .AddTaskActivities(this.serviceSettings.GetActivityTypes().ToArray()) - .StartAsync(); + await this.serviceContext.Register(this.worker); this.worker.TaskActivityDispatcher.IncludeDetails = true; } /// - /// TODO: Add documentation. + /// Handles node's role change. /// - /// - /// - /// + /// New for this service replica. + /// Cancellation token to monitor for cancellation requests. + /// + /// A that represents outstanding operation. + /// protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { ServiceEventSource.Tracing.ServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); @@ -130,10 +113,10 @@ protected override async Task OnChangeRoleAsync(ReplicaRole newRole, Cancellatio } /// - /// TODO: Add documentaiton. + /// Handles OnCloseAsync event, shuts down the service. /// - /// - /// + /// Cancellation token to monitor for cancellation requests. + /// A Task that represents outstanding operation. protected override async Task OnCloseAsync(CancellationToken cancellationToken) { ServiceEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); @@ -161,22 +144,5 @@ async Task ShutdownAsync() throw; } } - - // TODO: Delete? - Type GetOrchestrationType(string typeName) - { - if (this.knownOrchestrationInstances.ContainsKey(typeName)) - { - return this.knownOrchestrationInstances[typeName].GetType(); - } - - if (this.knownOrchestrationTypeNames.ContainsKey(typeName)) - { - return this.knownOrchestrationTypeNames[typeName]; - - } - - throw new Exception($"Unknown Orchestration Type Name : {typeName}"); - } } } diff --git a/src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs b/src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs similarity index 78% rename from src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs rename to src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs index a61faf1fc..8218d732f 100644 --- a/src/DurableTask.ServiceFabric/Service/IFabricServiceSettings.cs +++ b/src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs @@ -13,9 +13,8 @@ namespace DurableTask.ServiceFabric.Service { - using System; using System.Collections.Generic; - + using System.Threading.Tasks; using DurableTask.Core; using Microsoft.ServiceFabric.Services.Communication.Runtime; @@ -23,26 +22,13 @@ namespace DurableTask.ServiceFabric.Service /// /// Fabric Service settings. /// - public interface IFabricServiceSettings + public interface IFabricServiceContext { /// /// /// /// - IEnumerable GetActivityTypes(); - - /// - /// - /// - /// - IEnumerable> GetTaskOrchestrations(); - - - /// - /// - /// - /// - IEnumerable GetOrchestrationTypes(); + Task Register(TaskHubWorker taskHubWorker); /// /// diff --git a/src/DurableTask.ServiceFabric/Service/IHasher.cs b/src/DurableTask.ServiceFabric/Service/IHasher.cs new file mode 100644 index 000000000..b833026e6 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/IHasher.cs @@ -0,0 +1,29 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + /// + /// provides long hash code. + /// + /// Element type + public interface IHasher + { + /// + /// Gets Long hash code. + /// + /// Input element + /// 64 bit hash code + long GetLongHashCode(T @input); + } +} diff --git a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs index f88358de4..693c16e95 100644 --- a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs +++ b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs @@ -20,7 +20,6 @@ namespace DurableTask.ServiceFabric.Service using Microsoft.Owin.Hosting; using Microsoft.ServiceFabric.Services.Communication.Runtime; - using Owin; /// /// Provides with support for Owin. diff --git a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs index 322c2dace..2ee889579 100644 --- a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs @@ -18,7 +18,6 @@ namespace DurableTask.ServiceFabric.Stores using System.Threading; using System.Threading.Tasks; using Microsoft.ServiceFabric.Data; - using Microsoft.ServiceFabric.Data.Collections; class SessionMessagesProvider : MessageProviderBase { From 1bdcd26aa68369671e55ae28cb1d0b6fcf601e75 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Fri, 1 Mar 2019 11:59:39 -0800 Subject: [PATCH 04/12] Addressing code review comments. Listen for `ClientDisconnected` event. Merge both the event sources into single one. --- .../FunctionalTests.cs | 38 +--- .../Utilities.cs | 2 +- .../PackageRoot/ServiceManifest.xml | 2 +- .../Program.cs | 5 +- .../ServiceEventSource.cs | 189 ------------------ ...ntext.cs => TestOrchestrationsProvider.cs} | 63 ++---- .../ApplicationManifest.xml | 8 + .../DurableTask.ServiceFabric.csproj | 35 ++-- .../Exceptions/InvalidInstanceIdException.cs | 32 +++ src/DurableTask.ServiceFabric/Extensions.cs | 53 +++++ .../FabricOrchestrationInstanceStore.cs | 12 +- .../FabricOrchestrationServiceClient.cs | 11 + .../FabricOrchestrationServiceController.cs | 55 +++-- .../IPartitionEndpointResolver.cs | 4 +- .../RemoteOrchestrationServiceClient.cs | 110 ++++++---- .../Service/ApplicationExceptionsLogger.cs | 34 ++++ .../Service/DefaultDependencyResolver.cs | 1 - .../Service/DefaultStringHasher.cs | 29 +-- .../FabricPartitionEndpointResolver.cs | 97 ++++++--- .../Service/IHasher.cs | 6 +- ...eContext.cs => IOrchestrationsProvider.cs} | 30 +-- .../Service}/Startup.cs | 14 +- ...abricService.cs => TaskHubProxyService.cs} | 79 ++++---- .../Tracing/ProviderEventSource.cs | 81 +++++++- .../Tracing/ServiceEventSource.cs | 121 ----------- 25 files changed, 540 insertions(+), 571 deletions(-) delete mode 100644 Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs rename Test/TestFabricApplication/TestApplication.StatefulService/{TestFabricServiceContext.cs => TestOrchestrationsProvider.cs} (64%) create mode 100644 src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs create mode 100644 src/DurableTask.ServiceFabric/Extensions.cs create mode 100644 src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs rename src/DurableTask.ServiceFabric/Service/{IFabricServiceContext.cs => IOrchestrationsProvider.cs} (62%) rename {Test/TestFabricApplication/TestApplication.StatefulService => src/DurableTask.ServiceFabric/Service}/Startup.cs (87%) rename src/DurableTask.ServiceFabric/Service/{FabricService.cs => TaskHubProxyService.cs} (59%) delete mode 100644 src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs index b83093aed..7bf9e270f 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs @@ -55,20 +55,6 @@ public async Task Orchestration_With_Timer_Finishes_After_The_Wait_Time() Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Timer Wait time : {waitTime}"); } - [TestMethod] - [Ignore] - public async Task GenerationBasicTest() - { - GenerationBasicOrchestration.Result = 0; - GenerationBasicTask.GenerationCount = 0; - - var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(GenerationBasicOrchestration), 4); - var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); - - Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); - Assert.AreEqual(4, GenerationBasicOrchestration.Result, "Orchestration Result is wrong!!!"); - } - [TestMethod] public async Task Orchestration_With_SubOrchestration_Finishes() { @@ -231,25 +217,6 @@ public async Task FabricProviderClient_ReturnsRunningOrchestrations() } } - [TestMethod] - public async Task Orchestration_With_Long_InstanceId_Name_Test() - { - var instanceId = $"MySomewhatLongNamespaceName|MyRelativelyLongRootEntityName|{Guid.NewGuid().ToString("N")}|MyRatherLongRuleName"; - var input = new TestOrchestrationData() - { - NumberOfParallelTasks = 16, - NumberOfSerialTasks = 5, - MaxDelay = 0, - MinDelay = 0, - DelayUnit = TimeSpan.FromMilliseconds(1) - }; - - var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(TestOrchestration), instanceId, input); - - var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); - Assert.IsNotNull(result); - Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); - } [TestMethod] public async Task QueryState_For_Latest_Execution() @@ -300,16 +267,13 @@ public async Task QueryState_For_Latest_Execution() public async Task Retry_OnException_Test() { int retryAttempts = 3; - var minumumExpectedTime = TimeSpan.FromSeconds(7); //This should be calculated based on RetryOptions in the Orchestration code. - var instance = await this.taskHubClient.CreateOrchestrationInstanceAsync(typeof(OrchestrationRunningIntoRetry), retryAttempts); var result = await this.taskHubClient.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(2)); Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus); Assert.AreEqual(retryAttempts.ToString(), result.Output); var orchestrationTime = result.CompletedTime - result.CreatedTime; - Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Minumum expected time : {minumumExpectedTime}"); - Assert.IsTrue(orchestrationTime > minumumExpectedTime); + Console.WriteLine($"Time for Orchestration : {orchestrationTime}"); } [TestMethod] diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs index 85b71d869..50ec3e74c 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -26,7 +26,7 @@ public static class Utilities { public static TaskHubClient CreateTaskHubClient() { - var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestApplication/StatefulService"), new DefaultStringHasher()); + var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new DefaultStringHasher()); var httpClient = new HttpClient(); return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider)); } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml index b188283d4..7d8ee60a9 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml +++ b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml @@ -22,7 +22,7 @@ - + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index e881bf77b..153764392 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -14,6 +14,7 @@ namespace TestApplication.StatefulService { using System; + using System.Diagnostics; using System.Threading; using DurableTask.ServiceFabric.Service; using Microsoft.ServiceFabric.Services.Runtime; @@ -33,7 +34,7 @@ private static void Main() // an instance of the class is created in this host process. ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => { - return new FabricService(context, new TestFabricServiceContext()); + return new TaskHubProxyService(context, new TestOrchestrationsProvider()); }).GetAwaiter().GetResult(); // Prevents this host process from terminating so services keep running. @@ -41,7 +42,7 @@ private static void Main() } catch (Exception e) { - ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + Trace.WriteLine(e); throw; } } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs b/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs deleted file mode 100644 index 067da2a06..000000000 --- a/Test/TestFabricApplication/TestApplication.StatefulService/ServiceEventSource.cs +++ /dev/null @@ -1,189 +0,0 @@ -// ---------------------------------------------------------------------------------- -// 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 TestApplication.StatefulService -{ - using System; - using System.Diagnostics.Tracing; - using System.Threading.Tasks; - using Microsoft.ServiceFabric.Services.Runtime; - - [EventSource(Name = "MyCompany-TestFabricApplication-TestStatefulService")] - internal sealed class ServiceEventSource : EventSource - { - public static readonly ServiceEventSource Current = new ServiceEventSource(); - - static ServiceEventSource() - { - // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. - // This problem will be fixed in .NET Framework 4.6.2. - Task.Run(() => { }); - } - - // Instance constructor is private to enforce singleton semantics - private ServiceEventSource() : base() { } - - #region Keywords - // Event keywords can be used to categorize events. - // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). - // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. - public static class Keywords - { - public const EventKeywords Requests = (EventKeywords)0x1L; - public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; - } - #endregion - - #region Events - // Define an instance method for each event you want to record and apply an [Event] attribute to it. - // The method name is the name of the event. - // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). - // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. - // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). - // Put [NonEvent] attribute on all methods that do not define an event. - // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx - - [NonEvent] - public void Message(string message, params object[] args) - { - if (this.IsEnabled()) - { - string finalMessage = string.Format(message, args); - Message(finalMessage); - } - } - - private const int MessageEventId = 1; - [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] - public void Message(string message) - { - if (this.IsEnabled()) - { - WriteEvent(MessageEventId, message); - } - } - - [NonEvent] - public void ServiceMessage(StatefulService service, string message, params object[] args) - { - if (this.IsEnabled()) - { - string finalMessage = string.Format(message, args); - ServiceMessage( - service.Context.ServiceName.ToString(), - service.Context.ServiceTypeName, - service.Context.ReplicaId, - service.Context.PartitionId, - service.Context.CodePackageActivationContext.ApplicationName, - service.Context.CodePackageActivationContext.ApplicationTypeName, - service.Context.NodeContext.NodeName, - finalMessage); - } - } - - // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. - // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. - // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. - private const int ServiceMessageEventId = 2; - [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] - private -#if UNSAFE - unsafe -#endif - void ServiceMessage( - string serviceName, - string serviceTypeName, - long replicaOrInstanceId, - Guid partitionId, - string applicationName, - string applicationTypeName, - string nodeName, - string message) - { -#if !UNSAFE - WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); -#else - const int numArgs = 8; - fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) - { - EventData* eventData = stackalloc EventData[numArgs]; - eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; - eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; - eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; - eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; - eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; - eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; - eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; - eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; - - WriteEventCore(ServiceMessageEventId, numArgs, eventData); - } -#endif - } - - private const int ServiceTypeRegisteredEventId = 3; - [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)] - public void ServiceTypeRegistered(int hostProcessId, string serviceType) - { - WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); - } - - private const int ServiceHostInitializationFailedEventId = 4; - [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)] - public void ServiceHostInitializationFailed(string exception) - { - WriteEvent(ServiceHostInitializationFailedEventId, exception); - } - - // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. - // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, - // and other statistics. - private const int ServiceRequestStartEventId = 5; - [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] - public void ServiceRequestStart(string requestTypeName) - { - WriteEvent(ServiceRequestStartEventId, requestTypeName); - } - - private const int ServiceRequestStopEventId = 6; - [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] - public void ServiceRequestStop(string requestTypeName) - { - WriteEvent(ServiceRequestStopEventId, requestTypeName); - } - - private const int ServiceRequestFailedEventId = 7; - [Event(ServiceRequestFailedEventId, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] - public void ServiceRequestFailed(string requestTypeName, string exception) - { - WriteEvent(ServiceRequestFailedEventId, exception); - } - #endregion - - #region Private methods -#if UNSAFE - private int SizeInBytes(string s) - { - if (s == null) - { - return 0; - } - else - { - return (s.Length + 1) * sizeof(char); - } - } -#endif - #endregion - } -} diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs similarity index 64% rename from Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs rename to Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs index 2234e7ab8..2b59fe3fe 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestFabricServiceContext.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs @@ -15,8 +15,6 @@ namespace TestApplication.StatefulService { using System; using System.Collections.Generic; - using System.Fabric; - using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -24,24 +22,38 @@ namespace TestApplication.StatefulService using DurableTask.ServiceFabric; using DurableTask.ServiceFabric.Service; using DurableTask.Test.Orchestrations.Performance; - using Microsoft.ServiceFabric.Services.Communication.Runtime; + using TestApplication.Common.Orchestrations; /// - public class TestFabricServiceContext : IFabricServiceContext + public class TestOrchestrationsProvider : IOrchestrationsProvider { + #region Implementation of IOrchestrationsProvider interface - public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } + /// + public FabricOrchestrationProviderSettings GetFabricOrchestrationProviderSettings() + { + var settings = new FabricOrchestrationProviderSettings(); + settings.TaskOrchestrationDispatcherSettings.DispatcherCount = 5; + settings.TaskActivityDispatcherSettings.DispatcherCount = 5; + return settings; + } /// - public int GetActivityDispatcherCount() + public Task RegisterOrchestrationArtifactsAsync(TaskHubWorker taskHubWorker) { - return 5; + taskHubWorker + .AddTaskOrchestrations(this.GetOrchestrationTypes().ToArray()) + .AddTaskOrchestrations(this.GetTaskOrchestrations().Select(instance => new DefaultObjectCreator(instance.Value)).ToArray()) + .AddTaskActivities(GetActivityTypes().ToArray()); + + return Task.CompletedTask; } + #endregion Implementation of IOrchestrationsProvider interface /// - public IEnumerable GetActivityTypes() + private IEnumerable GetActivityTypes() { return new Type[] { @@ -55,13 +67,7 @@ public IEnumerable GetActivityTypes() } /// - public int GetOrchestrationDispatcherCount() - { - return 5; - } - - /// - public IEnumerable GetOrchestrationTypes() + private IEnumerable GetOrchestrationTypes() { return new Type[] { @@ -76,34 +82,9 @@ public IEnumerable GetOrchestrationTypes() } /// - public IEnumerable GetServiceReplicaListeners() - { - yield return new ServiceReplicaListener(context => - { - var activationContext = FabricRuntime.GetActivationContext(); - var serviceEndpoint = activationContext.GetEndpoint("ServiceEndpoint"); - int port = serviceEndpoint.Port; - - string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://+:{0}/", port) - .Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); - return new OwinCommunicationListener(new Startup(listeningAddress, this.FabricOrchestrationProvider)); - }); - } - - /// - public IEnumerable> GetTaskOrchestrations() + private IEnumerable> GetTaskOrchestrations() { yield return new KeyValuePair(typeof(OrchestrationRunningIntoRetry).Name, new OrchestrationRunningIntoRetry()); } - - public Task Register(TaskHubWorker taskHubWorker) - { - taskHubWorker - .AddTaskOrchestrations(this.GetOrchestrationTypes().ToArray()) - .AddTaskOrchestrations(this.GetTaskOrchestrations().Select(instance => new DefaultObjectCreator(instance.Value)).ToArray()) - .AddTaskActivities(GetActivityTypes().ToArray()); - - return Task.CompletedTask; - } } } diff --git a/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml index 8b9eb8af2..ffa737502 100644 --- a/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml +++ b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml @@ -8,6 +8,9 @@ + + + @@ -16,4 +19,9 @@ + + + + + \ No newline at end of file diff --git a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj index 17b02313b..7b173b721 100644 --- a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj +++ b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj @@ -2,30 +2,39 @@ - net461 + net461;net472 true Microsoft.Azure.DurableTask.ServiceFabric - + - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + diff --git a/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs b/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs new file mode 100644 index 000000000..3a6ef5815 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Exceptions +{ + using System; + + /// + /// Exception representing that instanceId is not Valid. + /// + public class InvalidInstanceIdException : Exception + { + /// + /// Creates an instance of + /// + /// Orchestration instance id + public InvalidInstanceIdException(string instanceId) + : base("Not a valid instanceId: " + instanceId) + { + } + } +} diff --git a/src/DurableTask.ServiceFabric/Extensions.cs b/src/DurableTask.ServiceFabric/Extensions.cs new file mode 100644 index 000000000..3395bc29c --- /dev/null +++ b/src/DurableTask.ServiceFabric/Extensions.cs @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric +{ + using System; + using System.Web; + using DurableTask.ServiceFabric.Exceptions; + + + /// + /// Defines extensions methods. + /// + public static class Extensions + { + /// + /// Validates given instanceId. + /// + /// Id of an orchestration instance + /// boolean indicating whether instanceId is valid or not + public static bool IsValidInstanceId(this string instanceId) + { + if (instanceId == null) + { + return false; + } + + // InsanceId consists of valid url characters is treated as valid. + var encodedInstanceId = HttpUtility.UrlEncode(instanceId); + + return instanceId.Equals(encodedInstanceId, StringComparison.OrdinalIgnoreCase); + } + + internal static void EnsureValidInstanceId(this string instanceId) + { + if (!instanceId.IsValidInstanceId()) + { + throw new InvalidInstanceIdException(instanceId); + } + } + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs index 3b23e10e2..74b1a0d4f 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs @@ -96,15 +96,16 @@ public async Task WriteEntitiesAsync(ITransaction transaction, IEnumerable this.MaxExecutionIdsLength) { - int newLength = (int)(this.MaxExecutionIdsLength * 0.9); - old = old.Take(newLength).ToList(); + // Remove first 10% items. + int skipItemsLength = (int)(this.MaxExecutionIdsLength * 0.1); + old = old.Skip(skipItemsLength).ToList(); } return old; }); } - await this.instanceStore.AddOrUpdateAsync(transaction, key, state.State, - (k, oldValue) => state.State); + + await this.instanceStore.AddOrUpdateAsync(transaction, key, state.State, (k, oldValue) => state.State); } else { @@ -113,8 +114,7 @@ await this.instanceStore.AddOrUpdateAsync(transaction, key, state.State, // see-what-you-commit-within-the-transaction rules. If we add it within the transaction and immediately // try to AddOrUpdateAsync an entry in dictionary that doesn't work. var backupDictionary = await this.stateManager.GetOrAddAsync>(backupDictionaryName); - await backupDictionary.AddOrUpdateAsync(transaction, key, state.State, - (k, oldValue) => state.State); + await backupDictionary.AddOrUpdateAsync(transaction, key, state.State, (k, oldValue) => state.State); await this.instanceStore.TryRemoveAsync(transaction, key); } } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index 4f287ca13..9a19c1944 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -43,6 +43,7 @@ public FabricOrchestrationServiceClient(IReliableStateManager stateManager, Sess #region IOrchestrationServiceClient public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) { + creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); ExecutionStartedEvent startEvent = creationMessage.Event as ExecutionStartedEvent; if (startEvent == null) { @@ -86,11 +87,14 @@ public Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, Orchestrat throw new NotSupportedException($"DedupeStatuses are not supported yet with service fabric provider"); } + creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); + return CreateTaskOrchestrationAsync(creationMessage); } public Task SendTaskOrchestrationMessageAsync(TaskMessage message) { + message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); return this.orchestrationProvider.AppendMessageAsync(new TaskMessageItem(message)); } @@ -98,12 +102,14 @@ public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] me { foreach(var message in messages) { + message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); await this.SendTaskOrchestrationMessageAsync(message); } } public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) { + instanceId.EnsureValidInstanceId(); var latestExecutionId = (await this.instanceStore.GetExecutionIds(instanceId)).Last(); if (latestExecutionId == null) @@ -122,6 +128,7 @@ public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string public async Task> GetOrchestrationStateAsync(string instanceId, bool allExecutions) { + instanceId.EnsureValidInstanceId(); var stateInstances = await this.instanceStore.GetOrchestrationStateAsync(instanceId, allExecutions); var result = new List(); @@ -137,12 +144,15 @@ public async Task> GetOrchestrationStateAsync(string i public async Task GetOrchestrationStateAsync(string instanceId, string executionId) { + instanceId.EnsureValidInstanceId(); var stateInstance = await this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId); return stateInstance?.State; } public Task GetOrchestrationHistoryAsync(string instanceId, string executionId) { + instanceId.EnsureValidInstanceId(); + // Other implementations returns full history for the execution. // This implementation returns just the final history, i.e., state. var result = Newtonsoft.Json.JsonConvert.SerializeObject(this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId)); @@ -161,6 +171,7 @@ public Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, Orches public async Task WaitForOrchestrationAsync(string instanceId, string executionId, TimeSpan timeout, CancellationToken cancellationToken) { + instanceId.EnsureValidInstanceId(); var instance = new OrchestrationInstance() { InstanceId = instanceId, ExecutionId = executionId }; var state = await this.instanceStore.WaitForOrchestrationAsync(instance, timeout); return state?.State; diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs index c0209b836..a29857150 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -42,14 +42,21 @@ public FabricOrchestrationServiceController(IOrchestrationServiceClient orchestr /// /// Creates a task orchestration. /// + /// Orchestration Id /// Parameters for creating task orchestration. /// Will throw an OrchestrationAlreadyExistsException exception /// If any orchestration with the same instance Id exists in the instance store and it has a status specified in dedupeStatuses. /// object. [HttpPut] - public async Task CreateTaskOrchestration([FromBody] CreateTaskOrchestrationParameters parameters) + [Route("orchestrations/{orchestrationId}")] + public async Task CreateTaskOrchestration([FromUri] string orchestrationId, [FromBody] CreateTaskOrchestrationParameters parameters) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(CreateTaskOrchestration)); + parameters.TaskMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); + if (!orchestrationId.Equals(parameters.TaskMessage.OrchestrationInstance.InstanceId)) + { + return Conflict(); + } try { if (parameters.DedupeStatuses == null) @@ -72,12 +79,20 @@ public async Task CreateTaskOrchestration([FromBody] CreateTa /// /// Sends an orchestration message to TaskHubClient. /// + /// /// Message to send /// object. [HttpPost] - public async Task SendTaskOrchestrationMessage([FromBody] TaskMessage message) + [Route("messages/{messageId}")] + public async Task SendTaskOrchestrationMessage([FromUri]long messageId,[FromBody] TaskMessage message) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessage)); + if (messageId != message.SequenceNumber) + { + return Conflict(); + } + + message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); await this.orchestrationServiceClient.SendTaskOrchestrationMessageAsync(message); return new OkResult(this); } @@ -88,6 +103,7 @@ public async Task SendTaskOrchestrationMessage([FromBody] Tas /// Message to send /// object. [HttpPost] + [Route("messages")] public async Task SendTaskOrchestrationMessageBatch([FromBody] TaskMessage[] messages) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessageBatch)); @@ -98,56 +114,64 @@ public async Task SendTaskOrchestrationMessageBatch([FromBody /// /// Gets the state of orchestration. /// - /// Instance id of the orchestration + /// Instance id of the orchestration /// Execution id of the orchestration /// object. [HttpGet] - public async Task GetOrchestrationState(string instanceId, string executionId) + [Route("orchestrations/{orchestrationId}")] + public async Task GetOrchestrationState([FromUri]string orchestrationId, string executionId) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); - var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, executionId); + orchestrationId.EnsureValidInstanceId(); + var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(orchestrationId, executionId); return state; } /// /// Gets the state of orchestration. /// - /// Instance id of the orchestration + /// Instance id of the orchestration /// True if method should fetch all executions of the instance, false if the method should only fetch the most recent execution /// List of . [HttpGet] - public async Task> GetOrchestrationState(string instanceId, bool allExecutions) + [Route("orchestrations/{orchestrationId}")] + public async Task> GetOrchestrationState([FromUri]string orchestrationId, bool allExecutions) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); - var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(instanceId, allExecutions); + orchestrationId.EnsureValidInstanceId(); + var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(orchestrationId, allExecutions); return state; } /// /// Terminates an orchestration. /// - /// Instance id of the orchestration + /// Instance id of the orchestration /// Execution id of the orchestration /// object. [HttpDelete] - public async Task ForceTerminateTaskOrchestration(string instanceId, string reason) + [Route("orchestrations/{orchestrationId}")] + public async Task ForceTerminateTaskOrchestration([FromUri]string orchestrationId, string reason) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(ForceTerminateTaskOrchestration)); - await this.orchestrationServiceClient.ForceTerminateTaskOrchestrationAsync(instanceId, reason); + orchestrationId.EnsureValidInstanceId(); + await this.orchestrationServiceClient.ForceTerminateTaskOrchestrationAsync(orchestrationId, reason); return new OkResult(this); } /// /// Gets the history of orchestration. /// - /// Instance id of the orchestration + /// Instance id of the orchestration /// Execution id of the orchestration /// Orchestration history [HttpGet] - public async Task GetOrchestrationHistory(string instanceId, string executionId) + [Route("history/{orchestrationId}")] + public async Task GetOrchestrationHistory([FromUri]string orchestrationId, string executionId) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationHistory)); - var result = await this.orchestrationServiceClient.GetOrchestrationHistoryAsync(instanceId, executionId); + orchestrationId.EnsureValidInstanceId(); + var result = await this.orchestrationServiceClient.GetOrchestrationHistoryAsync(orchestrationId, executionId); return result; } @@ -157,7 +181,8 @@ public async Task GetOrchestrationHistory(string instanceId, string exec /// Purge history parameters /// object. [HttpPost] - public async Task PurgeOrchestrationHistory(PurgeOrchestrationHistoryParameters purgeParameters) + [Route("history")] + public async Task PurgeOrchestrationHistory([FromBody]PurgeOrchestrationHistoryParameters purgeParameters) { ProviderEventSource.Tracing.LogServingNetworkAction(nameof(PurgeOrchestrationHistory)); await this.orchestrationServiceClient.PurgeOrchestrationHistoryAsync(purgeParameters.ThresholdDateTimeUtc, purgeParameters.TimeRangeFilterType); diff --git a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs index 6b3b39af4..3023e9328 100644 --- a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs @@ -26,12 +26,12 @@ public interface IPartitionEndpointResolver /// Gets end points for all the partitions. /// /// All the end points. - Task> GetPartitionEndpointsAsync(); + Task> GetPartitionEndpointsAsync(); /// /// Gets partition end point for given instanceId. /// /// Partition end point - Task GetParitionEndpointAsync(string instanceId); + Task GetParitionEndpointAsync(string instanceId); } } diff --git a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index cf8b0c5b7..8cb13140a 100644 --- a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -15,6 +15,7 @@ namespace DurableTask.ServiceFabric.Remote { using System; using System.Collections.Generic; + using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; using System.Threading; @@ -25,6 +26,7 @@ namespace DurableTask.ServiceFabric.Remote using DurableTask.Core.Exceptions; using DurableTask.ServiceFabric.Models; using Newtonsoft.Json; + using Newtonsoft.Json.Linq; /// /// Allows to interact with a remote IOrchestrationServiceClient @@ -55,24 +57,11 @@ public RemoteOrchestrationServiceClient(IPartitionEndpointResolver partitionProv /// public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) { - var uri = ConstructEndpointUri(creationMessage.OrchestrationInstance, "CreateTaskOrchestration"); + creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment()); await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage }); } - private async Task PutJsonAsync(Uri uri, object @object) - { - var mediaFormatter = new JsonMediaTypeFormatter() - { - SerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } - }; - - var result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); - if (!result.IsSuccessStatusCode) - { - throw new Exception("CreateTaskOrchestrationAsync failed"); - } - } - /// /// Creates a new orchestration and specifies a subset of states which should be de duplicated on in the client side /// @@ -82,7 +71,8 @@ private async Task PutJsonAsync(Uri uri, object @object) /// public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) { - var uri = ConstructEndpointUri(creationMessage.OrchestrationInstance, "CreateTaskOrchestration"); + creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment(creationMessage.OrchestrationInstance.InstanceId)); await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage, DedupeStatuses = dedupeStatuses }); } @@ -93,8 +83,9 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, Orch /// Reason for termination public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) { - var uri = ConstructEndpointUri(instanceId, "ForceTerminateTaskOrchestration"); - var builder = new UriBuilder($"{uri}?instanceId={instanceId}&reason={HttpUtility.UrlEncode(reason)}"); + instanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var builder = new UriBuilder($"{uri}?reason={HttpUtility.UrlEncode(reason)}"); var response = await this.HttpClient.DeleteAsync(builder.Uri); if (!response.IsSuccessStatusCode) { @@ -110,8 +101,9 @@ public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string /// String with formatted JSON representing the execution history public async Task GetOrchestrationHistoryAsync(string instanceId, string executionId) { - var uri = ConstructEndpointUri(instanceId, "GetOrchestrationHistory"); - var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); + instanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var builder = new UriBuilder($"{uri}?executionId={executionId}"); var history = await this.HttpClient.GetStringAsync(builder.Uri); return history; } @@ -124,8 +116,9 @@ public async Task GetOrchestrationHistoryAsync(string instanceId, string /// List of OrchestrationState objects that represents the list of orchestrations in the instance store public async Task> GetOrchestrationStateAsync(string instanceId, bool allExecutions) { - var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); - var builder = new UriBuilder($"{uri}?instanceId={instanceId}&allExecutions={allExecutions}"); + instanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var builder = new UriBuilder($"{uri}?allExecutions={allExecutions}"); var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var states = JsonConvert.DeserializeObject>(stateString); return states; @@ -139,8 +132,9 @@ public async Task> GetOrchestrationStateAsync(string i /// The OrchestrationState of the specified instanceId or null if not found public async Task GetOrchestrationStateAsync(string instanceId, string executionId) { - var uri = ConstructEndpointUri(instanceId, "GetOrchestrationState"); - var builder = new UriBuilder($"{uri}?instanceId={instanceId}&executionId={executionId}"); + instanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var builder = new UriBuilder($"{uri}?executionId={executionId}"); var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var state = JsonConvert.DeserializeObject(stateString); return state; @@ -154,15 +148,19 @@ public async Task GetOrchestrationStateAsync(string instance /// What to compare the threshold date time against public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) { - List allTasks = new List(); - foreach(var endpoint in await this.partitionProvider.GetPartitionEndpointsAsync()) + List> allTasks = new List>(); + foreach (var endpoint in await this.GetAllEndpointsAsync()) { - var uri = $"{endpoint.ToString()}/PurgeOrchestrationHistory"; + var uri = $"{endpoint.ToString()}/{GetHistoryFragment()}"; var task = this.HttpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); allTasks.Add(task); } - await Task.WhenAll(allTasks.ToArray()); + var responses = await Task.WhenAll(allTasks.ToArray()); + foreach (var response in responses) + { + response.EnsureSuccessStatusCode(); + } } /// @@ -172,7 +170,8 @@ public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, /// public async Task SendTaskOrchestrationMessageAsync(TaskMessage message) { - var uri = ConstructEndpointUri(message.OrchestrationInstance, "SendTaskOrchestrationMessage"); + message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); + var uri = await ConstructEndpointUri(message.OrchestrationInstance.InstanceId, GetMessageFragment(message.SequenceNumber)); await this.PutJsonAsync(uri, message); } @@ -183,8 +182,9 @@ public async Task SendTaskOrchestrationMessageAsync(TaskMessage message) /// public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] messages) { - foreach(var message in messages) + foreach (var message in messages) { + message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); await SendTaskOrchestrationMessageAsync(message); } } @@ -198,6 +198,7 @@ public async Task SendTaskOrchestrationMessageBatchAsync(params TaskMessage[] me /// Task cancellation token public async Task WaitForOrchestrationAsync(string instanceId, string executionId, TimeSpan timeout, CancellationToken cancellationToken) { + instanceId.EnsureValidInstanceId(); var maxTime = DateTime.Now.Add(timeout); OrchestrationState state = null; while (!cancellationToken.IsCancellationRequested && DateTime.Now < maxTime) @@ -215,14 +216,55 @@ public async Task WaitForOrchestrationAsync(string instanceI return state; } - private Uri ConstructEndpointUri(OrchestrationInstance instance, string action) + #region Prepare url fragments for the requests + + private string GetHistoryFragment() => "history"; + + private string GetHistoryFragment(string orchestrationId) => $"history/{orchestrationId}"; + + private string GetOrchestrationFragment() => "orchestrations"; + + private string GetOrchestrationFragment(string orchestrationId) => $"orchestrations/{orchestrationId}"; + + private string GetMessageFragment() => "messages"; + + private string GetMessageFragment(long messageId) => $"messages/{messageId}"; + + #endregion Prepare url fragments for the requests + + private async Task PutJsonAsync(Uri uri, object @object) + { + var mediaFormatter = new JsonMediaTypeFormatter() + { + SerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } + }; + + var result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); + if (!result.IsSuccessStatusCode) + { + throw new Exception("CreateTaskOrchestrationAsync failed"); + } + } + + private async Task ConstructEndpointUri(string instanceId, string fragment) + { + instanceId.EnsureValidInstanceId(); + var endpoint = await this.partitionProvider.GetParitionEndpointAsync(instanceId); + var defaultEndPoint = GetDefaultEndPoint(endpoint); + return new Uri($"{defaultEndPoint}/{fragment}"); + } + + private async Task> GetAllEndpointsAsync() { - return new Uri($"{this.partitionProvider.GetParitionEndpointAsync(instance.InstanceId)}/{action}"); + var endpoints = await this.partitionProvider.GetPartitionEndpointsAsync(); + return endpoints.Select(GetDefaultEndPoint).Select(x => new Uri(x)); } - private Uri ConstructEndpointUri(string instanceId, string action) + private string GetDefaultEndPoint(string endpoint) { - return new Uri($"{this.partitionProvider.GetParitionEndpointAsync(instanceId)}/{action}"); + var jObject = JObject.Parse(endpoint); + var defaultEndPoint = jObject["Endpoints"][string.Empty].ToString(); + return defaultEndPoint; } } } diff --git a/src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs b/src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs new file mode 100644 index 000000000..2f5d4a97a --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System.Threading; + using System.Threading.Tasks; + using System.Web.Http.ExceptionHandling; + + using DurableTask.ServiceFabric.Tracing; + + /// + /// Traces application exceptions. + /// + public class ApplicationExceptionsLogger : IExceptionLogger + { + /// + public Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) + { + ProviderEventSource.Tracing.ServiceRequestFailed(context.Request.RequestUri.AbsolutePath, context.Exception.ToString()); + return Task.CompletedTask; + } + } +} diff --git a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs index f63b6a332..30edb813c 100644 --- a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs @@ -54,7 +54,6 @@ public IDependencyScope BeginScope() /// public void Dispose() { - this.provider = null; } } } diff --git a/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs b/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs index 78f359cc9..52bb9627b 100644 --- a/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs @@ -16,6 +16,7 @@ namespace DurableTask.ServiceFabric.Service using System; using System.Security.Cryptography; using System.Text; + using System.Threading.Tasks; /// /// Implements for string elements. @@ -23,23 +24,25 @@ namespace DurableTask.ServiceFabric.Service public class DefaultStringHasher : IHasher { /// - public long GetLongHashCode(string input) + public Task GenerateHashCode(string value) { - if (string.IsNullOrEmpty(input)) + long hashCode = 0; + if (!string.IsNullOrEmpty(value)) { - return 0; - } - using (var sha256 = SHA256Managed.Create()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = sha256.ComputeHash(bytes); - var long1 = BitConverter.ToInt64(bytes, 0); - var long2 = BitConverter.ToInt64(bytes, 8); - var long3 = BitConverter.ToInt64(bytes, 16); - var long4 = BitConverter.ToInt64(bytes, 24); + using (var sha256 = SHA256Managed.Create()) + { + var bytes = Encoding.UTF8.GetBytes(value); + var hash = sha256.ComputeHash(bytes); + var long1 = BitConverter.ToInt64(hash, 0); + var long2 = BitConverter.ToInt64(hash, 8); + var long3 = BitConverter.ToInt64(hash, 16); + var long4 = BitConverter.ToInt64(hash, 24); - return long1 ^ long2 ^ long3 ^ long4; + hashCode = long1 ^ long2 ^ long3 ^ long4; + } } + + return Task.FromResult(hashCode); } } } diff --git a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs index ba4049ed1..fee6a3dbc 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -21,70 +21,113 @@ namespace DurableTask.ServiceFabric.Service using System.Threading; using System.Threading.Tasks; + using DurableTask.ServiceFabric.Tracing; using Microsoft.ServiceFabric.Services.Client; /// public class FabricPartitionEndpointResolver : IPartitionEndpointResolver { readonly Uri service; - readonly FabricClient fabricClient; + FabricClient fabricClient; Int64RangePartitionInformation[] partitionInfos; ConcurrentDictionary endPointsLookup; ServicePartitionResolver partitionResolver; IHasher instanceIdHasher; + SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); /// - /// + /// Creates instance of . /// - /// - /// + /// Fabric service uri + /// Instance id hasher public FabricPartitionEndpointResolver(Uri service, IHasher instanceIdHasher) { this.service = service ?? throw new ArgumentNullException(nameof(service)); this.instanceIdHasher = instanceIdHasher ?? throw new ArgumentNullException(nameof(instanceIdHasher)); - this.fabricClient = new FabricClient(); this.endPointsLookup = new ConcurrentDictionary(); this.partitionResolver = ServicePartitionResolver.GetDefault(); + + this.InitializeFabricClient(); + } + + private void InitializeFabricClient() + { + if (this.fabricClient != null) + { + // unregister events + this.fabricClient.ServiceManager.ServiceNotificationFilterMatched -= OnServiceNotificationFilterMatched; + this.fabricClient.ClientDisconnected -= OnClientDisconnected; + } + + this.fabricClient = new FabricClient(); + this.fabricClient.ServiceManager.ServiceNotificationFilterMatched += OnServiceNotificationFilterMatched; + this.fabricClient.ClientDisconnected += OnClientDisconnected; + } + + private void OnServiceNotificationFilterMatched(object sender, EventArgs e) + { + // Currently, code rebuilds entire partition table. + BuildPartitionInfosAsync(true).ConfigureAwait(false); + } + + private void OnClientDisconnected(object sender, EventArgs e) + { + // FabicClient is disconnected, recreate FabricClient. + InitializeFabricClient(); + BuildPartitionInfosAsync(true).ConfigureAwait(false); } /// - public async Task GetParitionEndpointAsync(string instanceId) + public async Task GetParitionEndpointAsync(string instanceId) { - await BuildPartitionInfos(false); - var hash = this.instanceIdHasher.GetLongHashCode(instanceId); + await BuildPartitionInfosAsync(false); + var hash = await this.instanceIdHasher.GenerateHashCode(instanceId); var partition = this.BinarySearch(partitionInfos, hash); - return new Uri(endPointsLookup[partition.LowKey].Address); + return endPointsLookup[partition.LowKey].Address; } /// - public async Task> GetPartitionEndpointsAsync() + public async Task> GetPartitionEndpointsAsync() { - await BuildPartitionInfos(false); - return this.endPointsLookup.Values.Select(x => new Uri(x.Address)); + await BuildPartitionInfosAsync(false); + return this.endPointsLookup.Values.Select(x => x.Address); } - private async Task BuildPartitionInfos(bool force) + private async Task BuildPartitionInfosAsync(bool force) { if (force || this.partitionInfos == null) { - CancellationToken cancellationToken = new CancellationToken(); - var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.service); - List partitions = new List(); - foreach (var partition in partitionList) + await this.semaphoreSlim.WaitAsync(); + try { - var info = (Int64RangePartitionInformation)partition.PartitionInformation; - if (info == null) + if (force || this.partitionInfos == null) { - throw new NotSupportedException($"{partition.PartitionInformation.Kind} is not supported"); - } + ProviderEventSource.Tracing.PartitionCacheBuildStart(); + CancellationToken cancellationToken = new CancellationToken(); + var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.service); + List partitions = new List(); + foreach (var partition in partitionList) + { + var info = partition.PartitionInformation as Int64RangePartitionInformation; + if (info == null) + { + throw new NotSupportedException($"{partition.PartitionInformation.Kind} is not supported"); + } - partitions.Add(info); - var resolvedServicePartition = await this.partitionResolver.ResolveAsync(this.service, new ServicePartitionKey(info.LowKey), cancellationToken); - var endpoint = resolvedServicePartition.GetEndpoint(); - this.endPointsLookup.AddOrUpdate(info.LowKey, endpoint, (_, __) => endpoint); - } + partitions.Add(info); + var resolvedServicePartition = await this.partitionResolver.ResolveAsync(this.service, new ServicePartitionKey(info.LowKey), cancellationToken); + var endpoint = resolvedServicePartition.GetEndpoint(); + this.endPointsLookup.AddOrUpdate(info.LowKey, endpoint, (_, __) => endpoint); + } - this.partitionInfos = partitions.OrderBy(x => x.LowKey).ToArray(); + this.partitionInfos = partitions.OrderBy(x => x.LowKey).ToArray(); + ProviderEventSource.Tracing.PartitionCacheBuildComplete(); + } + } + finally + { + this.semaphoreSlim.Release(); + } } } diff --git a/src/DurableTask.ServiceFabric/Service/IHasher.cs b/src/DurableTask.ServiceFabric/Service/IHasher.cs index b833026e6..b8c54417d 100644 --- a/src/DurableTask.ServiceFabric/Service/IHasher.cs +++ b/src/DurableTask.ServiceFabric/Service/IHasher.cs @@ -13,6 +13,8 @@ namespace DurableTask.ServiceFabric.Service { + using System.Threading.Tasks; + /// /// provides long hash code. /// @@ -22,8 +24,8 @@ public interface IHasher /// /// Gets Long hash code. /// - /// Input element + /// Input element /// 64 bit hash code - long GetLongHashCode(T @input); + Task GenerateHashCode(T value); } } diff --git a/src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs b/src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs similarity index 62% rename from src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs rename to src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs index 8218d732f..7ddf78977 100644 --- a/src/DurableTask.ServiceFabric/Service/IFabricServiceContext.cs +++ b/src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs @@ -13,39 +13,27 @@ namespace DurableTask.ServiceFabric.Service { - using System.Collections.Generic; using System.Threading.Tasks; + using DurableTask.Core; - using Microsoft.ServiceFabric.Services.Communication.Runtime; /// - /// Fabric Service settings. + /// Orchestrations provider. /// - public interface IFabricServiceContext + public interface IOrchestrationsProvider { /// - /// - /// - /// - Task Register(TaskHubWorker taskHubWorker); - - /// - /// + /// Register orchestration artifacts with taskHubWorker /// + /// Instance of /// - int GetActivityDispatcherCount(); + Task RegisterOrchestrationArtifactsAsync(TaskHubWorker taskHubWorker); /// - /// + /// Gets fabric orchestration provider settings /// - /// - int GetOrchestrationDispatcherCount(); - - /// - /// - /// - /// - IEnumerable GetServiceReplicaListeners(); + /// Fabric orchestration provider settings + FabricOrchestrationProviderSettings GetFabricOrchestrationProviderSettings(); } } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs b/src/DurableTask.ServiceFabric/Service/Startup.cs similarity index 87% rename from Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs rename to src/DurableTask.ServiceFabric/Service/Startup.cs index 235651daf..954f806a1 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Startup.cs +++ b/src/DurableTask.ServiceFabric/Service/Startup.cs @@ -11,14 +11,14 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace TestApplication.StatefulService +namespace DurableTask.ServiceFabric.Service { using System.Net; using System.Web.Http; + using System.Web.Http.ExceptionHandling; using DurableTask.Core; using DurableTask.ServiceFabric; - using DurableTask.ServiceFabric.Service; using Microsoft.Extensions.DependencyInjection; using Owin; @@ -43,6 +43,7 @@ private ServiceProvider GenerateServiceProvider() var services = new ServiceCollection(); services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); services.AddTransient(); + services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); return serviceProvider; } @@ -51,14 +52,9 @@ void IOwinAppBuilder.Startup(IAppBuilder appBuilder) { ServicePointManager.DefaultConnectionLimit = 256; HttpConfiguration config = new HttpConfiguration(); + config.MapHttpAttributeRoutes(); config.DependencyResolver = new DefaultDependencyResolver(GenerateServiceProvider()); - - config.Routes.MapHttpRoute( - name: "DurableTasks", - routeTemplate: "api/dtfx/{action}", - defaults: new { controller = "FabricOrchestrationService", id = RouteParameter.Optional } - ); - + config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All; appBuilder.UseWebApi(config); } diff --git a/src/DurableTask.ServiceFabric/Service/FabricService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs similarity index 59% rename from src/DurableTask.ServiceFabric/Service/FabricService.cs rename to src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs index 8051d799d..57eaeeb06 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs @@ -16,6 +16,10 @@ namespace DurableTask.ServiceFabric.Service using System; using System.Collections.Generic; using System.Fabric; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -28,37 +32,26 @@ namespace DurableTask.ServiceFabric.Service /// /// An instance of this class is created for each service replica by the Service Fabric runtime. /// - public sealed class FabricService : StatefulService + public sealed class TaskHubProxyService : StatefulService { readonly FabricOrchestrationProviderFactory fabricProviderFactory; - - /// - /// - /// - public FabricOrchestrationProvider FabricOrchestrationProvider { get; set; } - - IFabricServiceContext serviceContext; - + readonly IOrchestrationsProvider orchestrationsProvider; + FabricOrchestrationProvider fabricOrchestrationProvider; TaskHubWorker worker; - TaskHubClient client; ReplicaRole currentRole; /// - /// + /// Creates instance of /// - /// - /// - public FabricService(StatefulServiceContext context, IFabricServiceContext serviceContext) : base(context) + /// stateful service context + /// Orchestrations provider + public TaskHubProxyService(StatefulServiceContext context, IOrchestrationsProvider orchestrationsProvider) : base(context) { - this.serviceContext = serviceContext ?? throw new ArgumentNullException(nameof(serviceContext)); - - var providerSettings = new FabricOrchestrationProviderSettings(); - providerSettings.TaskOrchestrationDispatcherSettings.DispatcherCount = this.serviceContext.GetOrchestrationDispatcherCount(); - providerSettings.TaskActivityDispatcherSettings.DispatcherCount = this.serviceContext.GetOrchestrationDispatcherCount(); + this.orchestrationsProvider = orchestrationsProvider ?? throw new ArgumentNullException(nameof(orchestrationsProvider)); + var providerSettings = this.orchestrationsProvider.GetFabricOrchestrationProviderSettings(); this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, providerSettings); - this.FabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - this.client = new TaskHubClient(this.FabricOrchestrationProvider.OrchestrationServiceClient); + this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); } /// @@ -70,7 +63,22 @@ public FabricService(StatefulServiceContext context, IFabricServiceContext servi /// A collection of listeners. protected override IEnumerable CreateServiceReplicaListeners() { - return this.serviceContext.GetServiceReplicaListeners(); + yield return new ServiceReplicaListener(context => + { + var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); + int port = serviceEndpoint.Port; + + string ipAddress = context.NodeContext.IPAddressOrFQDN; +#if DEBUG + IPHostEntry entry = Dns.GetHostEntry(ipAddress); + IPAddress ipv4Address = entry.AddressList.FirstOrDefault( + address => (address.AddressFamily == AddressFamily.InterNetwork) && (!IPAddress.IsLoopback(address))); + ipAddress = ipv4Address.ToString(); +#endif + + string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/", ipAddress, port); + return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); + }); } /// @@ -80,15 +88,16 @@ protected override IEnumerable CreateServiceReplicaListe /// Canceled when Service Fabric needs to shut down this service replica. protected override async Task RunAsync(CancellationToken cancellationToken) { - if (this.FabricOrchestrationProvider == null) + if (this.fabricOrchestrationProvider == null) { - this.FabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - this.client = new TaskHubClient(this.FabricOrchestrationProvider.OrchestrationServiceClient); + this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); } - this.worker = new TaskHubWorker(this.FabricOrchestrationProvider.OrchestrationService); + this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); + + await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); - await this.serviceContext.Register(this.worker); + await this.worker.StartAsync(); this.worker.TaskActivityDispatcher.IncludeDetails = true; } @@ -103,13 +112,13 @@ protected override async Task RunAsync(CancellationToken cancellationToken) /// protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - ServiceEventSource.Tracing.ServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + ProviderEventSource.Tracing.FabricServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) { await ShutdownAsync(); } this.currentRole = newRole; - ServiceEventSource.Tracing.ServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); + ProviderEventSource.Tracing.FabricServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); } /// @@ -119,7 +128,7 @@ protected override async Task OnChangeRoleAsync(ReplicaRole newRole, Cancellatio /// A Task that represents outstanding operation. protected override async Task OnCloseAsync(CancellationToken cancellationToken) { - ServiceEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); + ProviderEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); await ShutdownAsync(); } @@ -129,18 +138,18 @@ async Task ShutdownAsync() { if (this.worker != null) { - ServiceEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); + ProviderEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); await this.worker.StopAsync(isForced: true); this.worker.Dispose(); - this.FabricOrchestrationProvider.Dispose(); - this.FabricOrchestrationProvider = null; + this.fabricOrchestrationProvider.Dispose(); + this.fabricOrchestrationProvider = null; this.worker = null; - ServiceEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); + ProviderEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); } } catch (Exception e) { - ServiceEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); + ProviderEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); throw; } } diff --git a/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs index 61d364eac..34deb8f52 100644 --- a/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs +++ b/src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs @@ -16,11 +16,15 @@ namespace DurableTask.ServiceFabric.Tracing using System.Diagnostics.Tracing; using System.Threading.Tasks; using System; + using Microsoft.ServiceFabric.Services.Runtime; + /// /// The event source which emits ETW events for service fabric based provider functionality. /// - [EventSource(Name = "DurableTask-ServiceFabricProvider", Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C")] + [EventSource( + Name = "DurableTask-ServiceFabricProvider", + Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C")] public sealed class ProviderEventSource : EventSource { /// @@ -47,6 +51,8 @@ private static class Keywords public const EventKeywords Warning = (EventKeywords)0x8L; public const EventKeywords Error = (EventKeywords)0x10L; public const EventKeywords NetworkAction = (EventKeywords)0x20L; + public const EventKeywords Requests = (EventKeywords)0x40L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x80L; } #region Informational 1-500 @@ -184,5 +190,78 @@ internal void LogSizeMeasure(string uniqueObjectIdentifier, long sizeInBytes) #endif } #endregion + + #region Proxy Service 2001-3000 + [NonEvent] + internal void ServiceMessage(StatefulService service, string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + ServiceMessage( + service.Context.ServiceName.ToString(), + service.Context.ServiceTypeName, + service.Context.ReplicaId, + service.Context.PartitionId, + service.Context.CodePackageActivationContext.ApplicationName, + service.Context.CodePackageActivationContext.ApplicationTypeName, + service.Context.NodeContext.NodeName, + finalMessage); + } + } + + [Event(2001, + Level = EventLevel.Informational, + Message = "Service request '{0}' started", + Opcode = EventOpcode.Start, + Keywords = Keywords.Requests)] + internal void FabricServiceRequestStart(string requestTypeName) + { + WriteEvent(2001, requestTypeName); + } + + [Event(2002, + Level = EventLevel.Informational, + Message = "Service request '{0}' finished", + Opcode = EventOpcode.Stop, + Keywords = Keywords.Requests)] + internal void FabricServiceRequestStop(string requestTypeName) + { + WriteEvent(2002, requestTypeName); + } + + [Event(2003, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] + internal void ServiceRequestFailed(string requestTypeName, string exception) + { + WriteEvent(2003, exception); + } + + + [Event(2004, Level = EventLevel.Error, Message = "Partition cache building is started", Keywords = Keywords.ServiceInitialization)] + internal void PartitionCacheBuildStart() + { + WriteEvent(2004, "Partition cache building is started"); + } + + [Event(2005, Level = EventLevel.Error, Message = "Partition cache building is completed", Keywords = Keywords.ServiceInitialization)] + internal void PartitionCacheBuildComplete() + { + WriteEvent(2005, "Partition cache building is completed"); + } + + [Event(2006, Level = EventLevel.Informational, Message = "{7}")] + private void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { + WriteEvent(2006, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); + } + #endregion } } diff --git a/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs deleted file mode 100644 index b593204e9..000000000 --- a/src/DurableTask.ServiceFabric/Tracing/ServiceEventSource.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace DurableTask.ServiceFabric.Tracing -{ - using System; - using System.Diagnostics.Tracing; - using System.Threading.Tasks; - using Microsoft.ServiceFabric.Services.Runtime; - - [EventSource(Name = "DurableTask-FabricService", Guid = "E8FFF9F2-BD27-41A7-B354-991AC4E4EBD9")] - internal sealed class ServiceEventSource : EventSource - { - /// - /// Singleton instance of the event source. - /// - public static readonly ServiceEventSource Tracing = new ServiceEventSource(); - - static ServiceEventSource() - { - // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. - // This problem will be fixed in .NET Framework 4.6.2. - Task.Run(() => { }); - } - - - #region Keywords - // Event keywords can be used to categorize events. - // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). - // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. - public static class Keywords - { - public const EventKeywords Requests = (EventKeywords)0x1L; - public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; - } - #endregion - - [NonEvent] - public void ServiceMessage(StatefulService service, string message, params object[] args) - { - if (this.IsEnabled()) - { - string finalMessage = string.Format(message, args); - ServiceMessage( - service.Context.ServiceName.ToString(), - service.Context.ServiceTypeName, - service.Context.ReplicaId, - service.Context.PartitionId, - service.Context.CodePackageActivationContext.ApplicationName, - service.Context.CodePackageActivationContext.ApplicationTypeName, - service.Context.NodeContext.NodeName, - finalMessage); - } - } - - - private const int ServiceRequestStopEventId = 6; - [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] - public void ServiceRequestStop(string requestTypeName) - { - WriteEvent(ServiceRequestStopEventId, requestTypeName); - } - - private const int ServiceRequestFailedEventId = 7; - [Event(ServiceRequestFailedEventId, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] - public void ServiceRequestFailed(string requestTypeName, string exception) - { - WriteEvent(ServiceRequestFailedEventId, exception); - } - - - // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. - // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. - // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. - private const int ServiceMessageEventId = 2; - [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] - private -#if UNSAFE - unsafe -#endif - void ServiceMessage( - string serviceName, - string serviceTypeName, - long replicaOrInstanceId, - Guid partitionId, - string applicationName, - string applicationTypeName, - string nodeName, - string message) - { -#if !UNSAFE - WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); -#else - const int numArgs = 8; - fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) - { - EventData* eventData = stackalloc EventData[numArgs]; - eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; - eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; - eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; - eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; - eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; - eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; - eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; - eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; - - WriteEventCore(ServiceMessageEventId, numArgs, eventData); - } -#endif - } - - // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. - // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, - // and other statistics. - private const int ServiceRequestStartEventId = 5; - [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] - public void ServiceRequestStart(string requestTypeName) - { - WriteEvent(ServiceRequestStartEventId, requestTypeName); - } - - } - -} From bb34736eb773ed088eefcad204336400b388ff18 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Mon, 25 Mar 2019 11:23:40 -0700 Subject: [PATCH 05/12] Fix reported issue. --- .../Exceptions/InvalidInstanceIdException.cs | 1 + .../FabricOrchestrationServiceClient.cs | 5 +- .../IPartitionEndpointResolver.cs | 2 +- .../RemoteOrchestrationServiceClient.cs | 42 ++++- .../Service/DefaultDependencyResolver.cs | 21 ++- .../FabricPartitionEndpointResolver.cs | 163 +++++++++++------- .../Service/TaskHubProxyService.cs | 2 + 7 files changed, 166 insertions(+), 70 deletions(-) diff --git a/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs b/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs index 3a6ef5815..ac73ed6bc 100644 --- a/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs +++ b/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs @@ -18,6 +18,7 @@ namespace DurableTask.ServiceFabric.Exceptions /// /// Exception representing that instanceId is not Valid. /// + [Serializable] public class InvalidInstanceIdException : Exception { /// diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index 9a19c1944..32bea2c5d 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -25,8 +25,11 @@ namespace DurableTask.ServiceFabric using DurableTask.ServiceFabric.Stores; using DurableTask.ServiceFabric.TaskHelpers; using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; + using Newtonsoft.Json; + class FabricOrchestrationServiceClient : IOrchestrationServiceClient { readonly IReliableStateManager stateManager; @@ -155,7 +158,7 @@ public Task GetOrchestrationHistoryAsync(string instanceId, string execu // Other implementations returns full history for the execution. // This implementation returns just the final history, i.e., state. - var result = Newtonsoft.Json.JsonConvert.SerializeObject(this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId)); + var result = JsonConvert.SerializeObject(this.instanceStore.GetOrchestrationStateAsync(instanceId, executionId)); return Task.FromResult(result); } diff --git a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs index 3023e9328..3a87d5fb5 100644 --- a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs @@ -32,6 +32,6 @@ public interface IPartitionEndpointResolver /// Gets partition end point for given instanceId. /// /// Partition end point - Task GetParitionEndpointAsync(string instanceId); + Task GetPartitionEndPointAsync(string instanceId); } } diff --git a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index 8cb13140a..5afd41b5c 100644 --- a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -31,10 +31,11 @@ namespace DurableTask.ServiceFabric.Remote /// /// Allows to interact with a remote IOrchestrationServiceClient /// - public class RemoteOrchestrationServiceClient : IOrchestrationServiceClient + public class RemoteOrchestrationServiceClient : IOrchestrationServiceClient, IDisposable { private readonly IPartitionEndpointResolver partitionProvider; private readonly TimeSpan pollDelay = TimeSpan.FromSeconds(10); + private HttpClient httpClient; /// /// Creates a new instance. @@ -42,12 +43,23 @@ public class RemoteOrchestrationServiceClient : IOrchestrationServiceClient public RemoteOrchestrationServiceClient(IPartitionEndpointResolver partitionProvider) { this.partitionProvider = partitionProvider; + this.httpClient = new HttpClient(); } /// /// HttpClient for making REST calls. /// - public HttpClient HttpClient { get; set; } = new HttpClient(); + public HttpClient HttpClient + { + get + { + return this.httpClient; + } + set + { + this.httpClient = value; + } + } /// /// Creates a new orchestration @@ -249,7 +261,7 @@ private async Task PutJsonAsync(Uri uri, object @object) private async Task ConstructEndpointUri(string instanceId, string fragment) { instanceId.EnsureValidInstanceId(); - var endpoint = await this.partitionProvider.GetParitionEndpointAsync(instanceId); + var endpoint = await this.partitionProvider.GetPartitionEndPointAsync(instanceId); var defaultEndPoint = GetDefaultEndPoint(endpoint); return new Uri($"{defaultEndPoint}/{fragment}"); } @@ -266,5 +278,29 @@ private string GetDefaultEndPoint(string endpoint) var defaultEndPoint = jObject["Endpoints"][string.Empty].ToString(); return defaultEndPoint; } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.httpClient.Dispose(); + } + + disposedValue = true; + } + } + + /// + public void Dispose() + { + Dispose(true); + } + #endregion } } diff --git a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs index 30edb813c..158964566 100644 --- a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs @@ -51,9 +51,28 @@ public IDependencyScope BeginScope() return this; } - /// + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.provider = null; + } + + disposedValue = true; + } + } + + /// public void Dispose() { + Dispose(true); } + #endregion } } diff --git a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs index fee6a3dbc..be42ac252 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -17,34 +17,34 @@ namespace DurableTask.ServiceFabric.Service using System.Collections.Concurrent; using System.Collections.Generic; using System.Fabric; + using System.Fabric.Description; + using System.Fabric.Query; using System.Linq; using System.Threading; using System.Threading.Tasks; - using DurableTask.ServiceFabric.Tracing; using Microsoft.ServiceFabric.Services.Client; + /// - public class FabricPartitionEndpointResolver : IPartitionEndpointResolver + public class FabricPartitionEndpointResolver : IPartitionEndpointResolver, IDisposable { - readonly Uri service; - FabricClient fabricClient; - Int64RangePartitionInformation[] partitionInfos; - ConcurrentDictionary endPointsLookup; - ServicePartitionResolver partitionResolver; - IHasher instanceIdHasher; - SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + private readonly Uri serviceUri; + private FabricClient fabricClient; + private Partition[] servicePartitions; + private ConcurrentDictionary> endPointsLookup; + private readonly ServicePartitionResolver partitionResolver; + private readonly IHasher instanceIdHasher; /// /// Creates instance of . /// - /// Fabric service uri + /// Fabric service uri /// Instance id hasher - public FabricPartitionEndpointResolver(Uri service, IHasher instanceIdHasher) + public FabricPartitionEndpointResolver(Uri serviceUri, IHasher instanceIdHasher) { - this.service = service ?? throw new ArgumentNullException(nameof(service)); + this.serviceUri = serviceUri ?? throw new ArgumentNullException(nameof(serviceUri)); this.instanceIdHasher = instanceIdHasher ?? throw new ArgumentNullException(nameof(instanceIdHasher)); - this.endPointsLookup = new ConcurrentDictionary(); this.partitionResolver = ServicePartitionResolver.GetDefault(); this.InitializeFabricClient(); @@ -59,92 +59,99 @@ private void InitializeFabricClient() this.fabricClient.ClientDisconnected -= OnClientDisconnected; } + this.endPointsLookup = new ConcurrentDictionary>(); this.fabricClient = new FabricClient(); this.fabricClient.ServiceManager.ServiceNotificationFilterMatched += OnServiceNotificationFilterMatched; this.fabricClient.ClientDisconnected += OnClientDisconnected; + var serviceNotificationFilterDescription = new ServiceNotificationFilterDescription() + { + Name = this.serviceUri, + MatchNamePrefix = true, + MatchPrimaryChangeOnly = true + }; + + this.fabricClient.ServiceManager.RegisterServiceNotificationFilterAsync(serviceNotificationFilterDescription, TimeSpan.FromSeconds(10), CancellationToken.None); + + this.BuildServicePartitions().GetAwaiter().GetResult(); + } + + private async Task BuildServicePartitions() + { + var concurrentDictionary = new ConcurrentDictionary>(); + var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.serviceUri); + this.servicePartitions = partitionList.OrderBy(x => ((Int64RangePartitionInformation)x.PartitionInformation).LowKey).ToArray(); } private void OnServiceNotificationFilterMatched(object sender, EventArgs e) { - // Currently, code rebuilds entire partition table. - BuildPartitionInfosAsync(true).ConfigureAwait(false); + var notification = ((FabricClient.ServiceManagementClient.ServiceNotificationEventArgs)e).Notification; + var partitionInformation = (Int64RangePartitionInformation)notification.PartitionInfo; + + OnServiceNotificationFilterMatchedAsync(partitionInformation).GetAwaiter().GetResult(); + } + + private async Task OnServiceNotificationFilterMatchedAsync(Int64RangePartitionInformation partitionInformation) + { + var completionSource = new TaskCompletionSource(); + this.endPointsLookup.AddOrUpdate(partitionInformation.Id, completionSource, (_, __) => completionSource); + + var endPoint = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(partitionInformation.LowKey), CancellationToken.None); + completionSource.SetResult(endPoint.GetEndpoint()); } private void OnClientDisconnected(object sender, EventArgs e) { // FabicClient is disconnected, recreate FabricClient. InitializeFabricClient(); - BuildPartitionInfosAsync(true).ConfigureAwait(false); } /// - public async Task GetParitionEndpointAsync(string instanceId) + public async Task GetPartitionEndPointAsync(string instanceId) { - await BuildPartitionInfosAsync(false); var hash = await this.instanceIdHasher.GenerateHashCode(instanceId); - var partition = this.BinarySearch(partitionInfos, hash); - return endPointsLookup[partition.LowKey].Address; + var partition = this.BinarySearch(this.servicePartitions, hash); + return await GetPartitionEndPointAsync(partition); } - /// - public async Task> GetPartitionEndpointsAsync() + private async Task GetPartitionEndPointAsync(Partition partition) { - await BuildPartitionInfosAsync(false); - return this.endPointsLookup.Values.Select(x => x.Address); + var newCompletionSource = new TaskCompletionSource(); + var completionSource = this.endPointsLookup.GetOrAdd(partition.PartitionInformation.Id, newCompletionSource); + if (completionSource == newCompletionSource) + { + var partitionInfo = (Int64RangePartitionInformation)partition.PartitionInformation; + var endPoint = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(partitionInfo.LowKey), CancellationToken.None); + newCompletionSource.SetResult(endPoint.GetEndpoint()); + } + + return completionSource.Task.Result.Address; } - private async Task BuildPartitionInfosAsync(bool force) + /// + public async Task> GetPartitionEndpointsAsync() { - if (force || this.partitionInfos == null) + var endpoints = new List(); + foreach (var partition in this.servicePartitions) { - await this.semaphoreSlim.WaitAsync(); - try - { - if (force || this.partitionInfos == null) - { - ProviderEventSource.Tracing.PartitionCacheBuildStart(); - CancellationToken cancellationToken = new CancellationToken(); - var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.service); - List partitions = new List(); - foreach (var partition in partitionList) - { - var info = partition.PartitionInformation as Int64RangePartitionInformation; - if (info == null) - { - throw new NotSupportedException($"{partition.PartitionInformation.Kind} is not supported"); - } - - partitions.Add(info); - var resolvedServicePartition = await this.partitionResolver.ResolveAsync(this.service, new ServicePartitionKey(info.LowKey), cancellationToken); - var endpoint = resolvedServicePartition.GetEndpoint(); - this.endPointsLookup.AddOrUpdate(info.LowKey, endpoint, (_, __) => endpoint); - } - - this.partitionInfos = partitions.OrderBy(x => x.LowKey).ToArray(); - ProviderEventSource.Tracing.PartitionCacheBuildComplete(); - } - } - finally - { - this.semaphoreSlim.Release(); - } + endpoints.Add(await GetPartitionEndPointAsync(partition)); } + + return endpoints; } - private Int64RangePartitionInformation BinarySearch(Int64RangePartitionInformation[] array, long hash) + private Partition BinarySearch(Partition[] array, long hash) { int start = 0, end = array.Length - 1; do { - int middle = (start + end) / 2; - - if (hash >= array[middle].LowKey && hash <= array[middle].HighKey) + var middle = (start + end) / 2; + var partitionInformation = (Int64RangePartitionInformation)array[middle].PartitionInformation; + if (hash >= partitionInformation.LowKey && hash <= partitionInformation.HighKey) { return array[middle]; } - - else if (hash < array[middle].LowKey) + else if (hash < partitionInformation.LowKey) { end = middle - 1; } @@ -156,5 +163,33 @@ private Int64RangePartitionInformation BinarySearch(Int64RangePartitionInformati throw new Exception($"{hash} is out of partition range"); } + + #region IDisposable Support + + private bool disposedValue = false; // To detect redundant calls + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.servicePartitions = null; + this.endPointsLookup = null; + this.fabricClient.Dispose(); + } + + disposedValue = true; + } + } + + /// + public void Dispose() + { + Dispose(true); + } + + #endregion IDisposable Support } -} +} \ No newline at end of file diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs index 57eaeeb06..536760bec 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs @@ -100,6 +100,8 @@ protected override async Task RunAsync(CancellationToken cancellationToken) await this.worker.StartAsync(); this.worker.TaskActivityDispatcher.IncludeDetails = true; + + await Task.Delay(Timeout.Infinite, cancellationToken); } /// From dcaa88e78f593b6e68d26dc46f5dab8350dbd636 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Thu, 28 Mar 2019 16:21:41 -0700 Subject: [PATCH 06/12] Address code review comments - Implement simple partition resolver, without local caching. --- DurableTask.sln | 1 + ...ask.ServiceFabric.Integration.Tests.csproj | 2 +- .../Utilities.cs | 2 +- .../PackageRoot/ServiceManifest.xml | 2 +- .../TestApplication.StatefulService.csproj | 8 +- .../ApplicationParameters/Local.5Node.xml | 2 +- .../TestApplication/TestApplication.sfproj | 2 +- src/DurableTask.ServiceFabric/AssemblyInfo.cs | 1 + .../ExceptionUtilities.cs | 4 +- .../Exceptions/RemoteServiceException.cs | 44 ++ .../FabricOrchestrationInstanceStore.cs | 8 +- .../FabricOrchestrationService.cs | 18 +- .../FabricOrchestrationServiceClient.cs | 2 +- .../FabricOrchestrationServiceController.cs | 8 - .../IPartitionEndpointResolver.cs | 9 +- .../RemoteOrchestrationServiceClient.cs | 37 +- .../Service/ActivityLoggingMessageHandler.cs | 46 ++ .../Service/DefaultDependencyResolver.cs | 20 +- ...er.cs => DefaultStringPartitionHashing.cs} | 8 +- .../FabricPartitionEndpointResolver.cs | 169 ++------ .../{IHasher.cs => IPartitionHashing.cs} | 8 +- .../Service/OwinCommunicationListener.cs | 13 +- .../Service/ProxyServiceExceptionHandler.cs | 35 ++ ...ger.cs => ProxyServiceExceptionsLogger.cs} | 8 +- .../Service/Startup.cs | 8 +- .../Service/TaskHubProxyService.cs | 35 +- .../Stores/MessageProviderBase.cs | 4 +- .../Stores/ScheduledMessageProvider.cs | 6 +- .../Stores/SessionsProvider.cs | 20 +- .../TaskHelpers/RetryHelper.cs | 8 +- .../Tracing/ProviderEventSource.cs | 267 ------------ .../ServiceFabricProviderEventSource.cs | 408 ++++++++++++++++++ src/DurableTask.ServiceFabric/Utils.cs | 2 +- 33 files changed, 696 insertions(+), 519 deletions(-) create mode 100644 src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs create mode 100644 src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs rename src/DurableTask.ServiceFabric/Service/{DefaultStringHasher.cs => DefaultStringPartitionHashing.cs} (83%) rename src/DurableTask.ServiceFabric/Service/{IHasher.cs => IPartitionHashing.cs} (75%) create mode 100644 src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs rename src/DurableTask.ServiceFabric/Service/{ApplicationExceptionsLogger.cs => ProxyServiceExceptionsLogger.cs} (73%) delete mode 100644 src/DurableTask.ServiceFabric/Tracing/ProviderEventSource.cs create mode 100644 src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs diff --git a/DurableTask.sln b/DurableTask.sln index 4e91a5226..9ac081198 100644 --- a/DurableTask.sln +++ b/DurableTask.sln @@ -44,6 +44,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis", "src\DurableTask.Redis\DurableTask.Redis.csproj", "{BDA5B320-5282-49FE-B0A7-1F8A0F06862C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis.Tests", "Test\DurableTask.Redis.Tests\DurableTask.Redis.Tests.csproj", "{B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric", "src\DurableTask.ServiceFabric\DurableTask.ServiceFabric.csproj", "{3C10169F-4ACB-4FF6-A12B-B74569613E81}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric.Tests", "Test\DurableTask.ServiceFabric.Tests\DurableTask.ServiceFabric.Tests.csproj", "{AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}" diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj b/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj index 276c8cec9..b6ae8a3e0 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs index 50ec3e74c..bb757945d 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -26,7 +26,7 @@ public static class Utilities { public static TaskHubClient CreateTaskHubClient() { - var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new DefaultStringHasher()); + var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new DefaultStringPartitionHashing()); var httpClient = new HttpClient(); return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider)); } diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml index 7d8ee60a9..3970b79b8 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml +++ b/Test/TestFabricApplication/TestApplication.StatefulService/PackageRoot/ServiceManifest.xml @@ -22,7 +22,7 @@ - + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj index e0d4e1207..409f87dfe 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml index 683ffaa5b..362d313b5 100644 --- a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml @@ -1,7 +1,7 @@  - + diff --git a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj index bc65629ae..bdfc8e742 100644 --- a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj +++ b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj @@ -40,7 +40,7 @@ - + diff --git a/src/DurableTask.ServiceFabric/AssemblyInfo.cs b/src/DurableTask.ServiceFabric/AssemblyInfo.cs index f179965ce..d4db855b5 100644 --- a/src/DurableTask.ServiceFabric/AssemblyInfo.cs +++ b/src/DurableTask.ServiceFabric/AssemblyInfo.cs @@ -15,6 +15,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Scope = "type", Target = "DurableTask.ServiceFabric.Exceptions.RemoteServiceException")] #if !SIGN_ASSEMBLY [assembly: InternalsVisibleTo("DurableTask.ServiceFabric.Tests")] #endif diff --git a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs index 798691785..68370bce2 100644 --- a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs +++ b/src/DurableTask.ServiceFabric/ExceptionUtilities.cs @@ -29,11 +29,11 @@ public static void LogReliableCollectionException(string uniqueIdentifier, int a { if (isTransient) { - ProviderEventSource.Tracing.RetryableFabricException(uniqueIdentifier, attemptNumber, e.ToString()); + ServiceFabricProviderEventSource.Tracing.RetryableFabricException(uniqueIdentifier, attemptNumber, e.ToString()); } else { - ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations(uniqueIdentifier, e.ToString()); + ServiceFabricProviderEventSource.Tracing.ExceptionInReliableCollectionOperations(uniqueIdentifier, e.ToString()); } } } diff --git a/src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs b/src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs new file mode 100644 index 000000000..0a4eaa576 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Exceptions +{ + using System; + using System.Net; + + using DurableTask.ServiceFabric.Remote; + + + /// + /// The exception that is thrown by when proxy service returns non successful message. + /// + public class RemoteServiceException : Exception + { + private HttpStatusCode statusCode; + + /// + /// Creates an instance of . + /// + /// Exception message. + /// Http response message + public RemoteServiceException(string message, HttpStatusCode statusCode) : base(message) + { + this.statusCode = statusCode; + } + + /// + /// Instace of sent by proxy service. + /// + public HttpStatusCode HttpStatusCode => this.statusCode; + } +} diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs index 74b1a0d4f..f1d40f90f 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs @@ -305,10 +305,10 @@ Task CleanupOldDictionaries() toKeep.Add(storeName); } }); - ProviderEventSource.Tracing.LogTimeTaken($"Enumerating all reliable states (count: {toDelete.Count + toKeep.Count})", enumerationTime.TotalMilliseconds); + ServiceFabricProviderEventSource.Tracing.LogTimeTaken($"Enumerating all reliable states (count: {toDelete.Count + toKeep.Count})", enumerationTime.TotalMilliseconds); - ProviderEventSource.Tracing.ReliableStateManagement($"Deleting {toDelete.Count} stores", String.Join(",", toDelete)); - ProviderEventSource.Tracing.ReliableStateManagement($"All remaining {toKeep.Count} stores", String.Join(",", toKeep)); + ServiceFabricProviderEventSource.Tracing.ReliableStateManagement($"Deleting {toDelete.Count} stores", String.Join(",", toDelete)); + ServiceFabricProviderEventSource.Tracing.ReliableStateManagement($"All remaining {toKeep.Count} stores", String.Join(",", toKeep)); foreach (var storeName in toDelete) { @@ -316,7 +316,7 @@ Task CleanupOldDictionaries() { await this.stateManager.RemoveAsync(storeName); }); - ProviderEventSource.Tracing.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); + ServiceFabricProviderEventSource.Tracing.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); } }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(1), delayOnException: TimeSpan.FromMinutes(10), actionName: $"{nameof(CleanupOldDictionaries)}", token: this.cancellationToken); } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs index e36687a31..580623086 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs @@ -160,7 +160,7 @@ public async Task LockNextTaskOrchestrationWorkItemAs { if (currentRuntimeState.ExecutionStartedEvent == null) { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"Orchestration with no execution started event found: {currentSession.SessionId}"); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"Orchestration with no execution started event found: {currentSession.SessionId}"); return null; } @@ -182,7 +182,7 @@ public async Task LockNextTaskOrchestrationWorkItemAs if (!this.sessionInfos.TryAdd(workItem.InstanceId, sessionInfo)) { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(FabricOrchestrationService)}.{nameof(LockNextTaskOrchestrationWorkItemAsync)} : Multiple receivers processing the same session : {currentSession.SessionId.InstanceId}?"); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(FabricOrchestrationService)}.{nameof(LockNextTaskOrchestrationWorkItemAsync)} : Multiple receivers processing the same session : {currentSession.SessionId.InstanceId}?"); } return workItem; @@ -296,7 +296,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (FabricReplicationOperationTooLargeException ex) { - ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {sessionInfo.Instance}, Action = {nameof(CompleteTaskOrchestrationWorkItemAsync)}", ex.ToString()); + ServiceFabricProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {sessionInfo.Instance}, Action = {nameof(CompleteTaskOrchestrationWorkItemAsync)}", ex.ToString()); retryOnException = true; newOrchestrationRuntimeState = null; outboundMessages = null; @@ -359,7 +359,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => this.instanceStore.OnOrchestrationCompleted(workItem.OrchestrationRuntimeState.OrchestrationInstance); - ProviderEventSource.Tracing.OrchestrationFinished(workItem.InstanceId, + ServiceFabricProviderEventSource.Tracing.OrchestrationFinished(workItem.InstanceId, workItem.OrchestrationRuntimeState.OrchestrationStatus.ToString(), (workItem.OrchestrationRuntimeState.CompletedTime - workItem.OrchestrationRuntimeState.CreatedTime).TotalSeconds, workItem.OrchestrationRuntimeState.Output, @@ -371,7 +371,7 @@ public Task AbandonTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem work SessionInformation sessionInfo = TryRemoveSessionInfo(workItem.InstanceId); if (sessionInfo == null) { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(AbandonTaskOrchestrationWorkItemAsync)} : Could not get a session info object while trying to abandon session {workItem.InstanceId}"); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(AbandonTaskOrchestrationWorkItemAsync)} : Could not get a session info object while trying to abandon session {workItem.InstanceId}"); } else { @@ -438,7 +438,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (FabricReplicationOperationTooLargeException ex) { - ProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {responseMessage.OrchestrationInstance}, ActivityId = {workItem.Id}, Action = {nameof(CompleteTaskActivityWorkItemAsync)}", ex.ToString()); + ServiceFabricProviderEventSource.Tracing.ExceptionInReliableCollectionOperations($"OrchestrationInstance = {responseMessage.OrchestrationInstance}, ActivityId = {workItem.Id}, Action = {nameof(CompleteTaskActivityWorkItemAsync)}", ex.ToString()); retryOnException = true; var originalEvent = responseMessage.Event; int taskScheduledId = GetTaskScheduledId(originalEvent); @@ -500,11 +500,11 @@ int GetDelayForFetchOrProcessException(Exception exception) SessionInformation GetSessionInfo(string sessionId) { - ProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(GetSessionInfo)} - Getting session info"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(GetSessionInfo)} - Getting session info"); if (!this.sessionInfos.TryGetValue(sessionId, out SessionInformation sessionInfo)) { var message = $"{nameof(GetSessionInfo)}. Trying to get a session that's not in locked sessions {sessionId}"; - ProviderEventSource.Tracing.UnexpectedCodeCondition(message); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(message); throw new Exception(message); } @@ -514,7 +514,7 @@ SessionInformation GetSessionInfo(string sessionId) SessionInformation TryRemoveSessionInfo(string sessionId) { var removed = this.sessionInfos.TryRemove(sessionId, out SessionInformation sessionInfo); - ProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(TryRemoveSessionInfo)}: Removed = {removed}"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(sessionId, $"{nameof(TryRemoveSessionInfo)}: Removed = {removed}"); return sessionInfo; } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index 32bea2c5d..afcc1c7b9 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -73,7 +73,7 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) if (added) { - ProviderEventSource.Tracing.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); + ServiceFabricProviderEventSource.Tracing.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); this.orchestrationProvider.TryEnqueueSession(creationMessage.OrchestrationInstance); } else diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs index a29857150..7460eecc1 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -51,7 +51,6 @@ public FabricOrchestrationServiceController(IOrchestrationServiceClient orchestr [Route("orchestrations/{orchestrationId}")] public async Task CreateTaskOrchestration([FromUri] string orchestrationId, [FromBody] CreateTaskOrchestrationParameters parameters) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(CreateTaskOrchestration)); parameters.TaskMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); if (!orchestrationId.Equals(parameters.TaskMessage.OrchestrationInstance.InstanceId)) { @@ -86,7 +85,6 @@ public async Task CreateTaskOrchestration([FromUri] string or [Route("messages/{messageId}")] public async Task SendTaskOrchestrationMessage([FromUri]long messageId,[FromBody] TaskMessage message) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessage)); if (messageId != message.SequenceNumber) { return Conflict(); @@ -106,7 +104,6 @@ public async Task SendTaskOrchestrationMessage([FromUri]long [Route("messages")] public async Task SendTaskOrchestrationMessageBatch([FromBody] TaskMessage[] messages) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(SendTaskOrchestrationMessageBatch)); await this.orchestrationServiceClient.SendTaskOrchestrationMessageBatchAsync(messages); return new OkResult(this); } @@ -121,7 +118,6 @@ public async Task SendTaskOrchestrationMessageBatch([FromBody [Route("orchestrations/{orchestrationId}")] public async Task GetOrchestrationState([FromUri]string orchestrationId, string executionId) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); orchestrationId.EnsureValidInstanceId(); var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(orchestrationId, executionId); return state; @@ -137,7 +133,6 @@ public async Task GetOrchestrationState([FromUri]string orch [Route("orchestrations/{orchestrationId}")] public async Task> GetOrchestrationState([FromUri]string orchestrationId, bool allExecutions) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationState)); orchestrationId.EnsureValidInstanceId(); var state = await this.orchestrationServiceClient.GetOrchestrationStateAsync(orchestrationId, allExecutions); return state; @@ -153,7 +148,6 @@ public async Task> GetOrchestrationState([FromUri]stri [Route("orchestrations/{orchestrationId}")] public async Task ForceTerminateTaskOrchestration([FromUri]string orchestrationId, string reason) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(ForceTerminateTaskOrchestration)); orchestrationId.EnsureValidInstanceId(); await this.orchestrationServiceClient.ForceTerminateTaskOrchestrationAsync(orchestrationId, reason); return new OkResult(this); @@ -169,7 +163,6 @@ public async Task ForceTerminateTaskOrchestration([FromUri]st [Route("history/{orchestrationId}")] public async Task GetOrchestrationHistory([FromUri]string orchestrationId, string executionId) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(GetOrchestrationHistory)); orchestrationId.EnsureValidInstanceId(); var result = await this.orchestrationServiceClient.GetOrchestrationHistoryAsync(orchestrationId, executionId); return result; @@ -184,7 +177,6 @@ public async Task GetOrchestrationHistory([FromUri]string orchestrationI [Route("history")] public async Task PurgeOrchestrationHistory([FromBody]PurgeOrchestrationHistoryParameters purgeParameters) { - ProviderEventSource.Tracing.LogServingNetworkAction(nameof(PurgeOrchestrationHistory)); await this.orchestrationServiceClient.PurgeOrchestrationHistoryAsync(purgeParameters.ThresholdDateTimeUtc, purgeParameters.TimeRangeFilterType); return new OkResult(this); } diff --git a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs index 3a87d5fb5..f5cbf05ea 100644 --- a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs @@ -13,8 +13,8 @@ namespace DurableTask.ServiceFabric { - using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; /// @@ -25,13 +25,16 @@ public interface IPartitionEndpointResolver /// /// Gets end points for all the partitions. /// + /// Token to inform when a task is cancelled. /// All the end points. - Task> GetPartitionEndpointsAsync(); + Task> GetPartitionEndpointsAsync(CancellationToken cancellationToken); /// /// Gets partition end point for given instanceId. /// + /// InstanceId of orchestration + /// Token to inform when a task is cancelled. /// Partition end point - Task GetPartitionEndPointAsync(string instanceId); + Task GetPartitionEndPointAsync(string instanceId, CancellationToken cancellationToken); } } diff --git a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index 5afd41b5c..bf3665a9c 100644 --- a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -24,7 +24,9 @@ namespace DurableTask.ServiceFabric.Remote using DurableTask.Core; using DurableTask.Core.Exceptions; + using DurableTask.ServiceFabric.Exceptions; using DurableTask.ServiceFabric.Models; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -70,7 +72,7 @@ public HttpClient HttpClient public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) { creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment()); + var uri = await ConstructEndpointUriAsync(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment(), CancellationToken.None); await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage }); } @@ -84,7 +86,7 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) { creationMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment(creationMessage.OrchestrationInstance.InstanceId)); + var uri = await ConstructEndpointUriAsync(creationMessage.OrchestrationInstance.InstanceId, GetOrchestrationFragment(creationMessage.OrchestrationInstance.InstanceId), CancellationToken.None); await this.PutJsonAsync(uri, new CreateTaskOrchestrationParameters() { TaskMessage = creationMessage, DedupeStatuses = dedupeStatuses }); } @@ -96,12 +98,12 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, Orch public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string reason) { instanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var uri = await ConstructEndpointUriAsync(instanceId, GetOrchestrationFragment(instanceId), CancellationToken.None); var builder = new UriBuilder($"{uri}?reason={HttpUtility.UrlEncode(reason)}"); var response = await this.HttpClient.DeleteAsync(builder.Uri); if (!response.IsSuccessStatusCode) { - throw new Exception("Unable to terminate task instance"); + throw new RemoteServiceException("Unable to terminate task instance", response.StatusCode); } } @@ -114,7 +116,7 @@ public async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string public async Task GetOrchestrationHistoryAsync(string instanceId, string executionId) { instanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var uri = await ConstructEndpointUriAsync(instanceId, GetOrchestrationFragment(instanceId), CancellationToken.None); var builder = new UriBuilder($"{uri}?executionId={executionId}"); var history = await this.HttpClient.GetStringAsync(builder.Uri); return history; @@ -129,7 +131,7 @@ public async Task GetOrchestrationHistoryAsync(string instanceId, string public async Task> GetOrchestrationStateAsync(string instanceId, bool allExecutions) { instanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var uri = await ConstructEndpointUriAsync(instanceId, GetOrchestrationFragment(instanceId), CancellationToken.None); var builder = new UriBuilder($"{uri}?allExecutions={allExecutions}"); var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var states = JsonConvert.DeserializeObject>(stateString); @@ -145,7 +147,7 @@ public async Task> GetOrchestrationStateAsync(string i public async Task GetOrchestrationStateAsync(string instanceId, string executionId) { instanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(instanceId, GetOrchestrationFragment(instanceId)); + var uri = await ConstructEndpointUriAsync(instanceId, GetOrchestrationFragment(instanceId), CancellationToken.None); var builder = new UriBuilder($"{uri}?executionId={executionId}"); var stateString = await this.HttpClient.GetStringAsync(builder.Uri); var state = JsonConvert.DeserializeObject(stateString); @@ -161,7 +163,7 @@ public async Task GetOrchestrationStateAsync(string instance public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, OrchestrationStateTimeRangeFilterType timeRangeFilterType) { List> allTasks = new List>(); - foreach (var endpoint in await this.GetAllEndpointsAsync()) + foreach (var endpoint in await this.GetAllEndpointsAsync(CancellationToken.None)) { var uri = $"{endpoint.ToString()}/{GetHistoryFragment()}"; var task = this.HttpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); @@ -183,7 +185,7 @@ public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, public async Task SendTaskOrchestrationMessageAsync(TaskMessage message) { message.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); - var uri = await ConstructEndpointUri(message.OrchestrationInstance.InstanceId, GetMessageFragment(message.SequenceNumber)); + var uri = await ConstructEndpointUriAsync(message.OrchestrationInstance.InstanceId, GetMessageFragment(message.SequenceNumber), CancellationToken.None); await this.PutJsonAsync(uri, message); } @@ -251,29 +253,32 @@ private async Task PutJsonAsync(Uri uri, object @object) SerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All } }; - var result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); + HttpResponseMessage result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); if (!result.IsSuccessStatusCode) { - throw new Exception("CreateTaskOrchestrationAsync failed"); + throw new RemoteServiceException("CreateTaskOrchestrationAsync failed", result.StatusCode); } } - private async Task ConstructEndpointUri(string instanceId, string fragment) + private async Task ConstructEndpointUriAsync(string instanceId, string fragment, CancellationToken cancellationToken) { instanceId.EnsureValidInstanceId(); - var endpoint = await this.partitionProvider.GetPartitionEndPointAsync(instanceId); - var defaultEndPoint = GetDefaultEndPoint(endpoint); + cancellationToken.ThrowIfCancellationRequested(); + string endpoint = await this.partitionProvider.GetPartitionEndPointAsync(instanceId, cancellationToken); + string defaultEndPoint = GetDefaultEndPoint(endpoint); return new Uri($"{defaultEndPoint}/{fragment}"); } - private async Task> GetAllEndpointsAsync() + private async Task> GetAllEndpointsAsync(CancellationToken cancellationToken) { - var endpoints = await this.partitionProvider.GetPartitionEndpointsAsync(); + cancellationToken.ThrowIfCancellationRequested(); + IEnumerable endpoints = await this.partitionProvider.GetPartitionEndpointsAsync(cancellationToken); return endpoints.Select(GetDefaultEndPoint).Select(x => new Uri(x)); } private string GetDefaultEndPoint(string endpoint) { + // sample endpoint - {"Endpoints":{"":"http:\/\/10.91.42.35:30001"}} var jObject = JObject.Parse(endpoint); var defaultEndPoint = jObject["Endpoints"][string.Empty].ToString(); return defaultEndPoint; diff --git a/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs b/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs new file mode 100644 index 000000000..6faef482e --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.ServiceFabric.Tracing; + + internal class ActivityLoggingMessageHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + string requestMethod = request.Method.ToString(); + string requestUri = request.RequestUri.AbsolutePath; + ServiceFabricProviderEventSource.Tracing.LogProxyServiceRequest(requestMethod, requestUri); + HttpResponseMessage response = null; + try + { + response = await base.SendAsync(request, cancellationToken); + ServiceFabricProviderEventSource.Tracing.LogProxyServiceResponse(requestMethod, requestUri); + } + catch (Exception exception) + { + ServiceFabricProviderEventSource.Tracing.LogProxyServiceError(requestMethod, requestUri, exception); + response = request.CreateErrorResponse(HttpStatusCode.InternalServerError, exception); + } + + return response; + } + } +} diff --git a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs index 158964566..ad799b808 100644 --- a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs @@ -20,7 +20,7 @@ namespace DurableTask.ServiceFabric.Service using Microsoft.Extensions.DependencyInjection; /// - public class DefaultDependencyResolver : IDependencyResolver + public sealed class DefaultDependencyResolver : IDependencyResolver { private IServiceProvider provider; @@ -52,26 +52,10 @@ public IDependencyScope BeginScope() } #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - this.provider = null; - } - - disposedValue = true; - } - } - /// public void Dispose() { - Dispose(true); + // no-op } #endregion } diff --git a/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs b/src/DurableTask.ServiceFabric/Service/DefaultStringPartitionHashing.cs similarity index 83% rename from src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs rename to src/DurableTask.ServiceFabric/Service/DefaultStringPartitionHashing.cs index 52bb9627b..69fddef90 100644 --- a/src/DurableTask.ServiceFabric/Service/DefaultStringHasher.cs +++ b/src/DurableTask.ServiceFabric/Service/DefaultStringPartitionHashing.cs @@ -16,16 +16,18 @@ namespace DurableTask.ServiceFabric.Service using System; using System.Security.Cryptography; using System.Text; + using System.Threading; using System.Threading.Tasks; /// - /// Implements for string elements. + /// Implements for string elements. /// - public class DefaultStringHasher : IHasher + public class DefaultStringPartitionHashing : IPartitionHashing { /// - public Task GenerateHashCode(string value) + public Task GeneratePartitionHashCode(string value, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); long hashCode = 0; if (!string.IsNullOrEmpty(value)) { diff --git a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs index be42ac252..43c737f54 100644 --- a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -14,10 +14,8 @@ namespace DurableTask.ServiceFabric.Service { using System; - using System.Collections.Concurrent; using System.Collections.Generic; using System.Fabric; - using System.Fabric.Description; using System.Fabric.Query; using System.Linq; using System.Threading; @@ -25,171 +23,84 @@ namespace DurableTask.ServiceFabric.Service using Microsoft.ServiceFabric.Services.Client; - /// - public class FabricPartitionEndpointResolver : IPartitionEndpointResolver, IDisposable + public class FabricPartitionEndpointResolver : IPartitionEndpointResolver { private readonly Uri serviceUri; - private FabricClient fabricClient; - private Partition[] servicePartitions; - private ConcurrentDictionary> endPointsLookup; private readonly ServicePartitionResolver partitionResolver; - private readonly IHasher instanceIdHasher; + private readonly IPartitionHashing instanceIdHasher; + + private TaskCompletionSource servicePartitionsTaskCompletionSource; + private object tcsLockObject = new object(); /// /// Creates instance of . /// /// Fabric service uri /// Instance id hasher - public FabricPartitionEndpointResolver(Uri serviceUri, IHasher instanceIdHasher) + public FabricPartitionEndpointResolver(Uri serviceUri, IPartitionHashing instanceIdHasher) { this.serviceUri = serviceUri ?? throw new ArgumentNullException(nameof(serviceUri)); this.instanceIdHasher = instanceIdHasher ?? throw new ArgumentNullException(nameof(instanceIdHasher)); this.partitionResolver = ServicePartitionResolver.GetDefault(); - - this.InitializeFabricClient(); - } - - private void InitializeFabricClient() - { - if (this.fabricClient != null) - { - // unregister events - this.fabricClient.ServiceManager.ServiceNotificationFilterMatched -= OnServiceNotificationFilterMatched; - this.fabricClient.ClientDisconnected -= OnClientDisconnected; - } - - this.endPointsLookup = new ConcurrentDictionary>(); - this.fabricClient = new FabricClient(); - this.fabricClient.ServiceManager.ServiceNotificationFilterMatched += OnServiceNotificationFilterMatched; - this.fabricClient.ClientDisconnected += OnClientDisconnected; - var serviceNotificationFilterDescription = new ServiceNotificationFilterDescription() - { - Name = this.serviceUri, - MatchNamePrefix = true, - MatchPrimaryChangeOnly = true - }; - - this.fabricClient.ServiceManager.RegisterServiceNotificationFilterAsync(serviceNotificationFilterDescription, TimeSpan.FromSeconds(10), CancellationToken.None); - - this.BuildServicePartitions().GetAwaiter().GetResult(); - } - - private async Task BuildServicePartitions() - { - var concurrentDictionary = new ConcurrentDictionary>(); - var partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.serviceUri); - this.servicePartitions = partitionList.OrderBy(x => ((Int64RangePartitionInformation)x.PartitionInformation).LowKey).ToArray(); - } - - private void OnServiceNotificationFilterMatched(object sender, EventArgs e) - { - var notification = ((FabricClient.ServiceManagementClient.ServiceNotificationEventArgs)e).Notification; - var partitionInformation = (Int64RangePartitionInformation)notification.PartitionInfo; - - OnServiceNotificationFilterMatchedAsync(partitionInformation).GetAwaiter().GetResult(); - } - - private async Task OnServiceNotificationFilterMatchedAsync(Int64RangePartitionInformation partitionInformation) - { - var completionSource = new TaskCompletionSource(); - this.endPointsLookup.AddOrUpdate(partitionInformation.Id, completionSource, (_, __) => completionSource); - - var endPoint = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(partitionInformation.LowKey), CancellationToken.None); - completionSource.SetResult(endPoint.GetEndpoint()); - } - - private void OnClientDisconnected(object sender, EventArgs e) - { - // FabicClient is disconnected, recreate FabricClient. - InitializeFabricClient(); } /// - public async Task GetPartitionEndPointAsync(string instanceId) - { - var hash = await this.instanceIdHasher.GenerateHashCode(instanceId); - var partition = this.BinarySearch(this.servicePartitions, hash); - return await GetPartitionEndPointAsync(partition); - } - - private async Task GetPartitionEndPointAsync(Partition partition) + public async Task GetPartitionEndPointAsync(string instanceId, CancellationToken cancellationToken) { - var newCompletionSource = new TaskCompletionSource(); - var completionSource = this.endPointsLookup.GetOrAdd(partition.PartitionInformation.Id, newCompletionSource); - if (completionSource == newCompletionSource) - { - var partitionInfo = (Int64RangePartitionInformation)partition.PartitionInformation; - var endPoint = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(partitionInfo.LowKey), CancellationToken.None); - newCompletionSource.SetResult(endPoint.GetEndpoint()); - } - - return completionSource.Task.Result.Address; + cancellationToken.ThrowIfCancellationRequested(); + long hash = await this.instanceIdHasher.GeneratePartitionHashCode(instanceId, cancellationToken); + ResolvedServicePartition partition = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(hash), cancellationToken); + return partition.GetEndpoint().Address; } /// - public async Task> GetPartitionEndpointsAsync() + public async Task> GetPartitionEndpointsAsync(CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); var endpoints = new List(); - foreach (var partition in this.servicePartitions) + foreach (Int64RangePartitionInformation partitionInfo in await this.GetServicePartitionsListAsync()) { - endpoints.Add(await GetPartitionEndPointAsync(partition)); + var resolvedPartition = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(partitionInfo.LowKey), cancellationToken); + endpoints.Add(resolvedPartition.GetEndpoint().Address); } return endpoints; } - private Partition BinarySearch(Partition[] array, long hash) + private async Task GetServicePartitionsListAsync() { - int start = 0, end = array.Length - 1; - - do + if (this.servicePartitionsTaskCompletionSource == null) { - var middle = (start + end) / 2; - var partitionInformation = (Int64RangePartitionInformation)array[middle].PartitionInformation; - if (hash >= partitionInformation.LowKey && hash <= partitionInformation.HighKey) + bool isNewTaskCompletionSourceCreated = false; + lock (tcsLockObject) { - return array[middle]; + if (this.servicePartitionsTaskCompletionSource == null) + { + this.servicePartitionsTaskCompletionSource = new TaskCompletionSource(); + isNewTaskCompletionSourceCreated = true; + } } - else if (hash < partitionInformation.LowKey) - { - end = middle - 1; - } - else - { - start = middle + 1; - } - } while (start > end); - - throw new Exception($"{hash} is out of partition range"); - } - #region IDisposable Support - - private bool disposedValue = false; // To detect redundant calls - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) + if (isNewTaskCompletionSourceCreated) { - this.servicePartitions = null; - this.endPointsLookup = null; - this.fabricClient.Dispose(); + using (var fabricClient = new FabricClient()) + { + ServicePartitionList partitionList = await fabricClient.QueryManager.GetPartitionListAsync(this.serviceUri); + Int64RangePartitionInformation[] orderedPartitions = partitionList.Select(x => x.PartitionInformation) + .OfType() + .OrderBy(x => x.LowKey).ToArray(); + if (partitionList.Count != orderedPartitions.Length) + { + throw new NotSupportedException("FabricPartitionEndpointResolver doesn't support non Int64RangePartitionInformation partitions"); + } + + this.servicePartitionsTaskCompletionSource.SetResult(orderedPartitions); + } } - - disposedValue = true; } - } - /// - public void Dispose() - { - Dispose(true); + return this.servicePartitionsTaskCompletionSource.Task.Result; } - - #endregion IDisposable Support } } \ No newline at end of file diff --git a/src/DurableTask.ServiceFabric/Service/IHasher.cs b/src/DurableTask.ServiceFabric/Service/IPartitionHashing.cs similarity index 75% rename from src/DurableTask.ServiceFabric/Service/IHasher.cs rename to src/DurableTask.ServiceFabric/Service/IPartitionHashing.cs index b8c54417d..f8353339f 100644 --- a/src/DurableTask.ServiceFabric/Service/IHasher.cs +++ b/src/DurableTask.ServiceFabric/Service/IPartitionHashing.cs @@ -13,19 +13,21 @@ namespace DurableTask.ServiceFabric.Service { + using System.Threading; using System.Threading.Tasks; /// - /// provides long hash code. + /// provides long hash code to identify which partition value belongs to. /// /// Element type - public interface IHasher + public interface IPartitionHashing { /// /// Gets Long hash code. /// /// Input element + /// Token to inform when a task is cancelled. /// 64 bit hash code - Task GenerateHashCode(T value); + Task GeneratePartitionHashCode(T value, CancellationToken cancellationToken); } } diff --git a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs index 693c16e95..0e8400eb7 100644 --- a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs +++ b/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs @@ -14,10 +14,11 @@ namespace DurableTask.ServiceFabric.Service { using System; - using System.Diagnostics; using System.Threading; using System.Threading.Tasks; + using DurableTask.ServiceFabric.Tracing; + using Microsoft.Owin.Hosting; using Microsoft.ServiceFabric.Services.Communication.Runtime; @@ -49,19 +50,17 @@ public OwinCommunicationListener(IOwinAppBuilder owinAppBuilder) /// public Task OpenAsync(CancellationToken cancellationToken) { - Trace.WriteLine("Initialize"); var listeningAddress = this.owinAppBuilder.GetListeningAddress(); - Trace.WriteLine(String.Format("Opening on {0}", listeningAddress)); + ServiceFabricProviderEventSource.Tracing.TraceMessage(nameof(OwinCommunicationListener), $"Opening on {listeningAddress}"); try { - Trace.WriteLine(String.Format("Starting web server on {0}", listeningAddress)); this.serverHandle = WebApp.Start(listeningAddress, appBuilder => this.owinAppBuilder.Startup(appBuilder)); return Task.FromResult(listeningAddress); } catch (Exception ex) { - Trace.WriteLine(ex); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{ex.Message} {ex.StackTrace} {ex.InnerException}"); this.StopWebServer(); throw; } @@ -74,7 +73,7 @@ public Task OpenAsync(CancellationToken cancellationToken) /// public Task CloseAsync(CancellationToken cancellationToken) { - Trace.WriteLine("Close"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(nameof(OwinCommunicationListener), "Listener is closing"); this.StopWebServer(); return Task.CompletedTask; } @@ -84,7 +83,7 @@ public Task CloseAsync(CancellationToken cancellationToken) /// public void Abort() { - Trace.WriteLine("Abort"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(nameof(OwinCommunicationListener), "Listener is aborting"); this.StopWebServer(); } diff --git a/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs b/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs new file mode 100644 index 000000000..9b0cb70cc --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System.Net; + using System.Net.Http; + using System.Web.Http.ExceptionHandling; + using System.Web.Http.Results; + + using DurableTask.ServiceFabric.Tracing; + + internal class ProxyServiceExceptionHandler : ExceptionHandler + { + public override void Handle(ExceptionHandlerContext context) + { + ServiceFabricProviderEventSource.Tracing.LogProxyServiceError(context.Request.Method.ToString(), + context.Request.RequestUri.AbsolutePath, + context.Exception); + + HttpResponseMessage response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, context.Exception); + context.Result = new ResponseMessageResult(response); + } + } +} diff --git a/src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs b/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionsLogger.cs similarity index 73% rename from src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs rename to src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionsLogger.cs index 2f5d4a97a..bd009c8ce 100644 --- a/src/DurableTask.ServiceFabric/Service/ApplicationExceptionsLogger.cs +++ b/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionsLogger.cs @@ -22,13 +22,13 @@ namespace DurableTask.ServiceFabric.Service /// /// Traces application exceptions. /// - public class ApplicationExceptionsLogger : IExceptionLogger + public class ProxyServiceExceptionsLogger : ExceptionLogger { /// - public Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) + public async override Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) { - ProviderEventSource.Tracing.ServiceRequestFailed(context.Request.RequestUri.AbsolutePath, context.Exception.ToString()); - return Task.CompletedTask; + ServiceFabricProviderEventSource.Tracing.LogProxyServiceError(context.Request.Method.ToString(), context.Request.RequestUri.AbsolutePath, context.Exception); + await base.LogAsync(context, cancellationToken); } } } diff --git a/src/DurableTask.ServiceFabric/Service/Startup.cs b/src/DurableTask.ServiceFabric/Service/Startup.cs index 954f806a1..0fb37b81d 100644 --- a/src/DurableTask.ServiceFabric/Service/Startup.cs +++ b/src/DurableTask.ServiceFabric/Service/Startup.cs @@ -13,6 +13,7 @@ namespace DurableTask.ServiceFabric.Service { + using System; using System.Net; using System.Web.Http; using System.Web.Http.ExceptionHandling; @@ -29,8 +30,8 @@ class Startup : IOwinAppBuilder public Startup(string listeningAddress, FabricOrchestrationProvider fabricOrchestrationProvider) { - this.listeningAddress = listeningAddress; - this.fabricOrchestrationProvider = fabricOrchestrationProvider; + this.listeningAddress = listeningAddress ?? throw new ArgumentNullException(nameof(listeningAddress)); + this.fabricOrchestrationProvider = fabricOrchestrationProvider ?? throw new ArgumentNullException(nameof(fabricOrchestrationProvider)); } public string GetListeningAddress() @@ -43,7 +44,8 @@ private ServiceProvider GenerateServiceProvider() var services = new ServiceCollection(); services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); services.AddTransient(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); return serviceProvider; } diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs index 536760bec..71c7af5e2 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs @@ -65,7 +65,7 @@ protected override IEnumerable CreateServiceReplicaListe { yield return new ServiceReplicaListener(context => { - var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint"); + var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("DtfxServiceEndpoint"); int port = serviceEndpoint.Port; string ipAddress = context.NodeContext.IPAddressOrFQDN; @@ -76,11 +76,20 @@ protected override IEnumerable CreateServiceReplicaListe ipAddress = ipv4Address.ToString(); #endif + EnsureFabricOrchestrationProvider(); string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/", ipAddress, port); return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); }); } + private void EnsureFabricOrchestrationProvider() + { + if (this.fabricOrchestrationProvider == null) + { + this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); + } + } + /// /// This is the main entry point for your service replica. /// This method executes when this replica of your service becomes primary and has write status. @@ -88,10 +97,7 @@ protected override IEnumerable CreateServiceReplicaListe /// Canceled when Service Fabric needs to shut down this service replica. protected override async Task RunAsync(CancellationToken cancellationToken) { - if (this.fabricOrchestrationProvider == null) - { - this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - } + EnsureFabricOrchestrationProvider(); this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); @@ -101,7 +107,10 @@ protected override async Task RunAsync(CancellationToken cancellationToken) this.worker.TaskActivityDispatcher.IncludeDetails = true; - await Task.Delay(Timeout.Infinite, cancellationToken); + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000); + } } /// @@ -114,13 +123,13 @@ protected override async Task RunAsync(CancellationToken cancellationToken) /// protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - ProviderEventSource.Tracing.FabricServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + ServiceFabricProviderEventSource.Tracing.FabricServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) { await ShutdownAsync(); } this.currentRole = newRole; - ProviderEventSource.Tracing.FabricServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); + ServiceFabricProviderEventSource.Tracing.FabricServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); } /// @@ -130,7 +139,7 @@ protected override async Task OnChangeRoleAsync(ReplicaRole newRole, Cancellatio /// A Task that represents outstanding operation. protected override async Task OnCloseAsync(CancellationToken cancellationToken) { - ProviderEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); + ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); await ShutdownAsync(); } @@ -140,18 +149,18 @@ async Task ShutdownAsync() { if (this.worker != null) { - ProviderEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); await this.worker.StopAsync(isForced: true); this.worker.Dispose(); + this.worker = null; this.fabricOrchestrationProvider.Dispose(); this.fabricOrchestrationProvider = null; - this.worker = null; - ProviderEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); } } catch (Exception e) { - ProviderEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); + ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); throw; } } diff --git a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs index 3bba5a94a..75d407c4c 100644 --- a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs +++ b/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs @@ -116,7 +116,7 @@ protected Task> GetValueAsync(TKey key) return new Message(key, result.Value); } var errorMessage = $"Internal Server Error: Did not find an item in reliable dictionary while having the item key {key} in memory"; - ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } }, uniqueActionIdentifier: $"Key = {key}, Action = MessageProviderBase.GetValueAsync, StoreName : {this.storeName}"); @@ -183,7 +183,7 @@ protected Task LogMetrics() count = await this.Store.GetCountAsync(tx); } - ProviderEventSource.Tracing.LogStoreCount(this.storeName, count); + ServiceFabricProviderEventSource.Tracing.LogStoreCount(this.storeName, count); }, initialDelay: metricsInterval, delayOnSuccess: metricsInterval, delayOnException: metricsInterval, actionName: $"Log Store Count of {this.storeName}", token: this.CancellationToken); } } diff --git a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs index dd0f53768..722bbd40b 100644 --- a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs @@ -50,7 +50,7 @@ await this.EnumerateItems(kvp => var timerEvent = kvp.Value?.TaskMessage?.Event as TimerFiredEvent; if (timerEvent == null) { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Seeing a non timer event in scheduled messages while filling the pending items collection in role start"); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Seeing a non timer event in scheduled messages while filling the pending items collection in role start"); } else { @@ -62,7 +62,7 @@ await this.EnumerateItems(kvp => { if (this.inMemorySet.Count > 0) { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Before we set the In memory set from the builder, there are items in it which should not happen."); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(ScheduledMessageProvider)}.{nameof(StartAsync)} : Before we set the In memory set from the builder, there are items in it which should not happen."); } this.inMemorySet = builder.ToImmutableSortedSet(TimerFiredEventComparer.Instance); } @@ -163,7 +163,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => } catch (Exception e) { - ProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob($"{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}", e.ToString()); + ServiceFabricProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob($"{nameof(ScheduledMessageProvider)}.{nameof(ProcessScheduledMessages)}", e.ToString()); await Task.Delay(TimeSpan.FromMilliseconds(100)); } } diff --git a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs index 4282268da..a453d017f 100644 --- a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs +++ b/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs @@ -102,20 +102,20 @@ public async Task AcceptSessionAsync(TimeSpan receiveTimeout) { if (this.lockedSessions.TryUpdate(returnInstanceId, newValue: LockState.Locked, comparisonValue: LockState.InFetchQueue)) { - ProviderEventSource.Tracing.TraceMessage(returnInstanceId, "Session Locked Accepted"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(returnInstanceId, "Session Locked Accepted"); return existingValue.Value; } else { var errorMessage = $"Internal Server Error : Unexpected to dequeue the session {returnInstanceId} which was already locked before"; - ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } } else { var errorMessage = $"Internal Server Error: Did not find the session object in reliable dictionary while having the session {returnInstanceId} in memory"; - ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } } @@ -143,7 +143,7 @@ public async Task>> ReceiveSessionMessagesAs { var sessionMessageProvider = await GetOrAddSessionMessagesInstance(session.SessionId); var messages = await sessionMessageProvider.ReceiveBatchAsync(); - ProviderEventSource.Tracing.TraceMessage(session.SessionId.InstanceId, $"Number of received messages {messages.Count}"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(session.SessionId.InstanceId, $"Number of received messages {messages.Count}"); return messages; } @@ -151,12 +151,12 @@ public async Task CompleteMessages(ITransaction transaction, OrchestrationInstan { if (this.sessionMessageProviders.TryGetValue(instance, out SessionMessagesProvider sessionMessageProvider)) { - ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); await sessionMessageProvider.CompleteBatchAsync(transaction, lockTokens); } else { - ProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); } } @@ -245,11 +245,11 @@ public async Task> GetSessions() public void TryUnlockSession(OrchestrationInstance instance, bool abandon = false, bool isComplete = false) { - ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); if (!this.lockedSessions.TryRemove(instance.InstanceId, out LockState lockState) || lockState == LockState.InFetchQueue) { var errorMessage = $"{nameof(SessionsProvider)}.{nameof(TryUnlockSession)} : Trying to unlock the session {instance.InstanceId} which was not locked."; - ProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } @@ -258,7 +258,7 @@ public void TryUnlockSession(OrchestrationInstance instance, bool abandon = fals this.TryEnqueueSession(instance); } - ProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock End, Abandon = {abandon}, removed lock state = {lockState}"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock End, Abandon = {abandon}, removed lock state = {lockState}"); } public async Task TryAddSession(ITransaction transaction, TaskMessageItem newMessage) @@ -303,7 +303,7 @@ void TryEnqueueSession(string instanceId) { if (this.lockedSessions.TryAdd(instanceId, LockState.InFetchQueue)) { - ProviderEventSource.Tracing.TraceMessage(instanceId, "Session Getting Enqueued"); + ServiceFabricProviderEventSource.Tracing.TraceMessage(instanceId, "Session Getting Enqueued"); this.fetchQueue.Enqueue(instanceId); SetWaiterForNewItems(); } diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs index 94205b9a3..f78f092fc 100644 --- a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs +++ b/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs @@ -39,7 +39,7 @@ static async Task ExecuteWithRetryOnTransient(Func action, RetryPolicy ret { await action(); timer.Stop(); - ProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); + ServiceFabricProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, Result : Success", timer.ElapsedMilliseconds); return; } catch (Exception e) @@ -49,7 +49,7 @@ static async Task ExecuteWithRetryOnTransient(Func action, RetryPolicy ret bool shouldRetry = ExceptionUtilities.IsRetryableFabricException(e); ExceptionUtilities.LogReliableCollectionException(uniqueActionIdentifier, attemptNumber, e, shouldRetry); - ProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); + ServiceFabricProviderEventSource.Tracing.LogMeasurement($"{uniqueActionIdentifier}, Attempt Number : {attemptNumber}, ShouldRetry : {shouldRetry}", timer.ElapsedMilliseconds); if (shouldRetry) { @@ -83,7 +83,7 @@ static async Task ExecuteWithRetryOnTransient(Func ExecuteWithRetryOnTransient(Func - /// The event source which emits ETW events for service fabric based provider functionality. - /// - [EventSource( - Name = "DurableTask-ServiceFabricProvider", - Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C")] - public sealed class ProviderEventSource : EventSource - { - /// - /// Singleton instance of the event source. - /// - public static readonly ProviderEventSource Tracing = new ProviderEventSource(); - - static ProviderEventSource() - { - // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. - // This problem will be fixed in .NET Framework 4.6.2. - Task.Run(() => { }); - } - - private ProviderEventSource() : base() - { - } - - private static class Keywords - { - public const EventKeywords Orchestration = (EventKeywords)0x1L; - public const EventKeywords Activity = (EventKeywords)0x2L; - public const EventKeywords Common = (EventKeywords)0x4L; - public const EventKeywords Warning = (EventKeywords)0x8L; - public const EventKeywords Error = (EventKeywords)0x10L; - public const EventKeywords NetworkAction = (EventKeywords)0x20L; - public const EventKeywords Requests = (EventKeywords)0x40L; - public const EventKeywords ServiceInitialization = (EventKeywords)0x80L; - } - - #region Informational 1-500 - [Event(1, Level = EventLevel.Informational, Message = "Orchestration with instanceId : '{0}' and executionId : '{1}' is Created.", Channel = EventChannel.Operational)] - internal void OrchestrationCreated(string instanceId, string executionId) - { - if (IsEnabled(EventLevel.Informational, Keywords.Orchestration)) - { - WriteEvent(1, instanceId, executionId); - } - } - - [Event(2, Level = EventLevel.Informational, Message = "Orchestration with instanceId : '{0}' and executionId : '{4}' Finished with the status {1} and result {3} in {2} seconds.", Channel = EventChannel.Operational)] - internal void OrchestrationFinished(string instanceId, string terminalStatus, double runningTimeInSeconds, string result, string executionId) - { - if (IsEnabled(EventLevel.Informational, Keywords.Orchestration)) - { - WriteEvent(2, instanceId, terminalStatus, runningTimeInSeconds, result, executionId); - } - } - - [Event(4, Level = EventLevel.Informational, Message = "Current number of entries in store {0} : {1}", Channel = EventChannel.Operational)] - internal void LogStoreCount(string storeName, long count) - { - if (IsEnabled(EventLevel.Informational, Keywords.Common)) - { - WriteEvent(4, storeName, count); - } - } - - [Event(8, Level = EventLevel.Informational, Message = "Time taken for {0} : {1} milli seconds.", Channel = EventChannel.Operational)] - internal void LogTimeTaken(string uniqueActionIdentifier, double elapsedMilliseconds) - { - if (IsEnabled(EventLevel.Informational, Keywords.Common)) - { - WriteEvent(8, uniqueActionIdentifier, elapsedMilliseconds); - } - } - - [Event(9, Level = EventLevel.Informational, Message = "{0} : {1}", Channel = EventChannel.Operational)] - internal void ReliableStateManagement(string operationIdentifier, string operationData) - { - if (IsEnabled(EventLevel.Informational, Keywords.Common)) - { - WriteEvent(9, operationIdentifier, operationData); - } - } - - [Event(10, Level = EventLevel.Informational, Message = "Serving action: {0}", Channel = EventChannel.Operational)] - internal void LogServingNetworkAction(string operation) - { - if (IsEnabled(EventLevel.Informational, Keywords.NetworkAction)) - { - WriteEvent(10, operation); - } - } - #endregion - - #region Warnings 1001-1500 - [Event(1001, Level = EventLevel.Warning, Message = "Exception in the background job {0} : {1}", Channel = EventChannel.Operational)] - internal void ExceptionWhileRunningBackgroundJob(string operationIdentifier, string exception) - { - if (IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) - { - WriteEvent(1001, operationIdentifier, exception); - } - } - - [Event(1002, Level = EventLevel.Warning, Message = "Hint : {0}, AttemptNumber : {1}, Exception: {2}", Channel = EventChannel.Operational)] - internal void RetryableFabricException(string uniqueIdentifier, int attemptNumber, string exception) - { - if (IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) - { - WriteEvent(1002, uniqueIdentifier, attemptNumber, exception); - } - } - #endregion - - #region Errors 1501-2000 - [Event(1501, Level = EventLevel.Error, Message = "We are seeing something that we don't expect to see : {0}", Channel = EventChannel.Operational)] - internal void UnexpectedCodeCondition(string uniqueMessage) - { - if (IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) - { - WriteEvent(1501, uniqueMessage); - } - -#if DEBUG - // This is so that tests fail when this event happens. - throw new Exception(uniqueMessage); -#endif - } - - [Event(1502, Level = EventLevel.Error, Message = "Hint : {0}, Exception: {1}", Channel = EventChannel.Operational)] - internal void ExceptionInReliableCollectionOperations(string uniqueIdentifier, string exception) - { - if (IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) - { - WriteEvent(1502, uniqueIdentifier, exception); - } - } - #endregion - - #region Verbose Events 501-1000 - [Event(501, Level = EventLevel.Verbose, Message = "Trace Message for Session {0} : {1}", Channel = EventChannel.Debug)] - internal void TraceMessage(string instanceId, string message) - { -#if DEBUG - if (IsEnabled(EventLevel.Verbose, Keywords.Common)) - { - WriteEvent(501, instanceId, message); - } -#endif - } - - [Event(502, Level = EventLevel.Verbose, Message = "Time taken for {0} : {1} milli seconds.", Channel = EventChannel.Analytic)] - internal void LogMeasurement(string uniqueActionIdentifier, long elapsedMilliseconds) - { -#if DEBUG - if (IsEnabled(EventLevel.Verbose, Keywords.Common)) - { - WriteEvent(502, uniqueActionIdentifier, elapsedMilliseconds); - } -#endif - } - - [Event(503, Level = EventLevel.Verbose, Message = "Size of {0} : {1} bytes.", Channel = EventChannel.Analytic)] - internal void LogSizeMeasure(string uniqueObjectIdentifier, long sizeInBytes) - { -#if DEBUG - if (IsEnabled(EventLevel.Verbose, Keywords.Common)) - { - WriteEvent(503, uniqueObjectIdentifier, sizeInBytes); - } -#endif - } - #endregion - - #region Proxy Service 2001-3000 - [NonEvent] - internal void ServiceMessage(StatefulService service, string message, params object[] args) - { - if (this.IsEnabled()) - { - string finalMessage = string.Format(message, args); - ServiceMessage( - service.Context.ServiceName.ToString(), - service.Context.ServiceTypeName, - service.Context.ReplicaId, - service.Context.PartitionId, - service.Context.CodePackageActivationContext.ApplicationName, - service.Context.CodePackageActivationContext.ApplicationTypeName, - service.Context.NodeContext.NodeName, - finalMessage); - } - } - - [Event(2001, - Level = EventLevel.Informational, - Message = "Service request '{0}' started", - Opcode = EventOpcode.Start, - Keywords = Keywords.Requests)] - internal void FabricServiceRequestStart(string requestTypeName) - { - WriteEvent(2001, requestTypeName); - } - - [Event(2002, - Level = EventLevel.Informational, - Message = "Service request '{0}' finished", - Opcode = EventOpcode.Stop, - Keywords = Keywords.Requests)] - internal void FabricServiceRequestStop(string requestTypeName) - { - WriteEvent(2002, requestTypeName); - } - - [Event(2003, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)] - internal void ServiceRequestFailed(string requestTypeName, string exception) - { - WriteEvent(2003, exception); - } - - - [Event(2004, Level = EventLevel.Error, Message = "Partition cache building is started", Keywords = Keywords.ServiceInitialization)] - internal void PartitionCacheBuildStart() - { - WriteEvent(2004, "Partition cache building is started"); - } - - [Event(2005, Level = EventLevel.Error, Message = "Partition cache building is completed", Keywords = Keywords.ServiceInitialization)] - internal void PartitionCacheBuildComplete() - { - WriteEvent(2005, "Partition cache building is completed"); - } - - [Event(2006, Level = EventLevel.Informational, Message = "{7}")] - private void ServiceMessage( - string serviceName, - string serviceTypeName, - long replicaOrInstanceId, - Guid partitionId, - string applicationName, - string applicationTypeName, - string nodeName, - string message) - { - WriteEvent(2006, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); - } - #endregion - } -} diff --git a/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs new file mode 100644 index 000000000..bf99280de --- /dev/null +++ b/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs @@ -0,0 +1,408 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tracing +{ + using System; + using System.Diagnostics.Tracing; + using System.Threading.Tasks; + + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// The event source which emits ETW events for service fabric based provider functionality. + /// + [EventSource( + Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C", + Name = "DurableTask-ServiceFabricProvider-V2")] + public sealed class ServiceFabricProviderEventSource : EventSource + { + /// + /// Singleton instance of the event source. + /// + public static readonly ServiceFabricProviderEventSource Tracing = new ServiceFabricProviderEventSource(); + + static ServiceFabricProviderEventSource() + { + // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized. + // This problem will be fixed in .NET Framework 4.6.2. + Task.Run(() => { }); + } + + private ServiceFabricProviderEventSource() : base() + { + } + + /// + /// Event keywords. + /// + public class Keywords + { + /// + /// Orchestration Keyword + /// + public const EventKeywords Orchestration = (EventKeywords)0x1L; + + /// + /// Activity Keyword + /// + public const EventKeywords Activity = (EventKeywords)0x2L; + + /// + /// Common Keyword + /// + public const EventKeywords Common = (EventKeywords)0x4L; + + /// + /// Warning Keyword + /// + public const EventKeywords Warning = (EventKeywords)0x8L; + + /// + /// Error Keyword + /// + public const EventKeywords Error = (EventKeywords)0x10L; + + /// + /// ProxyService Keyword + /// + public const EventKeywords ProxyService = (EventKeywords)0x20L; + + /// + /// Requests Keyword + /// + public const EventKeywords FabricService = (EventKeywords)0x40L; + } + + #region Verbose Events 001-500 + + [Event(1, + Keywords = Keywords.Common, + Level = EventLevel.Verbose, + Message = "Trace Message for Session {0} : {1}")] + internal void TraceMessage(string instanceId, string message) + { +#if DEBUG + if (this.IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + this.WriteEvent(1, instanceId, message); + } +#endif + } + + [Event(2, + Keywords = Keywords.Common, + Level = EventLevel.Verbose, + Message = "Time taken for {0} : {1} milli seconds.")] + internal void LogMeasurement(string uniqueActionIdentifier, long elapsedMilliseconds) + { +#if DEBUG + if (this.IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + this.WriteEvent(2, uniqueActionIdentifier, elapsedMilliseconds); + } +#endif + } + + [Event(3, + Keywords = Keywords.Common, + Level = EventLevel.Verbose, + Message = "Size of {0} : {1} bytes.")] + internal void LogSizeMeasure(string uniqueObjectIdentifier, long sizeInBytes) + { +#if DEBUG + if (this.IsEnabled(EventLevel.Verbose, Keywords.Common)) + { + this.WriteEvent(3, uniqueObjectIdentifier, sizeInBytes); + } +#endif + } + + #endregion + + + #region Informational 501-1000 + + [Event(501, + Keywords = Keywords.Orchestration, + Level = EventLevel.Informational, + Message = "Orchestration with instanceId : '{0}' and executionId : '{1}' is Created.")] + internal void OrchestrationCreated(string instanceId, string executionId) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Orchestration)) + { + this.WriteEvent(501, instanceId, executionId); + } + } + + [Event(502, + Keywords = Keywords.Orchestration, + Level = EventLevel.Informational, + Message = "Orchestration with instanceId : '{0}' and executionId : '{4}' Finished with the status {1} and result {3} in {2} seconds.")] + internal void OrchestrationFinished(string instanceId, string terminalStatus, double runningTimeInSeconds, string result, string executionId) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Orchestration)) + { + this.WriteEvent(502, instanceId, terminalStatus, runningTimeInSeconds, result, executionId); + } + } + + [Event(503, + Keywords = Keywords.Common, + Level = EventLevel.Informational, + Message = "Current number of entries in store {0} : {1}")] + internal void LogStoreCount(string storeName, long count) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Common)) + { + this.WriteEvent(503, storeName, count); + } + } + + [Event(504, + Keywords = Keywords.Common, + Level = EventLevel.Informational, + Message = "Time taken for {0} : {1} milli seconds.")] + internal void LogTimeTaken(string uniqueActionIdentifier, double elapsedMilliseconds) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Common)) + { + this.WriteEvent(504, uniqueActionIdentifier, elapsedMilliseconds); + } + } + + [Event(505, + Keywords = Keywords.Common, + Level = EventLevel.Informational, + Message = "{0} : {1}")] + internal void ReliableStateManagement(string operationIdentifier, string operationData) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Common)) + { + this.WriteEvent(505, operationIdentifier, operationData); + } + } + + [Event(506, + Keywords = Keywords.ProxyService, + Level = EventLevel.Informational, + Message = "Proxy service incoming request {1} with method {0}")] + internal void LogProxyServiceRequest(string method, string uri) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.ProxyService)) + { + this.WriteEvent(506, method, uri); + } + } + + [Event(507, + Keywords = Keywords.ProxyService, + Level = EventLevel.Informational, + Message = "Proxy service responding request {1} with method {0}")] + internal void LogProxyServiceResponse(string method, string uri) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.ProxyService)) + { + this.WriteEvent(507, method, uri); + } + } + + [Event(508, + Keywords = Keywords.FabricService, + Level = EventLevel.Informational, + Message = "Partition cache building is started")] + internal void PartitionCacheBuildStart() + { + if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) + { + this.WriteEvent(508, "Partition cache building is started"); + } + } + + [Event(509, + Keywords = Keywords.FabricService, + Level = EventLevel.Informational, + Message = "Partition cache building is completed")] + internal void PartitionCacheBuildComplete() + { + if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) + { + this.WriteEvent(509, "Partition cache building is completed"); + } + } + + [Event(510, + Keywords = Keywords.Common, + Level = EventLevel.Informational, + Message = "{7}")] + private void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.Common)) + { + this.WriteEvent(510, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); + } + } + + + [Event(511, + Level = EventLevel.Informational, + Message = "Service request '{0}' started", + Opcode = EventOpcode.Start, + Keywords = Keywords.FabricService)] + internal void FabricServiceRequestStart(string requestTypeName) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) + { + this.WriteEvent(511, requestTypeName); + } + } + + [Event(512, + Level = EventLevel.Informational, + Message = "Service request '{0}' finished", + Opcode = EventOpcode.Stop, + Keywords = Keywords.FabricService)] + internal void FabricServiceRequestStop(string requestTypeName) + { + if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) + { + this.WriteEvent(512, requestTypeName); + } + } + + #endregion + + #region Warnings 1001-1500 + [Event(1001, + Keywords = Keywords.Warning, + Level = EventLevel.Warning, + Message = "Exception in the background job {0} : {1}")] + internal void ExceptionWhileRunningBackgroundJob(string operationIdentifier, string exception) + { + if (this.IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) + { + this.WriteEvent(1001, operationIdentifier, exception); + } + } + + [Event(1002, + Level = EventLevel.Warning, + Message = "Hint : {0}, AttemptNumber : {1}, Exception: {2}")] + internal void RetryableFabricException(string uniqueIdentifier, int attemptNumber, string exception) + { + if (this.IsEnabled(EventLevel.Warning, Keywords.Common | Keywords.Warning)) + { + this.WriteEvent(1002, uniqueIdentifier, attemptNumber, exception); + } + } + #endregion + + #region Errors 1501-2000 + + [Event(1501, + Keywords = Keywords.Common | Keywords.Error, + Level = EventLevel.Error, + Message = "We are seeing something that we don't expect to see : {0}")] + internal void UnexpectedCodeCondition(string uniqueMessage) + { + if (this.IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) + { + this.WriteEvent(1501, uniqueMessage); + } + +#if DEBUG + // This is so that tests fail when this event happens. + throw new Exception(uniqueMessage); +#endif + } + + [Event(1502, + Keywords = Keywords.Common | Keywords.Error, + Level = EventLevel.Error, + Message = "Hint : {0}, Exception: {1}")] + internal void ExceptionInReliableCollectionOperations(string uniqueIdentifier, string exception) + { + if (this.IsEnabled(EventLevel.Error, Keywords.Common | Keywords.Error)) + { + this.WriteEvent(1502, uniqueIdentifier, exception); + } + } + + [NonEvent] + internal void LogProxyServiceError(string method, string uri, Exception exception) + { + if (exception == null) + { + LogProxyServiceError(method, uri, "with null exception"); + } + else + { + var message = $"with message {exception.Message}\n stacktrace {exception.StackTrace}\n innerexception {exception.InnerException}"; + LogProxyServiceError(method, uri, message); + } + } + + [Event(1503, + Keywords = Keywords.ProxyService, + Level = EventLevel.Error, + Message = "Proxy service request {1} with method {0} resulted in error {2}")] + internal void LogProxyServiceError(string method, string uri, string exception) + { + if (this.IsEnabled(EventLevel.Error, Keywords.ProxyService)) + { + this.WriteEvent(1503, method, uri, exception); + } + } + + [Event(1504, + Keywords = Keywords.FabricService, + Level = EventLevel.Error, + Message = "Service request '{0}' failed")] + internal void ServiceRequestFailed(string requestTypeName, string exception) + { + if (this.IsEnabled(EventLevel.Error, Keywords.FabricService)) + { + this.WriteEvent(1504, exception); + } + } + #endregion + + #region Non Events + [NonEvent] + internal void ServiceMessage(StatefulService service, string message, params object[] args) + { + if (this.IsEnabled()) + { + string finalMessage = string.Format(message, args); + this.ServiceMessage( + service.Context.ServiceName.ToString(), + service.Context.ServiceTypeName, + service.Context.ReplicaId, + service.Context.PartitionId, + service.Context.CodePackageActivationContext.ApplicationName, + service.Context.CodePackageActivationContext.ApplicationTypeName, + service.Context.NodeContext.NodeName, + finalMessage); + } + } + #endregion + } +} diff --git a/src/DurableTask.ServiceFabric/Utils.cs b/src/DurableTask.ServiceFabric/Utils.cs index 1493c14cd..077347e9e 100644 --- a/src/DurableTask.ServiceFabric/Utils.cs +++ b/src/DurableTask.ServiceFabric/Utils.cs @@ -79,7 +79,7 @@ public static async Task RunBackgroundJob(Func loopAction, TimeSpan initia } catch(Exception e) { - ProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob(actionName, e.ToString()); + ServiceFabricProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob(actionName, e.ToString()); await Task.Delay(delayOnException).ConfigureAwait(false); } } From d7a1e063d026e7aa7d669b8ea8a5f456a5028d13 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Wed, 12 Jun 2019 18:10:44 -0700 Subject: [PATCH 07/12] Fix duplicate orchestration issue. Refactor Event Source. --- .../AssemblySetup.cs | 2 + .../FunctionalTests.cs | 3 + .../StressTests.cs | 2 + .../Utilities.cs | 4 +- .../WaitForOrchestrationTests.cs | 2 + .../OrchestrationInstanceComparerTests.cs | 1 + .../PersistentSessionTests.cs | 2 + .../SerializationPerformanceExperiments.cs | 1 + .../TaskMessageItemTests.cs | 1 + .../TimerFiredEventComparerTests.cs | 1 + .../TestApplication/TestApplication.sfproj | 2 +- src/DurableTask.ServiceFabric/Constants.cs | 1 + src/DurableTask.ServiceFabric/Extensions.cs | 3 +- .../FabricOrchestrationService.cs | 10 +- .../FabricOrchestrationServiceClient.cs | 6 +- .../FabricOrchestrationServiceController.cs | 6 +- .../RemoteOrchestrationServiceClient.cs | 7 +- .../Service/ActivityLoggingMessageHandler.cs | 8 +- .../Service/TaskHubProxyService.cs | 36 +++-- .../ServiceFabricProviderEventSource.cs | 153 ++---------------- .../Tracing/TracingExtensions.cs | 44 +++++ 21 files changed, 123 insertions(+), 172 deletions(-) create mode 100644 src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs index d04dde543..a89cee40b 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs @@ -15,7 +15,9 @@ namespace DurableTask.ServiceFabric.Integration.Tests { using System; using System.IO; + using DurableTask.ServiceFabric.Integration.Tests.DeploymentUtil; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs index 7bf9e270f..04b9214a3 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs @@ -16,9 +16,12 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System; using System.Collections.Generic; using System.Threading.Tasks; + using DurableTask.Core; using DurableTask.Test.Orchestrations.Performance; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestApplication.Common.Orchestrations; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs index 5a71cd441..5b641891b 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs @@ -17,8 +17,10 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; + using DurableTask.Core; using DurableTask.Test.Orchestrations.Performance; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs index bb757945d..f8300ac2e 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs @@ -17,16 +17,18 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Diagnostics; using System.Net.Http; using System.Threading.Tasks; + using DurableTask.Core; using DurableTask.ServiceFabric.Remote; using DurableTask.ServiceFabric.Service; + using Microsoft.VisualStudio.TestTools.UnitTesting; public static class Utilities { public static TaskHubClient CreateTaskHubClient() { - var partitionProvider = new FabricPartitionEndpointResolver(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new DefaultStringPartitionHashing()); + var partitionProvider = new FabricPartitionEndpointResolver(new Uri(Constants.TestFabricApplicationAddress), new DefaultStringPartitionHashing()); var httpClient = new HttpClient(); return new TaskHubClient(new RemoteOrchestrationServiceClient(partitionProvider)); } diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs index 9e555a56e..4abbbb2cd 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs +++ b/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs @@ -15,8 +15,10 @@ namespace DurableTask.ServiceFabric.Integration.Tests { using System; using System.Threading.Tasks; + using DurableTask.Core; using DurableTask.Test.Orchestrations.Performance; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs b/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs index 66a7b89ef..e788698aa 100644 --- a/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs +++ b/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs @@ -14,6 +14,7 @@ namespace DurableTask.ServiceFabric.Tests { using DurableTask.Core; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs b/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs index 3b7caa45b..e956ad82b 100644 --- a/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs +++ b/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs @@ -16,8 +16,10 @@ namespace DurableTask.ServiceFabric.Tests using System; using System.Collections.Generic; using System.Collections.Immutable; + using DurableTask.Core; using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs index ade1f39dd..9b97b1847 100644 --- a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs +++ b/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs @@ -16,6 +16,7 @@ namespace DurableTask.ServiceFabric.Tests using System; using System.Collections.Generic; using System.Runtime.Serialization; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs b/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs index d071314d7..94693ccd4 100644 --- a/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs +++ b/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs @@ -15,6 +15,7 @@ namespace DurableTask.ServiceFabric.Tests { using DurableTask.Core; using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs index 1c21de39f..bc2fa6c59 100644 --- a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs +++ b/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs @@ -19,6 +19,7 @@ namespace DurableTask.ServiceFabric.Tests using DurableTask.Core; using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj index bdfc8e742..bc65629ae 100644 --- a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj +++ b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj @@ -40,7 +40,7 @@ - + diff --git a/src/DurableTask.ServiceFabric/Constants.cs b/src/DurableTask.ServiceFabric/Constants.cs index f46fccde0..c4559b551 100644 --- a/src/DurableTask.ServiceFabric/Constants.cs +++ b/src/DurableTask.ServiceFabric/Constants.cs @@ -22,5 +22,6 @@ static class Constants internal const string InstanceStoreDictionaryName = CollectionNameUniquenessPrefix + "InstanceStore"; internal const string ExecutionStoreDictionaryName = CollectionNameUniquenessPrefix + "ExecutionIdStore"; internal const string ScheduledMessagesDictionaryName = CollectionNameUniquenessPrefix + "ScheduledMessages"; + internal const string TaskHubProxyServiceName = "DurableTask-TaskHubProxyService"; } } diff --git a/src/DurableTask.ServiceFabric/Extensions.cs b/src/DurableTask.ServiceFabric/Extensions.cs index 3395bc29c..ce3980e55 100644 --- a/src/DurableTask.ServiceFabric/Extensions.cs +++ b/src/DurableTask.ServiceFabric/Extensions.cs @@ -11,13 +11,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- - namespace DurableTask.ServiceFabric { using System; using System.Web; - using DurableTask.ServiceFabric.Exceptions; + using DurableTask.ServiceFabric.Exceptions; /// /// Defines extensions methods. diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs index 580623086..b23191665 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs @@ -359,11 +359,15 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => this.instanceStore.OnOrchestrationCompleted(workItem.OrchestrationRuntimeState.OrchestrationInstance); - ServiceFabricProviderEventSource.Tracing.OrchestrationFinished(workItem.InstanceId, + string message = string.Format("Orchestration with instanceId : '{0}' and executionId : '{1}' Finished with the status {2} and result {3} in {4} seconds.", + workItem.InstanceId, + workItem.OrchestrationRuntimeState.OrchestrationInstance.ExecutionId, workItem.OrchestrationRuntimeState.OrchestrationStatus.ToString(), - (workItem.OrchestrationRuntimeState.CompletedTime - workItem.OrchestrationRuntimeState.CreatedTime).TotalSeconds, workItem.OrchestrationRuntimeState.Output, - workItem.OrchestrationRuntimeState.OrchestrationInstance.ExecutionId); + (workItem.OrchestrationRuntimeState.CompletedTime - workItem.OrchestrationRuntimeState.CreatedTime).TotalSeconds); + ServiceFabricProviderEventSource.Tracing.LogOrchestrationInformation(workItem.InstanceId, + workItem.OrchestrationRuntimeState.OrchestrationInstance.ExecutionId, + message); } public Task AbandonTaskOrchestrationWorkItemAsync(TaskOrchestrationWorkItem workItem) diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs index afcc1c7b9..5e6da06ce 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs @@ -20,6 +20,7 @@ namespace DurableTask.ServiceFabric using System.Threading.Tasks; using DurableTask.Core; + using DurableTask.Core.Exceptions; using DurableTask.Core.History; using DurableTask.Core.Tracking; using DurableTask.ServiceFabric.Stores; @@ -73,12 +74,13 @@ public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage) if (added) { - ServiceFabricProviderEventSource.Tracing.OrchestrationCreated(instance.InstanceId, instance.ExecutionId); + string message = string.Format("Orchestration with instanceId : '{0}' and executionId : '{1}' is Created.", instance.InstanceId, instance.ExecutionId); + ServiceFabricProviderEventSource.Tracing.LogOrchestrationInformation(instance.InstanceId, instance.ExecutionId, message); this.orchestrationProvider.TryEnqueueSession(creationMessage.OrchestrationInstance); } else { - throw new InvalidOperationException($"An orchestration with id '{creationMessage.OrchestrationInstance.InstanceId}' is already running."); + throw new OrchestrationAlreadyExistsException($"An orchestration with id '{creationMessage.OrchestrationInstance.InstanceId}' is already running."); } } diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs index 7460eecc1..d668e5f2d 100644 --- a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs @@ -54,7 +54,7 @@ public async Task CreateTaskOrchestration([FromUri] string or parameters.TaskMessage.OrchestrationInstance.InstanceId.EnsureValidInstanceId(); if (!orchestrationId.Equals(parameters.TaskMessage.OrchestrationInstance.InstanceId)) { - return Conflict(); + return BadRequest($"OrchestrationId from Uri {orchestrationId} doesn't match with the one from body {parameters.TaskMessage.OrchestrationInstance.InstanceId}"); } try { @@ -69,9 +69,9 @@ public async Task CreateTaskOrchestration([FromUri] string or return new OkResult(this); } - catch (OrchestrationAlreadyExistsException exception) + catch (OrchestrationAlreadyExistsException) { - return new ExceptionResult(exception, this); + return Conflict(); } } diff --git a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index bf3665a9c..ad69c03a6 100644 --- a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -254,6 +254,11 @@ private async Task PutJsonAsync(Uri uri, object @object) }; HttpResponseMessage result = await this.HttpClient.PutAsync(uri, @object, mediaFormatter); + if (result.StatusCode == System.Net.HttpStatusCode.Conflict) + { + throw new OrchestrationAlreadyExistsException("Orchestration already exists"); + } + if (!result.IsSuccessStatusCode) { throw new RemoteServiceException("CreateTaskOrchestrationAsync failed", result.StatusCode); @@ -280,7 +285,7 @@ private string GetDefaultEndPoint(string endpoint) { // sample endpoint - {"Endpoints":{"":"http:\/\/10.91.42.35:30001"}} var jObject = JObject.Parse(endpoint); - var defaultEndPoint = jObject["Endpoints"][string.Empty].ToString(); + var defaultEndPoint = jObject["Endpoints"][Constants.TaskHubProxyServiceName].ToString(); return defaultEndPoint; } diff --git a/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs b/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs index 6faef482e..e79d588df 100644 --- a/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs +++ b/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs @@ -27,17 +27,17 @@ protected override async Task SendAsync(HttpRequestMessage { string requestMethod = request.Method.ToString(); string requestUri = request.RequestUri.AbsolutePath; - ServiceFabricProviderEventSource.Tracing.LogProxyServiceRequest(requestMethod, requestUri); + ServiceFabricProviderEventSource.Tracing.LogProxyServiceRequestInformation($"Proxy service incoming request {requestUri} with method {requestMethod}"); HttpResponseMessage response = null; try { response = await base.SendAsync(request, cancellationToken); - ServiceFabricProviderEventSource.Tracing.LogProxyServiceResponse(requestMethod, requestUri); + ServiceFabricProviderEventSource.Tracing.LogProxyServiceRequestInformation($"Proxy service responding request {requestUri} with method {requestMethod}"); } catch (Exception exception) { - ServiceFabricProviderEventSource.Tracing.LogProxyServiceError(requestMethod, requestUri, exception); - response = request.CreateErrorResponse(HttpStatusCode.InternalServerError, exception); + ServiceFabricProviderEventSource.Tracing.LogProxyServiceError(requestUri, requestMethod, exception); + response = request.CreateErrorResponse(HttpStatusCode.InternalServerError, exception.Message); } return response; diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs index 71c7af5e2..1c4667b6e 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs @@ -26,6 +26,7 @@ namespace DurableTask.ServiceFabric.Service using DurableTask.Core; using DurableTask.ServiceFabric; using DurableTask.ServiceFabric.Tracing; + using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; @@ -77,9 +78,9 @@ protected override IEnumerable CreateServiceReplicaListe #endif EnsureFabricOrchestrationProvider(); - string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/", ipAddress, port); + string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/{2}/", ipAddress, port, context.PartitionId); return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); - }); + }, Constants.TaskHubProxyServiceName); } private void EnsureFabricOrchestrationProvider() @@ -97,19 +98,26 @@ private void EnsureFabricOrchestrationProvider() /// Canceled when Service Fabric needs to shut down this service replica. protected override async Task RunAsync(CancellationToken cancellationToken) { - EnsureFabricOrchestrationProvider(); + try + { + EnsureFabricOrchestrationProvider(); - this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); + this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); - await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); + await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); - await this.worker.StartAsync(); + await this.worker.StartAsync(); - this.worker.TaskActivityDispatcher.IncludeDetails = true; + this.worker.TaskActivityDispatcher.IncludeDetails = true; - while (!cancellationToken.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000); + } + } + catch (Exception exception) { - await Task.Delay(1000); + ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); } } @@ -123,13 +131,13 @@ protected override async Task RunAsync(CancellationToken cancellationToken) /// protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - ServiceFabricProviderEventSource.Tracing.FabricServiceRequestStart($"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) { await ShutdownAsync(); } this.currentRole = newRole; - ServiceFabricProviderEventSource.Tracing.FabricServiceRequestStop($"Fabric On Change Role Async, current role = {this.currentRole}"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}"); } /// @@ -139,7 +147,7 @@ protected override async Task OnChangeRoleAsync(ReplicaRole newRole, Cancellatio /// A Task that represents outstanding operation. protected override async Task OnCloseAsync(CancellationToken cancellationToken) { - ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "OnCloseAsync - will shutdown primary if not already done"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "OnCloseAsync - will shutdown primary if not already done"); await ShutdownAsync(); } @@ -149,13 +157,13 @@ async Task ShutdownAsync() { if (this.worker != null) { - ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "Stopping Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "Stopping Taskhub Worker"); await this.worker.StopAsync(isForced: true); this.worker.Dispose(); this.worker = null; this.fabricOrchestrationProvider.Dispose(); this.fabricOrchestrationProvider = null; - ServiceFabricProviderEventSource.Tracing.ServiceMessage(this, "Stopped Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "Stopped Taskhub Worker"); } } catch (Exception e) diff --git a/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs b/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs index bf99280de..19175d006 100644 --- a/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs +++ b/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs @@ -113,47 +113,18 @@ internal void LogMeasurement(string uniqueActionIdentifier, long elapsedMillisec } #endif } - - [Event(3, - Keywords = Keywords.Common, - Level = EventLevel.Verbose, - Message = "Size of {0} : {1} bytes.")] - internal void LogSizeMeasure(string uniqueObjectIdentifier, long sizeInBytes) - { -#if DEBUG - if (this.IsEnabled(EventLevel.Verbose, Keywords.Common)) - { - this.WriteEvent(3, uniqueObjectIdentifier, sizeInBytes); - } -#endif - } - #endregion - #region Informational 501-1000 [Event(501, Keywords = Keywords.Orchestration, - Level = EventLevel.Informational, - Message = "Orchestration with instanceId : '{0}' and executionId : '{1}' is Created.")] - internal void OrchestrationCreated(string instanceId, string executionId) - { - if (this.IsEnabled(EventLevel.Informational, Keywords.Orchestration)) - { - this.WriteEvent(501, instanceId, executionId); - } - } - - [Event(502, - Keywords = Keywords.Orchestration, - Level = EventLevel.Informational, - Message = "Orchestration with instanceId : '{0}' and executionId : '{4}' Finished with the status {1} and result {3} in {2} seconds.")] - internal void OrchestrationFinished(string instanceId, string terminalStatus, double runningTimeInSeconds, string result, string executionId) + Level = EventLevel.Informational)] + internal void LogOrchestrationInformation(string instanceId, string executionId, string message) { if (this.IsEnabled(EventLevel.Informational, Keywords.Orchestration)) { - this.WriteEvent(502, instanceId, terminalStatus, runningTimeInSeconds, result, executionId); + this.WriteEvent(501, instanceId, executionId, message); } } @@ -195,57 +166,20 @@ internal void ReliableStateManagement(string operationIdentifier, string operati [Event(506, Keywords = Keywords.ProxyService, - Level = EventLevel.Informational, - Message = "Proxy service incoming request {1} with method {0}")] - internal void LogProxyServiceRequest(string method, string uri) - { - if (this.IsEnabled(EventLevel.Informational, Keywords.ProxyService)) - { - this.WriteEvent(506, method, uri); - } - } - - [Event(507, - Keywords = Keywords.ProxyService, - Level = EventLevel.Informational, - Message = "Proxy service responding request {1} with method {0}")] - internal void LogProxyServiceResponse(string method, string uri) + Level = EventLevel.Informational)] + internal void LogProxyServiceRequestInformation(string message) { if (this.IsEnabled(EventLevel.Informational, Keywords.ProxyService)) { - this.WriteEvent(507, method, uri); - } - } - - [Event(508, - Keywords = Keywords.FabricService, - Level = EventLevel.Informational, - Message = "Partition cache building is started")] - internal void PartitionCacheBuildStart() - { - if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) - { - this.WriteEvent(508, "Partition cache building is started"); - } - } - - [Event(509, - Keywords = Keywords.FabricService, - Level = EventLevel.Informational, - Message = "Partition cache building is completed")] - internal void PartitionCacheBuildComplete() - { - if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) - { - this.WriteEvent(509, "Partition cache building is completed"); + this.WriteEvent(506, message); } } [Event(510, - Keywords = Keywords.Common, + Keywords = Keywords.FabricService, Level = EventLevel.Informational, Message = "{7}")] - private void ServiceMessage( + internal void LogFabricServiceInformation( string serviceName, string serviceTypeName, long replicaOrInstanceId, @@ -254,40 +188,12 @@ private void ServiceMessage( string applicationTypeName, string nodeName, string message) - { - if (this.IsEnabled(EventLevel.Informational, Keywords.Common)) - { - this.WriteEvent(510, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); - } - } - - - [Event(511, - Level = EventLevel.Informational, - Message = "Service request '{0}' started", - Opcode = EventOpcode.Start, - Keywords = Keywords.FabricService)] - internal void FabricServiceRequestStart(string requestTypeName) - { - if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) - { - this.WriteEvent(511, requestTypeName); - } - } - - [Event(512, - Level = EventLevel.Informational, - Message = "Service request '{0}' finished", - Opcode = EventOpcode.Stop, - Keywords = Keywords.FabricService)] - internal void FabricServiceRequestStop(string requestTypeName) { if (this.IsEnabled(EventLevel.Informational, Keywords.FabricService)) { - this.WriteEvent(512, requestTypeName); + this.WriteEvent(510, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); } } - #endregion #region Warnings 1001-1500 @@ -346,29 +252,14 @@ internal void ExceptionInReliableCollectionOperations(string uniqueIdentifier, s } } - [NonEvent] - internal void LogProxyServiceError(string method, string uri, Exception exception) - { - if (exception == null) - { - LogProxyServiceError(method, uri, "with null exception"); - } - else - { - var message = $"with message {exception.Message}\n stacktrace {exception.StackTrace}\n innerexception {exception.InnerException}"; - LogProxyServiceError(method, uri, message); - } - } - [Event(1503, Keywords = Keywords.ProxyService, - Level = EventLevel.Error, - Message = "Proxy service request {1} with method {0} resulted in error {2}")] - internal void LogProxyServiceError(string method, string uri, string exception) + Level = EventLevel.Error)] + internal void LogProxyServiceError(string message) { if (this.IsEnabled(EventLevel.Error, Keywords.ProxyService)) { - this.WriteEvent(1503, method, uri, exception); + this.WriteEvent(1503, message); } } @@ -384,25 +275,5 @@ internal void ServiceRequestFailed(string requestTypeName, string exception) } } #endregion - - #region Non Events - [NonEvent] - internal void ServiceMessage(StatefulService service, string message, params object[] args) - { - if (this.IsEnabled()) - { - string finalMessage = string.Format(message, args); - this.ServiceMessage( - service.Context.ServiceName.ToString(), - service.Context.ServiceTypeName, - service.Context.ReplicaId, - service.Context.PartitionId, - service.Context.CodePackageActivationContext.ApplicationName, - service.Context.CodePackageActivationContext.ApplicationTypeName, - service.Context.NodeContext.NodeName, - finalMessage); - } - } - #endregion } } diff --git a/src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs b/src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs new file mode 100644 index 000000000..62a6c9643 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Tracing +{ + using System; + + using Microsoft.ServiceFabric.Services.Runtime; + + internal static class TracingExtensions + { + internal static void LogFabricServiceInformation(this ServiceFabricProviderEventSource eventSource, StatefulService service, string message, params object[] args) + { + string formattedMessage = string.Format(message, args); + eventSource.LogFabricServiceInformation( + service.Context.ServiceName.ToString(), + service.Context.ServiceTypeName, + service.Context.ReplicaId, + service.Context.PartitionId, + service.Context.CodePackageActivationContext.ApplicationName, + service.Context.CodePackageActivationContext.ApplicationTypeName, + service.Context.NodeContext.NodeName, + formattedMessage); + } + + internal static void LogProxyServiceError(this ServiceFabricProviderEventSource eventSource, string requestUri, string requestMethod, Exception exception) + { + string exceptionDetails = $"Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}, InnerException: {exception.InnerException}"; + string logMessage = $"Proxy service request {requestUri} with method {requestMethod} resulted in error. Exception Details - {exceptionDetails}"; + + eventSource.LogProxyServiceError(logMessage); + } + } +} From 8e9566f3bbb5172771eac3a9924792be999dd531 Mon Sep 17 00:00:00 2001 From: Kamesh T <36271852+kamesh-msft@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:55:34 -0700 Subject: [PATCH 08/12] =?UTF-8?q?Introduce=20new=20interface=20`IServiceLi?= =?UTF-8?q?stener`=20to=20represent=20each=20listener=E2=80=A6=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce new interface `IServiceListener` to represent each listener in StatefulService. This allows us to develop each listener separately. Migrate current codebase to consume the same. --- .../Program.cs | 3 +- src/DurableTask.ServiceFabric/Constants.cs | 1 + .../Service/IServiceListener.cs | 62 ++++++++ ...roxyService.cs => TaskHubProxyListener.cs} | 140 +++++++++--------- .../Service/TaskHubStatefulService.cs | 125 ++++++++++++++++ 5 files changed, 256 insertions(+), 75 deletions(-) create mode 100644 src/DurableTask.ServiceFabric/Service/IServiceListener.cs rename src/DurableTask.ServiceFabric/Service/{TaskHubProxyService.cs => TaskHubProxyListener.cs} (72%) create mode 100644 src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index 153764392..b1be212d5 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -34,7 +34,8 @@ private static void Main() // an instance of the class is created in this host process. ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => { - return new TaskHubProxyService(context, new TestOrchestrationsProvider()); + var listener = new TaskHubProxyListener(context, new TestOrchestrationsProvider()); + return new TaskHubStatefulService(context, new[] { listener }); }).GetAwaiter().GetResult(); // Prevents this host process from terminating so services keep running. diff --git a/src/DurableTask.ServiceFabric/Constants.cs b/src/DurableTask.ServiceFabric/Constants.cs index c4559b551..46d70ad4f 100644 --- a/src/DurableTask.ServiceFabric/Constants.cs +++ b/src/DurableTask.ServiceFabric/Constants.cs @@ -23,5 +23,6 @@ static class Constants internal const string ExecutionStoreDictionaryName = CollectionNameUniquenessPrefix + "ExecutionIdStore"; internal const string ScheduledMessagesDictionaryName = CollectionNameUniquenessPrefix + "ScheduledMessages"; internal const string TaskHubProxyServiceName = "DurableTask-TaskHubProxyService"; + internal const string TaskHubProxyListenerEndpointName = "DtfxServiceEndpoint"; } } diff --git a/src/DurableTask.ServiceFabric/Service/IServiceListener.cs b/src/DurableTask.ServiceFabric/Service/IServiceListener.cs new file mode 100644 index 000000000..3127c8420 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/IServiceListener.cs @@ -0,0 +1,62 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System.Fabric; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// Represents a single listener for + /// + public interface IServiceListener + { + /// + /// Initialize required resources for service listener. + /// + /// An instance of which is creating this listener. + void Initialize(StatefulService statefulService); + + /// + /// Creates an instance of . + /// + /// instance of + ServiceReplicaListener CreateServiceReplicaListener(); + + /// + /// This method is invoked when the service is about to start. + /// + /// Cancellation token to monitor for cancellation requests. + /// + Task OnRunAsync(CancellationToken cancellationToken); + + /// + /// This method is called when role of the replica is changing. + /// + /// New for this service replica. + /// Cancellation token to monitor for cancellation requests. + /// + Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken); + + /// + /// This method is called as the final step of closing the service gracefully. + /// + /// Cancellation token to monitor for cancellation requests. + /// + Task OnCloseAsync(CancellationToken cancellationToken); + } +} diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs similarity index 72% rename from src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs rename to src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs index 1c4667b6e..b43042747 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubProxyService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs @@ -14,7 +14,6 @@ namespace DurableTask.ServiceFabric.Service { using System; - using System.Collections.Generic; using System.Fabric; using System.Globalization; using System.Linq; @@ -33,54 +32,24 @@ namespace DurableTask.ServiceFabric.Service /// /// An instance of this class is created for each service replica by the Service Fabric runtime. /// - public sealed class TaskHubProxyService : StatefulService + public sealed class TaskHubProxyListener : IServiceListener { - readonly FabricOrchestrationProviderFactory fabricProviderFactory; readonly IOrchestrationsProvider orchestrationsProvider; + + FabricOrchestrationProviderFactory fabricProviderFactory; FabricOrchestrationProvider fabricOrchestrationProvider; TaskHubWorker worker; ReplicaRole currentRole; + StatefulService statefulService; /// - /// Creates instance of + /// Creates instance of /// /// stateful service context /// Orchestrations provider - public TaskHubProxyService(StatefulServiceContext context, IOrchestrationsProvider orchestrationsProvider) : base(context) + public TaskHubProxyListener(StatefulServiceContext context, IOrchestrationsProvider orchestrationsProvider) { this.orchestrationsProvider = orchestrationsProvider ?? throw new ArgumentNullException(nameof(orchestrationsProvider)); - - var providerSettings = this.orchestrationsProvider.GetFabricOrchestrationProviderSettings(); - this.fabricProviderFactory = new FabricOrchestrationProviderFactory(this.StateManager, providerSettings); - this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - } - - /// - /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. - /// - /// - /// For more information on service communication, see http://aka.ms/servicefabricservicecommunication - /// - /// A collection of listeners. - protected override IEnumerable CreateServiceReplicaListeners() - { - yield return new ServiceReplicaListener(context => - { - var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint("DtfxServiceEndpoint"); - int port = serviceEndpoint.Port; - - string ipAddress = context.NodeContext.IPAddressOrFQDN; -#if DEBUG - IPHostEntry entry = Dns.GetHostEntry(ipAddress); - IPAddress ipv4Address = entry.AddressList.FirstOrDefault( - address => (address.AddressFamily == AddressFamily.InterNetwork) && (!IPAddress.IsLoopback(address))); - ipAddress = ipv4Address.ToString(); -#endif - - EnsureFabricOrchestrationProvider(); - string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/{2}/", ipAddress, port, context.PartitionId); - return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); - }, Constants.TaskHubProxyServiceName); } private void EnsureFabricOrchestrationProvider() @@ -91,36 +60,6 @@ private void EnsureFabricOrchestrationProvider() } } - /// - /// This is the main entry point for your service replica. - /// This method executes when this replica of your service becomes primary and has write status. - /// - /// Canceled when Service Fabric needs to shut down this service replica. - protected override async Task RunAsync(CancellationToken cancellationToken) - { - try - { - EnsureFabricOrchestrationProvider(); - - this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); - - await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); - - await this.worker.StartAsync(); - - this.worker.TaskActivityDispatcher.IncludeDetails = true; - - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(1000); - } - } - catch (Exception exception) - { - ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); - } - } - /// /// Handles node's role change. /// @@ -129,15 +68,15 @@ protected override async Task RunAsync(CancellationToken cancellationToken) /// /// A that represents outstanding operation. /// - protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + public async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) { - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, $"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) { await ShutdownAsync(); } this.currentRole = newRole; - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, $"Fabric On Change Role Async, current role = {this.currentRole}"); } /// @@ -145,9 +84,9 @@ protected override async Task OnChangeRoleAsync(ReplicaRole newRole, Cancellatio /// /// Cancellation token to monitor for cancellation requests. /// A Task that represents outstanding operation. - protected override async Task OnCloseAsync(CancellationToken cancellationToken) + public async Task OnCloseAsync(CancellationToken cancellationToken) { - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "OnCloseAsync - will shutdown primary if not already done"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "OnCloseAsync - will shutdown primary if not already done"); await ShutdownAsync(); } @@ -157,13 +96,13 @@ async Task ShutdownAsync() { if (this.worker != null) { - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "Stopping Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopping Taskhub Worker"); await this.worker.StopAsync(isForced: true); this.worker.Dispose(); this.worker = null; this.fabricOrchestrationProvider.Dispose(); this.fabricOrchestrationProvider = null; - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "Stopped Taskhub Worker"); + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopped Taskhub Worker"); } } catch (Exception e) @@ -172,5 +111,58 @@ async Task ShutdownAsync() throw; } } + + /// + public ServiceReplicaListener CreateServiceReplicaListener() + { + var providerSettings = this.orchestrationsProvider.GetFabricOrchestrationProviderSettings(); + this.fabricProviderFactory = new FabricOrchestrationProviderFactory(statefulService.StateManager, providerSettings); + this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); + + return new ServiceReplicaListener(context => + { + var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint(Constants.TaskHubProxyListenerEndpointName); + int port = serviceEndpoint.Port; + + string ipAddress = context.NodeContext.IPAddressOrFQDN; +#if DEBUG + IPHostEntry entry = Dns.GetHostEntry(ipAddress); + IPAddress ipv4Address = entry.AddressList.FirstOrDefault( + address => (address.AddressFamily == AddressFamily.InterNetwork) && (!IPAddress.IsLoopback(address))); + ipAddress = ipv4Address.ToString(); +#endif + + EnsureFabricOrchestrationProvider(); + string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/{2}/dtfx/", ipAddress, port, context.PartitionId); + return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); + }, Constants.TaskHubProxyServiceName); + } + + /// + public async Task OnRunAsync(CancellationToken cancellationToken) + { + try + { + EnsureFabricOrchestrationProvider(); + + this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); + + await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); + + await this.worker.StartAsync(); + + this.worker.TaskActivityDispatcher.IncludeDetails = true; + } + catch (Exception exception) + { + ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); + } + } + + /// + public void Initialize(StatefulService statefulService) + { + this.statefulService = statefulService; + } } } diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs new file mode 100644 index 000000000..a1213a5b2 --- /dev/null +++ b/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs @@ -0,0 +1,125 @@ +// ---------------------------------------------------------------------------------- +// 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.ServiceFabric.Service +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using DurableTask.ServiceFabric.Tracing; + + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// An instance of this class is created for each service replica by the Service Fabric runtime. + /// + public sealed class TaskHubStatefulService : StatefulService + { + readonly IList serviceListeners; + ReplicaRole currentRole; + + /// + /// Creates instance of + /// + /// stateful service context + /// List of + public TaskHubStatefulService(StatefulServiceContext context, IList serviceListeners) : base(context) + { + this.serviceListeners = serviceListeners ?? throw new ArgumentNullException(nameof(serviceListeners)); + if (this.serviceListeners.Count < 1) + { + throw new ArgumentOutOfRangeException(nameof(serviceListeners), "At least one service should be present"); + } + + foreach (var listener in this.serviceListeners) + { + listener.Initialize(this); + } + } + + /// + /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. + /// + /// + /// For more information on service communication, see http://aka.ms/servicefabricservicecommunication + /// + /// A collection of listeners. + protected override IEnumerable CreateServiceReplicaListeners() + { + return serviceListeners.Select(listener => listener.CreateServiceReplicaListener()); + } + + /// + /// This is the main entry point for your service replica. + /// This method executes when this replica of your service becomes primary and has write status. + /// + /// Canceled when Service Fabric needs to shut down this service replica. + protected override async Task RunAsync(CancellationToken cancellationToken) + { + try + { + foreach(var listener in this.serviceListeners) + { + await listener.OnRunAsync(cancellationToken); + } + + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000); + } + } + catch (Exception exception) + { + ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); + } + } + + /// + /// Handles node's role change. + /// + /// New for this service replica. + /// Cancellation token to monitor for cancellation requests. + /// + /// A that represents outstanding operation. + /// + protected override async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cancellationToken) + { + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); + foreach (var listener in this.serviceListeners) + { + await listener.OnChangeRoleAsync(newRole, cancellationToken); + } + this.currentRole = newRole; + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, $"Fabric On Change Role Async, current role = {this.currentRole}"); + } + + /// + /// Handles OnCloseAsync event, shuts down the service. + /// + /// Cancellation token to monitor for cancellation requests. + /// A Task that represents outstanding operation. + protected override async Task OnCloseAsync(CancellationToken cancellationToken) + { + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this, "OnCloseAsync - will shutdown primary if not already done"); + foreach (var listener in this.serviceListeners) + { + await listener.OnCloseAsync(cancellationToken); + } + } + } +} From aa215ddc92f7a11a1490bb6abe43bda6ef804e6c Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Fri, 28 Jun 2019 18:44:36 -0700 Subject: [PATCH 09/12] Remove IOrchestrationsProvider interface. --- .../TestApplication.Common.csproj | 2 +- .../Program.cs | 5 ++- .../TestOrchestrationsProvider.cs | 9 +---- .../TestApplication/TestApplication.sfproj | 2 +- .../DurableTask.ServiceFabric.csproj | 2 +- .../Service/IOrchestrationsProvider.cs | 39 ------------------- .../Service/IServiceListener.cs | 9 +++++ .../Service/TaskHubProxyListener.cs | 31 +++++++++++---- .../Service/TaskHubStatefulService.cs | 17 +++++++- 9 files changed, 57 insertions(+), 59 deletions(-) delete mode 100644 src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs diff --git a/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj b/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj index cb4801b4d..907f0b0d9 100644 --- a/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj +++ b/Test/TestFabricApplication/TestApplication.Common/TestApplication.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index b1be212d5..701f9f91c 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -16,7 +16,9 @@ namespace TestApplication.StatefulService using System; using System.Diagnostics; using System.Threading; + using DurableTask.ServiceFabric.Service; + using Microsoft.ServiceFabric.Services.Runtime; internal static class Program @@ -34,7 +36,8 @@ private static void Main() // an instance of the class is created in this host process. ServiceRuntime.RegisterServiceAsync("StatefulServiceType", context => { - var listener = new TaskHubProxyListener(context, new TestOrchestrationsProvider()); + var testProvider = new TestOrchestrationsProvider(); + var listener = new TaskHubProxyListener(context, testProvider.GetFabricOrchestrationProviderSettings(), testProvider.RegisterOrchestrations); return new TaskHubStatefulService(context, new[] { listener }); }).GetAwaiter().GetResult(); diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs index 2b59fe3fe..52a6b01f0 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs @@ -27,10 +27,8 @@ namespace TestApplication.StatefulService /// - public class TestOrchestrationsProvider : IOrchestrationsProvider + public class TestOrchestrationsProvider { - #region Implementation of IOrchestrationsProvider interface - /// public FabricOrchestrationProviderSettings GetFabricOrchestrationProviderSettings() { @@ -41,16 +39,13 @@ public FabricOrchestrationProviderSettings GetFabricOrchestrationProviderSetting } /// - public Task RegisterOrchestrationArtifactsAsync(TaskHubWorker taskHubWorker) + public void RegisterOrchestrations(TaskHubWorker taskHubWorker) { taskHubWorker .AddTaskOrchestrations(this.GetOrchestrationTypes().ToArray()) .AddTaskOrchestrations(this.GetTaskOrchestrations().Select(instance => new DefaultObjectCreator(instance.Value)).ToArray()) .AddTaskActivities(GetActivityTypes().ToArray()); - - return Task.CompletedTask; } - #endregion Implementation of IOrchestrationsProvider interface /// private IEnumerable GetActivityTypes() diff --git a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj index bc65629ae..bdfc8e742 100644 --- a/Test/TestFabricApplication/TestApplication/TestApplication.sfproj +++ b/Test/TestFabricApplication/TestApplication/TestApplication.sfproj @@ -40,7 +40,7 @@ - + diff --git a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj index 7b173b721..0bff327f9 100644 --- a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj +++ b/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs b/src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs deleted file mode 100644 index 7ddf78977..000000000 --- a/src/DurableTask.ServiceFabric/Service/IOrchestrationsProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ---------------------------------------------------------------------------------- -// 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.ServiceFabric.Service -{ - using System.Threading.Tasks; - - using DurableTask.Core; - - - /// - /// Orchestrations provider. - /// - public interface IOrchestrationsProvider - { - /// - /// Register orchestration artifacts with taskHubWorker - /// - /// Instance of - /// - Task RegisterOrchestrationArtifactsAsync(TaskHubWorker taskHubWorker); - - /// - /// Gets fabric orchestration provider settings - /// - /// Fabric orchestration provider settings - FabricOrchestrationProviderSettings GetFabricOrchestrationProviderSettings(); - } -} diff --git a/src/DurableTask.ServiceFabric/Service/IServiceListener.cs b/src/DurableTask.ServiceFabric/Service/IServiceListener.cs index 3127c8420..cc55a333a 100644 --- a/src/DurableTask.ServiceFabric/Service/IServiceListener.cs +++ b/src/DurableTask.ServiceFabric/Service/IServiceListener.cs @@ -31,6 +31,15 @@ public interface IServiceListener /// An instance of which is creating this listener. void Initialize(StatefulService statefulService); + /// + /// This method is called when the replica is being opened and it is the final step of opening the service. + /// Override this method to be notified that Open has completed for this replica's internal components. + /// + /// for this service replica. + /// Cancellation token to monitor for cancellation requests. + /// + Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken); + /// /// Creates an instance of . /// diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs b/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs index b43042747..2c8f958ae 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs @@ -29,13 +29,19 @@ namespace DurableTask.ServiceFabric.Service using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; + /// + /// Delegate invoked before starting the worker to register orchestrations. + /// + /// Instance of + public delegate void RegisterOrchestrations(TaskHubWorker taskHubWorker); + /// /// An instance of this class is created for each service replica by the Service Fabric runtime. /// public sealed class TaskHubProxyListener : IServiceListener { - readonly IOrchestrationsProvider orchestrationsProvider; - + readonly RegisterOrchestrations registerOrchestrations; + readonly FabricOrchestrationProviderSettings fabricOrchestrationProviderSettings; FabricOrchestrationProviderFactory fabricProviderFactory; FabricOrchestrationProvider fabricOrchestrationProvider; TaskHubWorker worker; @@ -46,10 +52,14 @@ public sealed class TaskHubProxyListener : IServiceListener /// Creates instance of /// /// stateful service context - /// Orchestrations provider - public TaskHubProxyListener(StatefulServiceContext context, IOrchestrationsProvider orchestrationsProvider) + /// instance of + /// Delegate invoked before starting the worker. + public TaskHubProxyListener(StatefulServiceContext context, + FabricOrchestrationProviderSettings fabricOrchestrationProviderSettings, + RegisterOrchestrations registerOrchestrations) { - this.orchestrationsProvider = orchestrationsProvider ?? throw new ArgumentNullException(nameof(orchestrationsProvider)); + this.fabricOrchestrationProviderSettings = fabricOrchestrationProviderSettings ?? throw new ArgumentNullException(nameof(fabricOrchestrationProviderSettings)); + this.registerOrchestrations = registerOrchestrations ?? throw new ArgumentNullException(nameof(registerOrchestrations)); } private void EnsureFabricOrchestrationProvider() @@ -115,8 +125,7 @@ async Task ShutdownAsync() /// public ServiceReplicaListener CreateServiceReplicaListener() { - var providerSettings = this.orchestrationsProvider.GetFabricOrchestrationProviderSettings(); - this.fabricProviderFactory = new FabricOrchestrationProviderFactory(statefulService.StateManager, providerSettings); + this.fabricProviderFactory = new FabricOrchestrationProviderFactory(statefulService.StateManager, this.fabricOrchestrationProviderSettings); this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); return new ServiceReplicaListener(context => @@ -147,7 +156,7 @@ public async Task OnRunAsync(CancellationToken cancellationToken) this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); - await this.orchestrationsProvider.RegisterOrchestrationArtifactsAsync(this.worker); + this.registerOrchestrations(this.worker); await this.worker.StartAsync(); @@ -164,5 +173,11 @@ public void Initialize(StatefulService statefulService) { this.statefulService = statefulService; } + + /// + public Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } } diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs b/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs index a1213a5b2..260cd9dea 100644 --- a/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs +++ b/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs @@ -52,6 +52,21 @@ public TaskHubStatefulService(StatefulServiceContext context, IList + /// This method is called when the replica is being opened and it is the final step of opening the service. + /// Override this method to be notified that Open has completed for this replica's internal components. + /// + /// for this service replica. + /// Cancellation token to monitor for cancellation requests. + /// + protected override async Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + { + foreach (var listener in this.serviceListeners) + { + await listener.OnOpenAsync(openMode, cancellationToken); + } + } + /// /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests. /// @@ -61,7 +76,7 @@ public TaskHubStatefulService(StatefulServiceContext context, IListA collection of listeners. protected override IEnumerable CreateServiceReplicaListeners() { - return serviceListeners.Select(listener => listener.CreateServiceReplicaListener()); + return serviceListeners.Select(listener => listener.CreateServiceReplicaListener()).Where(listener => listener != null); } /// From fad5ed5060c36ac154c1f784ecb24c4a3f9159fd Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Wed, 10 Jul 2019 12:37:52 -0700 Subject: [PATCH 10/12] Rename to AzureServiceFabric - Folders, CsProj files. --- DurableTask.sln | 6 ++--- .../App.config | 0 .../AssemblySetup.cs | 0 .../Constants.cs | 0 .../DeploymentUtil/ApplicationInfoReader.cs | 0 .../DeploymentUtil/DeploymentHelper.cs | 0 ...ureServiceFabric.Integration.Tests.csproj} | 2 +- .../FunctionalTests.cs | 0 .../StressTests.cs | 0 .../Utilities.cs | 0 .../WaitForOrchestrationTests.cs | 0 .../CountBasedFixedDelayRetryPolicyTests.cs | 0 ...urableTask.AzureServiceFabric.Tests.csproj | 25 +++++++++++++++++++ .../Measure.cs | 0 .../OrchestrationInstanceComparerTests.cs | 0 .../PersistentSessionTests.cs | 0 .../SerializationPerformanceExperiments.cs | 0 .../TaskMessageItemTests.cs | 0 .../TimerFiredEventComparerTests.cs | 0 .../DurableTask.ServiceFabric.Tests.csproj | 25 ------------------- .../TestApplication.StatefulService.csproj | 2 +- .../AssemblyInfo.cs | 2 +- .../AsyncManualResetEvent.cs | 0 .../Constants.cs | 0 .../DurableTask.AzureServiceFabric.csproj} | 0 .../ExceptionUtilities.cs | 0 .../Exceptions/InvalidInstanceIdException.cs | 0 .../Exceptions/RemoteServiceException.cs | 0 .../Extensions.cs | 0 .../FabricOrchestrationInstanceStore.cs | 0 .../FabricOrchestrationProvider.cs | 0 .../FabricOrchestrationProviderFactory.cs | 0 .../FabricOrchestrationProviderSettings.cs | 0 .../FabricOrchestrationService.cs | 0 .../FabricOrchestrationServiceClient.cs | 0 .../FabricOrchestrationServiceController.cs | 0 .../FabricProviderClient.cs | 0 ...FabricOrchestrationServiceInstanceStore.cs | 0 .../IFabricProviderClient.cs | 0 .../IPartitionEndpointResolver.cs | 0 .../Message.cs | 0 .../OrchestrationInstanceComparer.cs | 0 .../OrchestrationStatusExtensions.cs | 0 .../PersistentSession.cs | 0 .../README.md | 0 .../CreateTaskOrchestrationParameters.cs | 0 .../PurgeOrchestrationHistoryParameters.cs | 0 .../RemoteOrchestrationServiceClient.cs | 0 .../Service/ActivityLoggingMessageHandler.cs | 0 .../Service/DefaultDependencyResolver.cs | 0 .../Service/DefaultStringPartitionHashing.cs | 0 .../FabricPartitionEndpointResolver.cs | 0 .../Service/IOwinAppBuilder.cs | 0 .../Service/IPartitionHashing.cs | 0 .../Service/IServiceListener.cs | 0 .../Service/OwinCommunicationListener.cs | 0 .../Service/ProxyServiceExceptionHandler.cs | 0 .../Service/ProxyServiceExceptionsLogger.cs | 0 .../Service/Startup.cs | 0 .../Service/TaskHubProxyListener.cs | 0 .../Service/TaskHubStatefulService.cs | 0 .../Stores/ActivityProvider.cs | 0 .../Stores/MessageProviderBase.cs | 0 .../Stores/ScheduledMessageProvider.cs | 0 .../Stores/SessionMessagesProvider.cs | 0 .../Stores/SessionsProvider.cs | 0 .../CountBasedFixedDelayRetryPolicy.cs | 0 .../TaskHelpers/RetryHelper.cs | 0 .../TaskHelpers/RetryPolicy.cs | 0 .../TaskMessageItem.cs | 0 .../TimerFiredEventComparer.cs | 0 .../ServiceFabricProviderEventSource.cs | 0 .../Tracing/TracingExtensions.cs | 0 .../Utils.cs | 0 74 files changed, 31 insertions(+), 31 deletions(-) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/App.config (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/AssemblySetup.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/Constants.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/DeploymentUtil/ApplicationInfoReader.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/DeploymentUtil/DeploymentHelper.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj => DurableTask.AzureServiceFabric.Integration.Tests/DurableTask.AzureServiceFabric.Integration.Tests.csproj} (92%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/FunctionalTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/StressTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/Utilities.cs (100%) rename Test/{DurableTask.ServiceFabric.Integration.Tests => DurableTask.AzureServiceFabric.Integration.Tests}/WaitForOrchestrationTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/CountBasedFixedDelayRetryPolicyTests.cs (100%) create mode 100644 Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/Measure.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/OrchestrationInstanceComparerTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/PersistentSessionTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/SerializationPerformanceExperiments.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/TaskMessageItemTests.cs (100%) rename Test/{DurableTask.ServiceFabric.Tests => DurableTask.AzureServiceFabric.Tests}/TimerFiredEventComparerTests.cs (100%) delete mode 100644 Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/AssemblyInfo.cs (94%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/AsyncManualResetEvent.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Constants.cs (100%) rename src/{DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj => DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj} (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/ExceptionUtilities.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Exceptions/InvalidInstanceIdException.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Exceptions/RemoteServiceException.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Extensions.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationInstanceStore.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationProvider.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationProviderFactory.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationProviderSettings.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationService.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationServiceClient.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricOrchestrationServiceController.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/FabricProviderClient.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/IFabricOrchestrationServiceInstanceStore.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/IFabricProviderClient.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/IPartitionEndpointResolver.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Message.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/OrchestrationInstanceComparer.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/OrchestrationStatusExtensions.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/PersistentSession.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/README.md (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Remote/CreateTaskOrchestrationParameters.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Remote/PurgeOrchestrationHistoryParameters.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Remote/RemoteOrchestrationServiceClient.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/ActivityLoggingMessageHandler.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/DefaultDependencyResolver.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/DefaultStringPartitionHashing.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/FabricPartitionEndpointResolver.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/IOwinAppBuilder.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/IPartitionHashing.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/IServiceListener.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/OwinCommunicationListener.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/ProxyServiceExceptionHandler.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/ProxyServiceExceptionsLogger.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/Startup.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/TaskHubProxyListener.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Service/TaskHubStatefulService.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Stores/ActivityProvider.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Stores/MessageProviderBase.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Stores/ScheduledMessageProvider.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Stores/SessionMessagesProvider.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Stores/SessionsProvider.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/TaskHelpers/RetryHelper.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/TaskHelpers/RetryPolicy.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/TaskMessageItem.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/TimerFiredEventComparer.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Tracing/ServiceFabricProviderEventSource.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Tracing/TracingExtensions.cs (100%) rename src/{DurableTask.ServiceFabric => DurableTask.AzureServiceFabric}/Utils.cs (100%) diff --git a/DurableTask.sln b/DurableTask.sln index 9ac081198..dd8e8f4d8 100644 --- a/DurableTask.sln +++ b/DurableTask.sln @@ -45,11 +45,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis", "src\Du EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Redis.Tests", "Test\DurableTask.Redis.Tests\DurableTask.Redis.Tests.csproj", "{B1C3B554-A098-47F8-8FA9-C28B5BBF35B1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric", "src\DurableTask.ServiceFabric\DurableTask.ServiceFabric.csproj", "{3C10169F-4ACB-4FF6-A12B-B74569613E81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.AzureServiceFabric", "src\DurableTask.AzureServiceFabric\DurableTask.AzureServiceFabric.csproj", "{3C10169F-4ACB-4FF6-A12B-B74569613E81}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric.Tests", "Test\DurableTask.ServiceFabric.Tests\DurableTask.ServiceFabric.Tests.csproj", "{AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.AzureServiceFabric.Tests", "Test\DurableTask.AzureServiceFabric.Tests\DurableTask.AzureServiceFabric.Tests.csproj", "{AA0BCA9D-03F7-4F33-BBD5-2D99181E1C2A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ServiceFabric.Integration.Tests", "Test\DurableTask.ServiceFabric.Integration.Tests\DurableTask.ServiceFabric.Integration.Tests.csproj", "{8C3D4CA2-4158-4631-BD5E-F5D066962FAF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.AzureServiceFabric.Integration.Tests", "Test\DurableTask.AzureServiceFabric.Integration.Tests\DurableTask.AzureServiceFabric.Integration.Tests.csproj", "{8C3D4CA2-4158-4631-BD5E-F5D066962FAF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFabricApplication", "TestFabricApplication", "{C53918E6-667A-4930-837C-0018C5D6E374}" EndProject diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/App.config b/Test/DurableTask.AzureServiceFabric.Integration.Tests/App.config similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/App.config rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/App.config diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/AssemblySetup.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/Constants.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DurableTask.AzureServiceFabric.Integration.Tests.csproj similarity index 92% rename from Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/DurableTask.AzureServiceFabric.Integration.Tests.csproj index b6ae8a3e0..16ef549e3 100644 --- a/Test/DurableTask.ServiceFabric.Integration.Tests/DurableTask.ServiceFabric.Integration.Tests.csproj +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DurableTask.AzureServiceFabric.Integration.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/FunctionalTests.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/StressTests.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/Utilities.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs diff --git a/Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs rename to Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs rename to Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs diff --git a/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj b/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj new file mode 100644 index 000000000..f42cbef31 --- /dev/null +++ b/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj @@ -0,0 +1,25 @@ + + + + + net461 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Test/DurableTask.ServiceFabric.Tests/Measure.cs b/Test/DurableTask.AzureServiceFabric.Tests/Measure.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/Measure.cs rename to Test/DurableTask.AzureServiceFabric.Tests/Measure.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/OrchestrationInstanceComparerTests.cs rename to Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/PersistentSessionTests.cs rename to Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs b/Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/SerializationPerformanceExperiments.cs rename to Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/TaskMessageItemTests.cs rename to Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs similarity index 100% rename from Test/DurableTask.ServiceFabric.Tests/TimerFiredEventComparerTests.cs rename to Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs diff --git a/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj b/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj deleted file mode 100644 index a02a0631a..000000000 --- a/Test/DurableTask.ServiceFabric.Tests/DurableTask.ServiceFabric.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - net461 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj index 409f87dfe..2c7a894ff 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestApplication.StatefulService.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/DurableTask.ServiceFabric/AssemblyInfo.cs b/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs similarity index 94% rename from src/DurableTask.ServiceFabric/AssemblyInfo.cs rename to src/DurableTask.AzureServiceFabric/AssemblyInfo.cs index d4db855b5..58b440979 100644 --- a/src/DurableTask.ServiceFabric/AssemblyInfo.cs +++ b/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs @@ -17,5 +17,5 @@ [assembly: ComVisible(false)] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Scope = "type", Target = "DurableTask.ServiceFabric.Exceptions.RemoteServiceException")] #if !SIGN_ASSEMBLY -[assembly: InternalsVisibleTo("DurableTask.ServiceFabric.Tests")] +[assembly: InternalsVisibleTo("DurableTask.AzureServiceFabric.Tests")] #endif diff --git a/src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs b/src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs similarity index 100% rename from src/DurableTask.ServiceFabric/AsyncManualResetEvent.cs rename to src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs diff --git a/src/DurableTask.ServiceFabric/Constants.cs b/src/DurableTask.AzureServiceFabric/Constants.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Constants.cs rename to src/DurableTask.AzureServiceFabric/Constants.cs diff --git a/src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj b/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj similarity index 100% rename from src/DurableTask.ServiceFabric/DurableTask.ServiceFabric.csproj rename to src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj diff --git a/src/DurableTask.ServiceFabric/ExceptionUtilities.cs b/src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs similarity index 100% rename from src/DurableTask.ServiceFabric/ExceptionUtilities.cs rename to src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs diff --git a/src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs b/src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Exceptions/InvalidInstanceIdException.cs rename to src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs diff --git a/src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs b/src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Exceptions/RemoteServiceException.cs rename to src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs diff --git a/src/DurableTask.ServiceFabric/Extensions.cs b/src/DurableTask.AzureServiceFabric/Extensions.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Extensions.cs rename to src/DurableTask.AzureServiceFabric/Extensions.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationInstanceStore.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationProvider.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationProviderFactory.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationProviderSettings.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationService.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationServiceClient.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs diff --git a/src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricOrchestrationServiceController.cs rename to src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs diff --git a/src/DurableTask.ServiceFabric/FabricProviderClient.cs b/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs similarity index 100% rename from src/DurableTask.ServiceFabric/FabricProviderClient.cs rename to src/DurableTask.AzureServiceFabric/FabricProviderClient.cs diff --git a/src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs b/src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs similarity index 100% rename from src/DurableTask.ServiceFabric/IFabricOrchestrationServiceInstanceStore.cs rename to src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs diff --git a/src/DurableTask.ServiceFabric/IFabricProviderClient.cs b/src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs similarity index 100% rename from src/DurableTask.ServiceFabric/IFabricProviderClient.cs rename to src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs diff --git a/src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs b/src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs similarity index 100% rename from src/DurableTask.ServiceFabric/IPartitionEndpointResolver.cs rename to src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs diff --git a/src/DurableTask.ServiceFabric/Message.cs b/src/DurableTask.AzureServiceFabric/Message.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Message.cs rename to src/DurableTask.AzureServiceFabric/Message.cs diff --git a/src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs b/src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs similarity index 100% rename from src/DurableTask.ServiceFabric/OrchestrationInstanceComparer.cs rename to src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs diff --git a/src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.cs b/src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs similarity index 100% rename from src/DurableTask.ServiceFabric/OrchestrationStatusExtensions.cs rename to src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs diff --git a/src/DurableTask.ServiceFabric/PersistentSession.cs b/src/DurableTask.AzureServiceFabric/PersistentSession.cs similarity index 100% rename from src/DurableTask.ServiceFabric/PersistentSession.cs rename to src/DurableTask.AzureServiceFabric/PersistentSession.cs diff --git a/src/DurableTask.ServiceFabric/README.md b/src/DurableTask.AzureServiceFabric/README.md similarity index 100% rename from src/DurableTask.ServiceFabric/README.md rename to src/DurableTask.AzureServiceFabric/README.md diff --git a/src/DurableTask.ServiceFabric/Remote/CreateTaskOrchestrationParameters.cs b/src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Remote/CreateTaskOrchestrationParameters.cs rename to src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs diff --git a/src/DurableTask.ServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs b/src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs rename to src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs diff --git a/src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Remote/RemoteOrchestrationServiceClient.cs rename to src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs diff --git a/src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs b/src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/ActivityLoggingMessageHandler.cs rename to src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs diff --git a/src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs b/src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/DefaultDependencyResolver.cs rename to src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs diff --git a/src/DurableTask.ServiceFabric/Service/DefaultStringPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/DefaultStringPartitionHashing.cs rename to src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs diff --git a/src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/FabricPartitionEndpointResolver.cs rename to src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs diff --git a/src/DurableTask.ServiceFabric/Service/IOwinAppBuilder.cs b/src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/IOwinAppBuilder.cs rename to src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs diff --git a/src/DurableTask.ServiceFabric/Service/IPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/IPartitionHashing.cs rename to src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs diff --git a/src/DurableTask.ServiceFabric/Service/IServiceListener.cs b/src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/IServiceListener.cs rename to src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs diff --git a/src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs b/src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/OwinCommunicationListener.cs rename to src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs diff --git a/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionHandler.cs rename to src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs diff --git a/src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionsLogger.cs b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/ProxyServiceExceptionsLogger.cs rename to src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs diff --git a/src/DurableTask.ServiceFabric/Service/Startup.cs b/src/DurableTask.AzureServiceFabric/Service/Startup.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/Startup.cs rename to src/DurableTask.AzureServiceFabric/Service/Startup.cs diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/TaskHubProxyListener.cs rename to src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs diff --git a/src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Service/TaskHubStatefulService.cs rename to src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs diff --git a/src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Stores/ActivityProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs diff --git a/src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Stores/MessageProviderBase.cs rename to src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs diff --git a/src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Stores/ScheduledMessageProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs diff --git a/src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Stores/SessionMessagesProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs diff --git a/src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Stores/SessionsProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs similarity index 100% rename from src/DurableTask.ServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs rename to src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs similarity index 100% rename from src/DurableTask.ServiceFabric/TaskHelpers/RetryHelper.cs rename to src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs diff --git a/src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs similarity index 100% rename from src/DurableTask.ServiceFabric/TaskHelpers/RetryPolicy.cs rename to src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs diff --git a/src/DurableTask.ServiceFabric/TaskMessageItem.cs b/src/DurableTask.AzureServiceFabric/TaskMessageItem.cs similarity index 100% rename from src/DurableTask.ServiceFabric/TaskMessageItem.cs rename to src/DurableTask.AzureServiceFabric/TaskMessageItem.cs diff --git a/src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs b/src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs similarity index 100% rename from src/DurableTask.ServiceFabric/TimerFiredEventComparer.cs rename to src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs diff --git a/src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs b/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Tracing/ServiceFabricProviderEventSource.cs rename to src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs diff --git a/src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs b/src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Tracing/TracingExtensions.cs rename to src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs diff --git a/src/DurableTask.ServiceFabric/Utils.cs b/src/DurableTask.AzureServiceFabric/Utils.cs similarity index 100% rename from src/DurableTask.ServiceFabric/Utils.cs rename to src/DurableTask.AzureServiceFabric/Utils.cs From 417b751e47ffb48ee9726ad921b669560b640327 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Wed, 10 Jul 2019 12:45:43 -0700 Subject: [PATCH 11/12] Rename Namespaces. --- .../AssemblySetup.cs | 4 ++-- .../Constants.cs | 2 +- .../DeploymentUtil/ApplicationInfoReader.cs | 2 +- .../DeploymentUtil/DeploymentHelper.cs | 2 +- .../FunctionalTests.cs | 2 +- .../StressTests.cs | 2 +- .../Utilities.cs | 6 +++--- .../WaitForOrchestrationTests.cs | 2 +- .../CountBasedFixedDelayRetryPolicyTests.cs | 4 ++-- Test/DurableTask.AzureServiceFabric.Tests/Measure.cs | 2 +- .../OrchestrationInstanceComparerTests.cs | 2 +- .../PersistentSessionTests.cs | 2 +- .../SerializationPerformanceExperiments.cs | 2 +- .../TaskMessageItemTests.cs | 2 +- .../TimerFiredEventComparerTests.cs | 2 +- .../TestApplication.StatefulService/Program.cs | 2 +- .../TestOrchestrationsProvider.cs | 4 ++-- src/DurableTask.AzureServiceFabric/AssemblyInfo.cs | 2 +- .../AsyncManualResetEvent.cs | 2 +- src/DurableTask.AzureServiceFabric/Constants.cs | 2 +- .../DurableTask.AzureServiceFabric.csproj | 2 +- src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs | 4 ++-- .../Exceptions/InvalidInstanceIdException.cs | 2 +- .../Exceptions/RemoteServiceException.cs | 4 ++-- src/DurableTask.AzureServiceFabric/Extensions.cs | 4 ++-- .../FabricOrchestrationInstanceStore.cs | 6 +++--- .../FabricOrchestrationProvider.cs | 4 ++-- .../FabricOrchestrationProviderFactory.cs | 2 +- .../FabricOrchestrationProviderSettings.cs | 2 +- .../FabricOrchestrationService.cs | 8 ++++---- .../FabricOrchestrationServiceClient.cs | 8 ++++---- .../FabricOrchestrationServiceController.cs | 6 +++--- .../FabricProviderClient.cs | 4 ++-- .../IFabricOrchestrationServiceInstanceStore.cs | 2 +- .../IFabricProviderClient.cs | 6 +++--- .../IPartitionEndpointResolver.cs | 2 +- src/DurableTask.AzureServiceFabric/Message.cs | 2 +- .../OrchestrationInstanceComparer.cs | 2 +- .../OrchestrationStatusExtensions.cs | 2 +- src/DurableTask.AzureServiceFabric/PersistentSession.cs | 2 +- .../Remote/CreateTaskOrchestrationParameters.cs | 2 +- .../Remote/PurgeOrchestrationHistoryParameters.cs | 2 +- .../Remote/RemoteOrchestrationServiceClient.cs | 6 +++--- .../Service/ActivityLoggingMessageHandler.cs | 4 ++-- .../Service/DefaultDependencyResolver.cs | 2 +- .../Service/DefaultStringPartitionHashing.cs | 2 +- .../Service/FabricPartitionEndpointResolver.cs | 2 +- .../Service/IOwinAppBuilder.cs | 2 +- .../Service/IPartitionHashing.cs | 2 +- .../Service/IServiceListener.cs | 2 +- .../Service/OwinCommunicationListener.cs | 8 ++++---- .../Service/ProxyServiceExceptionHandler.cs | 4 ++-- .../Service/ProxyServiceExceptionsLogger.cs | 4 ++-- src/DurableTask.AzureServiceFabric/Service/Startup.cs | 4 ++-- .../Service/TaskHubProxyListener.cs | 6 +++--- .../Service/TaskHubStatefulService.cs | 4 ++-- .../Stores/ActivityProvider.cs | 2 +- .../Stores/MessageProviderBase.cs | 6 +++--- .../Stores/ScheduledMessageProvider.cs | 6 +++--- .../Stores/SessionMessagesProvider.cs | 2 +- .../Stores/SessionsProvider.cs | 6 +++--- .../TaskHelpers/CountBasedFixedDelayRetryPolicy.cs | 2 +- .../TaskHelpers/RetryHelper.cs | 4 ++-- .../TaskHelpers/RetryPolicy.cs | 2 +- src/DurableTask.AzureServiceFabric/TaskMessageItem.cs | 2 +- .../TimerFiredEventComparer.cs | 2 +- .../Tracing/ServiceFabricProviderEventSource.cs | 2 +- .../Tracing/TracingExtensions.cs | 2 +- src/DurableTask.AzureServiceFabric/Utils.cs | 4 ++-- 69 files changed, 111 insertions(+), 111 deletions(-) diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs index a89cee40b..f4cc21b60 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/AssemblySetup.cs @@ -11,12 +11,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { using System; using System.IO; - using DurableTask.ServiceFabric.Integration.Tests.DeploymentUtil; + using DurableTask.AzureServiceFabric.Integration.Tests.DeploymentUtil; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs index 1d3711b65..db8bc9f36 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Constants.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { static class Constants { diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs index 9de909184..de135a0ed 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/ApplicationInfoReader.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests.DeploymentUtil +namespace DurableTask.AzureServiceFabric.Integration.Tests.DeploymentUtil { using System.Collections.Specialized; using System.IO; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs index c3b436a72..80578da76 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/DeploymentUtil/DeploymentHelper.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests.DeploymentUtil +namespace DurableTask.AzureServiceFabric.Integration.Tests.DeploymentUtil { using System; using System.Fabric; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs index 04b9214a3..c14dd5932 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/FunctionalTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { using System; using System.Collections.Generic; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs index 5b641891b..1f7b3f154 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/StressTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { using System; using System.Collections.Generic; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs index f8300ac2e..4ab7d09f3 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/Utilities.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { using System; using System.Diagnostics; @@ -19,8 +19,8 @@ namespace DurableTask.ServiceFabric.Integration.Tests using System.Threading.Tasks; using DurableTask.Core; - using DurableTask.ServiceFabric.Remote; - using DurableTask.ServiceFabric.Service; + using DurableTask.AzureServiceFabric.Remote; + using DurableTask.AzureServiceFabric.Service; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs b/Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs index 4abbbb2cd..0d0eee392 100644 --- a/Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Integration.Tests/WaitForOrchestrationTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Integration.Tests +namespace DurableTask.AzureServiceFabric.Integration.Tests { using System; using System.Threading.Tasks; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs index 493816418..a03a3bcbc 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/CountBasedFixedDelayRetryPolicyTests.cs @@ -11,11 +11,11 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using System; - using DurableTask.ServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.TaskHelpers; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] diff --git a/Test/DurableTask.AzureServiceFabric.Tests/Measure.cs b/Test/DurableTask.AzureServiceFabric.Tests/Measure.cs index dda3ed6ed..3fb445f5d 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/Measure.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/Measure.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using System; using System.Diagnostics; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs index e788698aa..2b384275a 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/OrchestrationInstanceComparerTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using DurableTask.Core; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs index e956ad82b..9a9febe35 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/PersistentSessionTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using System; using System.Collections.Generic; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs b/Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs index 9b97b1847..e06be1417 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/SerializationPerformanceExperiments.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using System; using System.Collections.Generic; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs index 94693ccd4..5cd67dcd3 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/TaskMessageItemTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using DurableTask.Core; using DurableTask.Core.History; diff --git a/Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs b/Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs index bc2fa6c59..dd1ff9e77 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs +++ b/Test/DurableTask.AzureServiceFabric.Tests/TimerFiredEventComparerTests.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tests +namespace DurableTask.AzureServiceFabric.Tests { using System; using System.Collections.Immutable; diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs index 701f9f91c..dc2bde054 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/Program.cs @@ -17,7 +17,7 @@ namespace TestApplication.StatefulService using System.Diagnostics; using System.Threading; - using DurableTask.ServiceFabric.Service; + using DurableTask.AzureServiceFabric.Service; using Microsoft.ServiceFabric.Services.Runtime; diff --git a/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs index 52a6b01f0..3fcd93802 100644 --- a/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs +++ b/Test/TestFabricApplication/TestApplication.StatefulService/TestOrchestrationsProvider.cs @@ -19,8 +19,8 @@ namespace TestApplication.StatefulService using System.Threading.Tasks; using DurableTask.Core; - using DurableTask.ServiceFabric; - using DurableTask.ServiceFabric.Service; + using DurableTask.AzureServiceFabric; + using DurableTask.AzureServiceFabric.Service; using DurableTask.Test.Orchestrations.Performance; using TestApplication.Common.Orchestrations; diff --git a/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs b/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs index 58b440979..097afbd40 100644 --- a/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs +++ b/src/DurableTask.AzureServiceFabric/AssemblyInfo.cs @@ -15,7 +15,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Scope = "type", Target = "DurableTask.ServiceFabric.Exceptions.RemoteServiceException")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Scope = "type", Target = "DurableTask.AzureServiceFabric.Exceptions.RemoteServiceException")] #if !SIGN_ASSEMBLY [assembly: InternalsVisibleTo("DurableTask.AzureServiceFabric.Tests")] #endif diff --git a/src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs b/src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs index a26fd0966..17b246fa4 100644 --- a/src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs +++ b/src/DurableTask.AzureServiceFabric/AsyncManualResetEvent.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Threading; diff --git a/src/DurableTask.AzureServiceFabric/Constants.cs b/src/DurableTask.AzureServiceFabric/Constants.cs index 46d70ad4f..18a461730 100644 --- a/src/DurableTask.AzureServiceFabric/Constants.cs +++ b/src/DurableTask.AzureServiceFabric/Constants.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { static class Constants { diff --git a/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj b/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj index 0bff327f9..44c23b627 100644 --- a/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj +++ b/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj @@ -4,7 +4,7 @@ net461;net472 true - Microsoft.Azure.DurableTask.ServiceFabric + Microsoft.Azure.DurableTask.AzureServiceFabric diff --git a/src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs b/src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs index 68370bce2..01368a7f2 100644 --- a/src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs +++ b/src/DurableTask.AzureServiceFabric/ExceptionUtilities.cs @@ -11,12 +11,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Fabric; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; static class ExceptionUtilities { diff --git a/src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs b/src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs index ac73ed6bc..e45d99798 100644 --- a/src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs +++ b/src/DurableTask.AzureServiceFabric/Exceptions/InvalidInstanceIdException.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Exceptions +namespace DurableTask.AzureServiceFabric.Exceptions { using System; diff --git a/src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs b/src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs index 0a4eaa576..4590f6817 100644 --- a/src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs +++ b/src/DurableTask.AzureServiceFabric/Exceptions/RemoteServiceException.cs @@ -11,12 +11,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Exceptions +namespace DurableTask.AzureServiceFabric.Exceptions { using System; using System.Net; - using DurableTask.ServiceFabric.Remote; + using DurableTask.AzureServiceFabric.Remote; /// diff --git a/src/DurableTask.AzureServiceFabric/Extensions.cs b/src/DurableTask.AzureServiceFabric/Extensions.cs index ce3980e55..0c1ec296d 100644 --- a/src/DurableTask.AzureServiceFabric/Extensions.cs +++ b/src/DurableTask.AzureServiceFabric/Extensions.cs @@ -11,12 +11,12 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Web; - using DurableTask.ServiceFabric.Exceptions; + using DurableTask.AzureServiceFabric.Exceptions; /// /// Defines extensions methods. diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs index f1d40f90f..487cc6b8e 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Concurrent; @@ -24,8 +24,8 @@ namespace DurableTask.ServiceFabric using DurableTask.Core; using DurableTask.Core.Tracking; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs index b4b8652b7..c3c1c8d87 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs @@ -11,13 +11,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Threading; using DurableTask.Core; - using DurableTask.ServiceFabric.Stores; + using DurableTask.AzureServiceFabric.Stores; using Microsoft.ServiceFabric.Data; /// diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs index e2f5c4872..301dc448c 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderFactory.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using Microsoft.ServiceFabric.Data; diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs index c2b87677d..9a8f2d37e 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProviderSettings.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using DurableTask.Core; using DurableTask.Core.Settings; diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs index b23191665..4089a7330 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Concurrent; @@ -24,9 +24,9 @@ namespace DurableTask.ServiceFabric using DurableTask.Core; using DurableTask.Core.History; using DurableTask.Core.Tracking; - using DurableTask.ServiceFabric.Stores; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Stores; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; class FabricOrchestrationService : IOrchestrationService diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs index 5e6da06ce..10eaf7955 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Generic; @@ -23,9 +23,9 @@ namespace DurableTask.ServiceFabric using DurableTask.Core.Exceptions; using DurableTask.Core.History; using DurableTask.Core.Tracking; - using DurableTask.ServiceFabric.Stores; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Stores; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs index d668e5f2d..859dcfd91 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceController.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System.Collections.Generic; using System.Threading.Tasks; @@ -20,8 +20,8 @@ namespace DurableTask.ServiceFabric using DurableTask.Core; using DurableTask.Core.Exceptions; - using DurableTask.ServiceFabric.Models; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Models; + using DurableTask.AzureServiceFabric.Tracing; /// /// A Web Api controller that provides TaskHubClient operations. diff --git a/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs b/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs index e7eb78c7a..7b46eb389 100644 --- a/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs +++ b/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Generic; @@ -20,7 +20,7 @@ namespace DurableTask.ServiceFabric using DurableTask.Core; using DurableTask.Core.Serializing; - using DurableTask.ServiceFabric.Stores; + using DurableTask.AzureServiceFabric.Stores; using Microsoft.ServiceFabric.Data; using Newtonsoft.Json; diff --git a/src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs b/src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs index 63bf21062..187e88b2c 100644 --- a/src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs +++ b/src/DurableTask.AzureServiceFabric/IFabricOrchestrationServiceInstanceStore.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Generic; diff --git a/src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs b/src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs index 58803fe8d..717910645 100644 --- a/src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs +++ b/src/DurableTask.AzureServiceFabric/IFabricProviderClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Generic; @@ -35,9 +35,9 @@ internal interface IFabricProviderClient /// /// The /// of the orchestration. - /// Returns serialized runtime state which includes all the history events if the + /// Returns serialized runtime state which includes all the history events if the /// orchestration is running or pending. - /// The API is intended for diagnostics purpose, so current implementation returns + /// The API is intended for diagnostics purpose, so current implementation returns /// a formatted json serialized string which can be very large. /// When the orchestration already completed or was never started. Task GetOrchestrationRuntimeStateAsync(string instanceId); diff --git a/src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs b/src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs index f5cbf05ea..578ba4322 100644 --- a/src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs +++ b/src/DurableTask.AzureServiceFabric/IPartitionEndpointResolver.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System.Collections.Generic; using System.Threading; diff --git a/src/DurableTask.AzureServiceFabric/Message.cs b/src/DurableTask.AzureServiceFabric/Message.cs index acf4b375f..11269dafd 100644 --- a/src/DurableTask.AzureServiceFabric/Message.cs +++ b/src/DurableTask.AzureServiceFabric/Message.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; diff --git a/src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs b/src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs index e83fe4dc8..957ff5418 100644 --- a/src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs +++ b/src/DurableTask.AzureServiceFabric/OrchestrationInstanceComparer.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System.Collections.Generic; using DurableTask.Core; diff --git a/src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs b/src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs index 731557ad8..36745c634 100644 --- a/src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs +++ b/src/DurableTask.AzureServiceFabric/OrchestrationStatusExtensions.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using DurableTask.Core; diff --git a/src/DurableTask.AzureServiceFabric/PersistentSession.cs b/src/DurableTask.AzureServiceFabric/PersistentSession.cs index 2acea3db9..c51600b0b 100644 --- a/src/DurableTask.AzureServiceFabric/PersistentSession.cs +++ b/src/DurableTask.AzureServiceFabric/PersistentSession.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System.Collections.Generic; using System.Collections.Immutable; diff --git a/src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs b/src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs index 507a6a1fb..19320300e 100644 --- a/src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs +++ b/src/DurableTask.AzureServiceFabric/Remote/CreateTaskOrchestrationParameters.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Models +namespace DurableTask.AzureServiceFabric.Models { using DurableTask.Core; diff --git a/src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs b/src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs index af6321b49..b8366c47e 100644 --- a/src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs +++ b/src/DurableTask.AzureServiceFabric/Remote/PurgeOrchestrationHistoryParameters.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Models +namespace DurableTask.AzureServiceFabric.Models { using System; using DurableTask.Core; diff --git a/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index ad69c03a6..79ec74808 100644 --- a/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Remote +namespace DurableTask.AzureServiceFabric.Remote { using System; using System.Collections.Generic; @@ -24,8 +24,8 @@ namespace DurableTask.ServiceFabric.Remote using DurableTask.Core; using DurableTask.Core.Exceptions; - using DurableTask.ServiceFabric.Exceptions; - using DurableTask.ServiceFabric.Models; + using DurableTask.AzureServiceFabric.Exceptions; + using DurableTask.AzureServiceFabric.Models; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs b/src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs index e79d588df..42e3ef8ea 100644 --- a/src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs +++ b/src/DurableTask.AzureServiceFabric/Service/ActivityLoggingMessageHandler.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Net; @@ -19,7 +19,7 @@ namespace DurableTask.ServiceFabric.Service using System.Threading; using System.Threading.Tasks; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; internal class ActivityLoggingMessageHandler : DelegatingHandler { diff --git a/src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs b/src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs index ad799b808..b90c7fbca 100644 --- a/src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs +++ b/src/DurableTask.AzureServiceFabric/Service/DefaultDependencyResolver.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Collections.Generic; diff --git a/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs index 69fddef90..1e81527aa 100644 --- a/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs +++ b/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Security.Cryptography; diff --git a/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs index 43c737f54..524ca1d60 100644 --- a/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Collections.Generic; diff --git a/src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs b/src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs index e60f8932b..46e36a04d 100644 --- a/src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs +++ b/src/DurableTask.AzureServiceFabric/Service/IOwinAppBuilder.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using Owin; diff --git a/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs index f8353339f..1a5faa10e 100644 --- a/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs +++ b/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System.Threading; using System.Threading.Tasks; diff --git a/src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs b/src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs index cc55a333a..7ea42d19a 100644 --- a/src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs +++ b/src/DurableTask.AzureServiceFabric/Service/IServiceListener.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System.Fabric; using System.Threading; diff --git a/src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs b/src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs index 0e8400eb7..87a49193c 100644 --- a/src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs +++ b/src/DurableTask.AzureServiceFabric/Service/OwinCommunicationListener.cs @@ -11,13 +11,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Threading; using System.Threading.Tasks; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.Owin.Hosting; using Microsoft.ServiceFabric.Services.Communication.Runtime; @@ -35,7 +35,7 @@ public class OwinCommunicationListener : ICommunicationListener private IOwinAppBuilder owinAppBuilder; /// - /// + /// /// /// Owin Application builder public OwinCommunicationListener(IOwinAppBuilder owinAppBuilder) @@ -44,7 +44,7 @@ public OwinCommunicationListener(IOwinAppBuilder owinAppBuilder) } /// - /// + /// /// /// /// diff --git a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs index 9b0cb70cc..a40e4c564 100644 --- a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs +++ b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionHandler.cs @@ -11,14 +11,14 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System.Net; using System.Net.Http; using System.Web.Http.ExceptionHandling; using System.Web.Http.Results; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; internal class ProxyServiceExceptionHandler : ExceptionHandler { diff --git a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs index bd009c8ce..9f9ef6c64 100644 --- a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs +++ b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs @@ -11,13 +11,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System.Threading; using System.Threading.Tasks; using System.Web.Http.ExceptionHandling; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; /// /// Traces application exceptions. diff --git a/src/DurableTask.AzureServiceFabric/Service/Startup.cs b/src/DurableTask.AzureServiceFabric/Service/Startup.cs index 0fb37b81d..50c2b9d8e 100644 --- a/src/DurableTask.AzureServiceFabric/Service/Startup.cs +++ b/src/DurableTask.AzureServiceFabric/Service/Startup.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Net; @@ -19,7 +19,7 @@ namespace DurableTask.ServiceFabric.Service using System.Web.Http.ExceptionHandling; using DurableTask.Core; - using DurableTask.ServiceFabric; + using DurableTask.AzureServiceFabric; using Microsoft.Extensions.DependencyInjection; using Owin; diff --git a/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs index 2c8f958ae..0ff3d92db 100644 --- a/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs +++ b/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Fabric; @@ -23,8 +23,8 @@ namespace DurableTask.ServiceFabric.Service using System.Threading.Tasks; using DurableTask.Core; - using DurableTask.ServiceFabric; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; diff --git a/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs index 260cd9dea..f69fb782b 100644 --- a/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs +++ b/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Service +namespace DurableTask.AzureServiceFabric.Service { using System; using System.Collections.Generic; @@ -20,7 +20,7 @@ namespace DurableTask.ServiceFabric.Service using System.Threading; using System.Threading.Tasks; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; diff --git a/src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs index f12ae9341..1146af9d1 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/ActivityProvider.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Stores +namespace DurableTask.AzureServiceFabric.Stores { using System; using System.Collections.Concurrent; diff --git a/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs index 75d407c4c..35dd2d84f 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs @@ -11,15 +11,15 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Stores +namespace DurableTask.AzureServiceFabric.Stores { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; diff --git a/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs index 722bbd40b..7712a835d 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Stores +namespace DurableTask.AzureServiceFabric.Stores { using System; using System.Collections.Generic; @@ -22,8 +22,8 @@ namespace DurableTask.ServiceFabric.Stores using DurableTask.Core; using DurableTask.Core.History; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; class ScheduledMessageProvider : MessageProviderBase diff --git a/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs index 2ee889579..241e57230 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Stores +namespace DurableTask.AzureServiceFabric.Stores { using System; using System.Collections.Generic; diff --git a/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs index a453d017f..4f2f1f648 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Stores +namespace DurableTask.AzureServiceFabric.Stores { using System; using System.Collections.Concurrent; @@ -22,8 +22,8 @@ namespace DurableTask.ServiceFabric.Stores using System.Threading.Tasks; using DurableTask.Core; - using DurableTask.ServiceFabric.TaskHelpers; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.TaskHelpers; + using DurableTask.AzureServiceFabric.Tracing; using Microsoft.ServiceFabric.Data; /// diff --git a/src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs index 7195ecdc0..f2d5d03da 100644 --- a/src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs +++ b/src/DurableTask.AzureServiceFabric/TaskHelpers/CountBasedFixedDelayRetryPolicy.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.TaskHelpers +namespace DurableTask.AzureServiceFabric.TaskHelpers { using System; diff --git a/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs index f78f092fc..94768d0e9 100644 --- a/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs +++ b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryHelper.cs @@ -11,13 +11,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.TaskHelpers +namespace DurableTask.AzureServiceFabric.TaskHelpers { using System; using System.Diagnostics; using System.Threading.Tasks; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; static class RetryHelper { diff --git a/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs index 7b3f46e7c..afc4fc7bf 100644 --- a/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs +++ b/src/DurableTask.AzureServiceFabric/TaskHelpers/RetryPolicy.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.TaskHelpers +namespace DurableTask.AzureServiceFabric.TaskHelpers { using System; diff --git a/src/DurableTask.AzureServiceFabric/TaskMessageItem.cs b/src/DurableTask.AzureServiceFabric/TaskMessageItem.cs index 94d19fa15..bce653a53 100644 --- a/src/DurableTask.AzureServiceFabric/TaskMessageItem.cs +++ b/src/DurableTask.AzureServiceFabric/TaskMessageItem.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Runtime.Serialization; diff --git a/src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs b/src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs index d3dfb55fe..25cc09395 100644 --- a/src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs +++ b/src/DurableTask.AzureServiceFabric/TimerFiredEventComparer.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Collections.Generic; diff --git a/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs b/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs index 19175d006..7e812d780 100644 --- a/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs +++ b/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tracing +namespace DurableTask.AzureServiceFabric.Tracing { using System; using System.Diagnostics.Tracing; diff --git a/src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs b/src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs index 62a6c9643..f89dcb0d5 100644 --- a/src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs +++ b/src/DurableTask.AzureServiceFabric/Tracing/TracingExtensions.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric.Tracing +namespace DurableTask.AzureServiceFabric.Tracing { using System; diff --git a/src/DurableTask.AzureServiceFabric/Utils.cs b/src/DurableTask.AzureServiceFabric/Utils.cs index 077347e9e..a6bffa79c 100644 --- a/src/DurableTask.AzureServiceFabric/Utils.cs +++ b/src/DurableTask.AzureServiceFabric/Utils.cs @@ -11,7 +11,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -namespace DurableTask.ServiceFabric +namespace DurableTask.AzureServiceFabric { using System; using System.Diagnostics; @@ -19,7 +19,7 @@ namespace DurableTask.ServiceFabric using System.Threading.Tasks; using DurableTask.Core; - using DurableTask.ServiceFabric.Tracing; + using DurableTask.AzureServiceFabric.Tracing; internal static class Utils { From 2818fb09cb9265ca9766cb862cfa8e8ff2380bb6 Mon Sep 17 00:00:00 2001 From: Kamesh Tanneru Date: Wed, 10 Jul 2019 16:53:43 -0700 Subject: [PATCH 12/12] Add list of contributors. Update version. Address comments. --- ...urableTask.AzureServiceFabric.Tests.csproj | 2 +- .../ApplicationManifest.xml | 4 +- .../ApplicationParameters/Cloud.xml | 4 +- .../ApplicationParameters/Local.5Node.xml | 2 +- .../DurableTask.AzureServiceFabric.csproj | 2 +- .../FabricOrchestrationInstanceStore.cs | 31 ++++--- .../FabricOrchestrationProvider.cs | 8 +- .../FabricOrchestrationService.cs | 4 +- .../FabricOrchestrationServiceClient.cs | 4 +- .../FabricProviderClient.cs | 4 +- src/DurableTask.AzureServiceFabric/README.md | 8 +- .../RemoteOrchestrationServiceClient.cs | 2 +- .../Service/DefaultStringPartitionHashing.cs | 2 +- .../FabricPartitionEndpointResolver.cs | 2 +- .../Service/IPartitionHashing.cs | 2 +- ...gger.cs => ProxyServiceExceptionLogger.cs} | 2 +- .../Service/Startup.cs | 2 +- .../Service/TaskHubProxyListener.cs | 91 ++++++++++--------- .../Service/TaskHubStatefulService.cs | 1 + .../Stores/MessageProviderBase.cs | 38 +++++--- .../Stores/ScheduledMessageProvider.cs | 10 +- ...sProvider.cs => SessionMessageProvider.cs} | 4 +- ...SessionsProvider.cs => SessionProvider.cs} | 27 +++--- .../ServiceFabricProviderEventSource.cs | 2 +- tools/DurableTask.props | 6 +- 25 files changed, 146 insertions(+), 118 deletions(-) rename src/DurableTask.AzureServiceFabric/Service/{ProxyServiceExceptionsLogger.cs => ProxyServiceExceptionLogger.cs} (95%) rename src/DurableTask.AzureServiceFabric/Stores/{SessionMessagesProvider.cs => SessionMessageProvider.cs} (89%) rename src/DurableTask.AzureServiceFabric/Stores/{SessionsProvider.cs => SessionProvider.cs} (93%) diff --git a/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj b/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj index f42cbef31..2f45a1744 100644 --- a/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj +++ b/Test/DurableTask.AzureServiceFabric.Tests/DurableTask.AzureServiceFabric.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml index ffa737502..c123c17c9 100644 --- a/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml +++ b/Test/TestFabricApplication/TestApplication/ApplicationPackageRoot/ApplicationManifest.xml @@ -1,8 +1,8 @@  - - + + diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml index 683ffaa5b..c47814698 100644 --- a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Cloud.xml @@ -1,8 +1,8 @@  - - + + \ No newline at end of file diff --git a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml index 362d313b5..c47814698 100644 --- a/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml +++ b/Test/TestFabricApplication/TestApplication/ApplicationParameters/Local.5Node.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj b/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj index 44c23b627..8dde46a47 100644 --- a/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj +++ b/src/DurableTask.AzureServiceFabric/DurableTask.AzureServiceFabric.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs index 487cc6b8e..90768c598 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationInstanceStore.cs @@ -65,8 +65,8 @@ public Task InitializeStoreAsync(bool recreate) public async Task StartAsync() { - await EnsureStoreInitialized(); - var nowait = CleanupOldDictionaries(); + await EnsureStoreInitializedAsync(); + var nowait = CleanupOldDictionariesAsync(); } public async Task DeleteStoreAsync() @@ -77,7 +77,7 @@ public async Task DeleteStoreAsync() public async Task WriteEntitiesAsync(ITransaction transaction, IEnumerable entities) { - await EnsureStoreInitialized(); + await EnsureStoreInitializedAsync(); foreach (var entity in entities) { var state = entity as OrchestrationStateInstanceEntity; @@ -132,7 +132,7 @@ public async Task> GetOrchestrationState throw new NotImplementedException("Querying for state across all executions for an orchestration is not supported, only the latest execution can be queried"); } - await EnsureStoreInitialized(); + await EnsureStoreInitializedAsync(); string latestExecutionId = (await GetExecutionIds(instanceId))?.Last(); @@ -169,7 +169,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => public async Task GetOrchestrationStateAsync(string instanceId, string executionId) { - await EnsureStoreInitialized(); + await EnsureStoreInitializedAsync(); var queryKey = this.GetKey(instanceId, executionId); var result = await RetryHelper.ExecuteWithRetryOnTransient(async () => { @@ -237,9 +237,16 @@ public Task> GetOrchestrationHi // The method will cleanup state for every orchestration happening in the hour time window of given time, // for example, if the given time is 9.35, it will delete state for all orchestrations that // are completed between 9.00 to 9.59!!! - public Task PurgeOrchestrationHistoryEventsAsync(DateTime threshholdHourlyDateTimeUtc) + public async Task PurgeOrchestrationHistoryEventsAsync(DateTime threshholdHourlyDateTimeUtc) { - return this.stateManager.RemoveAsync(GetDictionaryKeyFromTimeFormat(threshholdHourlyDateTimeUtc)); + try + { + await this.stateManager.RemoveAsync(GetDictionaryKeyFromTimeFormat(threshholdHourlyDateTimeUtc)); + } + catch (Exception e) + { + ServiceFabricProviderEventSource.Tracing.LogProxyServiceError($"PurgeOrchestrationHistoryEventsAsync failed with exception {e.ToString()}"); + } } string GetKey(string instanceId, string executionId) @@ -262,7 +269,7 @@ string GetInstanceStoreBackupDictionaryKey(DateTime time, string formatString) return InstanceStoreCollectionNamePrefix + time.ToString(formatString); } - Task CleanupDayOldDictionaries() + Task CleanupDayOldDictionariesAsync() { return Utils.RunBackgroundJob(async () => { @@ -272,10 +279,10 @@ Task CleanupDayOldDictionaries() { await this.stateManager.RemoveAsync($"{purgeTime}{i:D2}"); } - }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(12), delayOnException: TimeSpan.FromHours(1), actionName: $"{nameof(CleanupDayOldDictionaries)}", token: this.cancellationToken); + }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(12), delayOnException: TimeSpan.FromHours(1), actionName: $"{nameof(CleanupDayOldDictionariesAsync)}", token: this.cancellationToken); } - Task CleanupOldDictionaries() + Task CleanupOldDictionariesAsync() { return Utils.RunBackgroundJob(async () => { @@ -318,10 +325,10 @@ Task CleanupOldDictionaries() }); ServiceFabricProviderEventSource.Tracing.LogTimeTaken($"Deleting reliable state {storeName}", deleteTime.TotalMilliseconds); } - }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(1), delayOnException: TimeSpan.FromMinutes(10), actionName: $"{nameof(CleanupOldDictionaries)}", token: this.cancellationToken); + }, initialDelay: TimeSpan.FromMinutes(5), delayOnSuccess: TimeSpan.FromHours(1), delayOnException: TimeSpan.FromMinutes(10), actionName: $"{nameof(CleanupOldDictionariesAsync)}", token: this.cancellationToken); } - async Task EnsureStoreInitialized() + async Task EnsureStoreInitializedAsync() { if (this.instanceStore == null) { diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs index c3c1c8d87..ec8fc06b4 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationProvider.cs @@ -40,11 +40,11 @@ public sealed class FabricOrchestrationProvider : IDisposable internal FabricOrchestrationProvider(IReliableStateManager stateManager, FabricOrchestrationProviderSettings settings) { this.cancellationTokenSource = new CancellationTokenSource(); - var sessionsProvider = new SessionsProvider(stateManager, cancellationTokenSource.Token); + var sessionProvider = new SessionProvider(stateManager, cancellationTokenSource.Token); var instanceStore = new FabricOrchestrationInstanceStore(stateManager, cancellationTokenSource.Token); - this.orchestrationService = new FabricOrchestrationService(stateManager, sessionsProvider, instanceStore, settings, cancellationTokenSource); - this.orchestrationClient = new FabricOrchestrationServiceClient(stateManager, sessionsProvider, instanceStore); - this.fabricProviderClient = new FabricProviderClient(stateManager, sessionsProvider); + this.orchestrationService = new FabricOrchestrationService(stateManager, sessionProvider, instanceStore, settings, cancellationTokenSource); + this.orchestrationClient = new FabricOrchestrationServiceClient(stateManager, sessionProvider, instanceStore); + this.fabricProviderClient = new FabricProviderClient(stateManager, sessionProvider); } /// diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs index 4089a7330..fe9ef78ba 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationService.cs @@ -33,7 +33,7 @@ class FabricOrchestrationService : IOrchestrationService { readonly IReliableStateManager stateManager; readonly IFabricOrchestrationServiceInstanceStore instanceStore; - readonly SessionsProvider orchestrationProvider; + readonly SessionProvider orchestrationProvider; readonly ActivityProvider activitiesProvider; readonly ScheduledMessageProvider scheduledMessagesProvider; readonly FabricOrchestrationProviderSettings settings; @@ -42,7 +42,7 @@ class FabricOrchestrationService : IOrchestrationService ConcurrentDictionary sessionInfos = new ConcurrentDictionary(); public FabricOrchestrationService(IReliableStateManager stateManager, - SessionsProvider orchestrationProvider, + SessionProvider orchestrationProvider, IFabricOrchestrationServiceInstanceStore instanceStore, FabricOrchestrationProviderSettings settings, CancellationTokenSource cancellationTokenSource) diff --git a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs index 10eaf7955..9cc365b35 100644 --- a/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs +++ b/src/DurableTask.AzureServiceFabric/FabricOrchestrationServiceClient.cs @@ -35,9 +35,9 @@ class FabricOrchestrationServiceClient : IOrchestrationServiceClient { readonly IReliableStateManager stateManager; readonly IFabricOrchestrationServiceInstanceStore instanceStore; - readonly SessionsProvider orchestrationProvider; + readonly SessionProvider orchestrationProvider; - public FabricOrchestrationServiceClient(IReliableStateManager stateManager, SessionsProvider orchestrationProvider, IFabricOrchestrationServiceInstanceStore instanceStore) + public FabricOrchestrationServiceClient(IReliableStateManager stateManager, SessionProvider orchestrationProvider, IFabricOrchestrationServiceInstanceStore instanceStore) { this.stateManager = stateManager ?? throw new ArgumentNullException(nameof(stateManager)); this.orchestrationProvider = orchestrationProvider ?? throw new ArgumentNullException(nameof(orchestrationProvider)); diff --git a/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs b/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs index 7b46eb389..b5d4ce5a6 100644 --- a/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs +++ b/src/DurableTask.AzureServiceFabric/FabricProviderClient.cs @@ -27,10 +27,10 @@ namespace DurableTask.AzureServiceFabric internal class FabricProviderClient : IFabricProviderClient { readonly IReliableStateManager stateManager; - readonly SessionsProvider orchestrationProvider; + readonly SessionProvider orchestrationProvider; readonly JsonDataConverter FormattingConverter = new JsonDataConverter(new JsonSerializerSettings() { Formatting = Formatting.Indented }); - public FabricProviderClient(IReliableStateManager stateManager, SessionsProvider orchestrationProvider) + public FabricProviderClient(IReliableStateManager stateManager, SessionProvider orchestrationProvider) { this.stateManager = stateManager ?? throw new ArgumentNullException(nameof(stateManager)); this.orchestrationProvider = orchestrationProvider ?? throw new ArgumentNullException(nameof(orchestrationProvider)); diff --git a/src/DurableTask.AzureServiceFabric/README.md b/src/DurableTask.AzureServiceFabric/README.md index 6f0932ad4..948190b1f 100644 --- a/src/DurableTask.AzureServiceFabric/README.md +++ b/src/DurableTask.AzureServiceFabric/README.md @@ -41,4 +41,10 @@ There are pre-requisites for setting up this project for developing or making co * In visual studio, the default test architecture should be set to x64 for detecting tests properly. * There are 2 manual steps that must be done before running tests: * A 5 node local cluster must be setup using Service Fabric Local Cluster Manager (installed by SDK). - * After the solution is built, the "TestFabricApplication" must be packaged using the context menu. (The packaged application will be deployed to local cluster as part of test setup) \ No newline at end of file + * After the solution is built, the "TestFabricApplication" must be packaged using the context menu. (The packaged application will be deployed to local cluster as part of test setup) + +## Main Contributors + +* Pradeep Kadubandi +* Shankar Reddy Sama +* Kameswara Rao Tanneru diff --git a/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs b/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs index 79ec74808..7f2a883d5 100644 --- a/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs +++ b/src/DurableTask.AzureServiceFabric/Remote/RemoteOrchestrationServiceClient.cs @@ -166,7 +166,7 @@ public async Task PurgeOrchestrationHistoryAsync(DateTime thresholdDateTimeUtc, foreach (var endpoint in await this.GetAllEndpointsAsync(CancellationToken.None)) { var uri = $"{endpoint.ToString()}/{GetHistoryFragment()}"; - var task = this.HttpClient.PostAsJsonAsync(uri, new { thresholdDateTimeUtc, timeRangeFilterType }); + var task = this.HttpClient.PostAsJsonAsync(uri, new PurgeOrchestrationHistoryParameters { ThresholdDateTimeUtc = thresholdDateTimeUtc, TimeRangeFilterType = timeRangeFilterType }); allTasks.Add(task); } diff --git a/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs index 1e81527aa..b73804482 100644 --- a/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs +++ b/src/DurableTask.AzureServiceFabric/Service/DefaultStringPartitionHashing.cs @@ -25,7 +25,7 @@ namespace DurableTask.AzureServiceFabric.Service public class DefaultStringPartitionHashing : IPartitionHashing { /// - public Task GeneratePartitionHashCode(string value, CancellationToken cancellationToken) + public Task GeneratePartitionHashCodeAsync(string value, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); long hashCode = 0; diff --git a/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs b/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs index 524ca1d60..787513573 100644 --- a/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs +++ b/src/DurableTask.AzureServiceFabric/Service/FabricPartitionEndpointResolver.cs @@ -49,7 +49,7 @@ public FabricPartitionEndpointResolver(Uri serviceUri, IPartitionHashing public async Task GetPartitionEndPointAsync(string instanceId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - long hash = await this.instanceIdHasher.GeneratePartitionHashCode(instanceId, cancellationToken); + long hash = await this.instanceIdHasher.GeneratePartitionHashCodeAsync(instanceId, cancellationToken); ResolvedServicePartition partition = await this.partitionResolver.ResolveAsync(this.serviceUri, new ServicePartitionKey(hash), cancellationToken); return partition.GetEndpoint().Address; } diff --git a/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs b/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs index 1a5faa10e..560c619be 100644 --- a/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs +++ b/src/DurableTask.AzureServiceFabric/Service/IPartitionHashing.cs @@ -28,6 +28,6 @@ public interface IPartitionHashing /// Input element /// Token to inform when a task is cancelled. /// 64 bit hash code - Task GeneratePartitionHashCode(T value, CancellationToken cancellationToken); + Task GeneratePartitionHashCodeAsync(T value, CancellationToken cancellationToken); } } diff --git a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionLogger.cs similarity index 95% rename from src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs rename to src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionLogger.cs index 9f9ef6c64..f0c039155 100644 --- a/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionsLogger.cs +++ b/src/DurableTask.AzureServiceFabric/Service/ProxyServiceExceptionLogger.cs @@ -22,7 +22,7 @@ namespace DurableTask.AzureServiceFabric.Service /// /// Traces application exceptions. /// - public class ProxyServiceExceptionsLogger : ExceptionLogger + public class ProxyServiceExceptionLogger : ExceptionLogger { /// public async override Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) diff --git a/src/DurableTask.AzureServiceFabric/Service/Startup.cs b/src/DurableTask.AzureServiceFabric/Service/Startup.cs index 50c2b9d8e..20ade0f5c 100644 --- a/src/DurableTask.AzureServiceFabric/Service/Startup.cs +++ b/src/DurableTask.AzureServiceFabric/Service/Startup.cs @@ -44,7 +44,7 @@ private ServiceProvider GenerateServiceProvider() var services = new ServiceCollection(); services.AddSingleton(this.fabricOrchestrationProvider.OrchestrationServiceClient); services.AddTransient(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); return serviceProvider; diff --git a/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs index 0ff3d92db..07e0871b8 100644 --- a/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs +++ b/src/DurableTask.AzureServiceFabric/Service/TaskHubProxyListener.cs @@ -37,6 +37,7 @@ namespace DurableTask.AzureServiceFabric.Service /// /// An instance of this class is created for each service replica by the Service Fabric runtime. + /// Listening on HTTP port will expose security risk, use this listener at your own discretation. /// public sealed class TaskHubProxyListener : IServiceListener { @@ -62,14 +63,6 @@ public TaskHubProxyListener(StatefulServiceContext context, this.registerOrchestrations = registerOrchestrations ?? throw new ArgumentNullException(nameof(registerOrchestrations)); } - private void EnsureFabricOrchestrationProvider() - { - if (this.fabricOrchestrationProvider == null) - { - this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - } - } - /// /// Handles node's role change. /// @@ -83,7 +76,7 @@ public async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cance ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, $"Fabric On Change Role Async, current role = {this.currentRole}, new role = {newRole}"); if (newRole != ReplicaRole.Primary && this.currentRole == ReplicaRole.Primary) { - await ShutdownAsync(); + await StopAsync(); } this.currentRole = newRole; ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, $"Fabric On Change Role Async, current role = {this.currentRole}"); @@ -97,37 +90,12 @@ public async Task OnChangeRoleAsync(ReplicaRole newRole, CancellationToken cance public async Task OnCloseAsync(CancellationToken cancellationToken) { ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "OnCloseAsync - will shutdown primary if not already done"); - await ShutdownAsync(); - } - - async Task ShutdownAsync() - { - try - { - if (this.worker != null) - { - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopping Taskhub Worker"); - await this.worker.StopAsync(isForced: true); - this.worker.Dispose(); - this.worker = null; - this.fabricOrchestrationProvider.Dispose(); - this.fabricOrchestrationProvider = null; - ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopped Taskhub Worker"); - } - } - catch (Exception e) - { - ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); - throw; - } + await StopAsync(); } /// public ServiceReplicaListener CreateServiceReplicaListener() { - this.fabricProviderFactory = new FabricOrchestrationProviderFactory(statefulService.StateManager, this.fabricOrchestrationProviderSettings); - this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); - return new ServiceReplicaListener(context => { var serviceEndpoint = context.CodePackageActivationContext.GetEndpoint(Constants.TaskHubProxyListenerEndpointName); @@ -141,7 +109,7 @@ public ServiceReplicaListener CreateServiceReplicaListener() ipAddress = ipv4Address.ToString(); #endif - EnsureFabricOrchestrationProvider(); + EnsureFabricOrchestrationProviderIsInitialized(); string listeningAddress = String.Format(CultureInfo.InvariantCulture, "http://{0}:{1}/{2}/dtfx/", ipAddress, port, context.PartitionId); return new OwinCommunicationListener(new Startup(listeningAddress, this.fabricOrchestrationProvider)); }, Constants.TaskHubProxyServiceName); @@ -149,10 +117,28 @@ public ServiceReplicaListener CreateServiceReplicaListener() /// public async Task OnRunAsync(CancellationToken cancellationToken) + { + await StartAsync(); + } + + /// + public void Initialize(StatefulService statefulService) + { + this.statefulService = statefulService; + } + + /// + public Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + { + this.fabricProviderFactory = new FabricOrchestrationProviderFactory(statefulService.StateManager, this.fabricOrchestrationProviderSettings); + return Task.CompletedTask; + } + + async Task StartAsync() { try { - EnsureFabricOrchestrationProvider(); + EnsureFabricOrchestrationProviderIsInitialized(); this.worker = new TaskHubWorker(this.fabricOrchestrationProvider.OrchestrationService); @@ -165,19 +151,38 @@ public async Task OnRunAsync(CancellationToken cancellationToken) catch (Exception exception) { ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); + throw; } } - /// - public void Initialize(StatefulService statefulService) + async Task StopAsync() { - this.statefulService = statefulService; + try + { + if (this.worker != null) + { + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopping Taskhub Worker"); + await this.worker.StopAsync(isForced: true); + this.worker.Dispose(); + this.worker = null; + this.fabricOrchestrationProvider.Dispose(); + this.fabricOrchestrationProvider = null; + ServiceFabricProviderEventSource.Tracing.LogFabricServiceInformation(this.statefulService, "Stopped Taskhub Worker"); + } + } + catch (Exception e) + { + ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("Exception when Stopping Worker On Primary Stop", e.ToString()); + throw; + } } - /// - public Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken) + private void EnsureFabricOrchestrationProviderIsInitialized() { - return Task.CompletedTask; + if (this.fabricOrchestrationProvider == null) + { + this.fabricOrchestrationProvider = this.fabricProviderFactory.CreateProvider(); + } } } } diff --git a/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs b/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs index f69fb782b..fc6a2bc47 100644 --- a/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs +++ b/src/DurableTask.AzureServiceFabric/Service/TaskHubStatefulService.cs @@ -101,6 +101,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) catch (Exception exception) { ServiceFabricProviderEventSource.Tracing.ServiceRequestFailed("RunAsync failed", $"Exception Details Type: {exception.GetType()}, Message: {exception.Message}, StackTrace: {exception.StackTrace}"); + throw new FabricException("Exception in RunAsync", exception); } } diff --git a/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs b/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs index 35dd2d84f..dd2df57a3 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/MessageProviderBase.cs @@ -16,10 +16,13 @@ namespace DurableTask.AzureServiceFabric.Stores using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Fabric; using System.Threading; using System.Threading.Tasks; + using DurableTask.AzureServiceFabric.TaskHelpers; using DurableTask.AzureServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; using Microsoft.ServiceFabric.Data.Collections; @@ -45,9 +48,9 @@ protected MessageProviderBase(IReliableStateManager stateManager, string storeNa public virtual async Task StartAsync() { - await InitializeStore(); - await EnumerateItems(kvp => AddItemInMemory(kvp.Key, kvp.Value)); - var nowait = LogMetrics(); + await this.InitializeStore(); + await this.EnumerateItems(kvp => this.AddItemInMemory(kvp.Key, kvp.Value)); + Task nowait = this.LogMetrics(); } protected async Task InitializeStore() @@ -62,7 +65,7 @@ public Task CompleteAsync(ITransaction tx, TKey key) public async Task CompleteBatchAsync(ITransaction tx, IEnumerable keys) { - foreach (var key in keys) + foreach (TKey key in keys) { await this.Store.TryRemoveAsync(tx, key); } @@ -79,7 +82,7 @@ public Task SendBeginAsync(ITransaction tx, Message item) public void SendComplete(Message item) { - AddItemInMemory(item.Key, item.Value); + this.AddItemInMemory(item.Key, item.Value); this.waitEvent.Set(); } @@ -89,7 +92,7 @@ public void SendComplete(Message item) /// public async Task SendBatchBeginAsync(ITransaction tx, IEnumerable> items) { - foreach (var item in items) + foreach (Message item in items) { await this.Store.TryAddAsync(tx, item.Key, item.Value); } @@ -97,9 +100,9 @@ public async Task SendBatchBeginAsync(ITransaction tx, IEnumerable> items) { - foreach (var item in items) + foreach (Message item in items) { - AddItemInMemory(item.Key, item.Value); + this.AddItemInMemory(item.Key, item.Value); } this.waitEvent.Set(); } @@ -149,7 +152,7 @@ public Task EnsureStoreInitialized() { if (this.Store == null) { - return InitializeStore(); + return this.InitializeStore(); } return Task.CompletedTask; @@ -177,14 +180,19 @@ protected Task LogMetrics() { return Utils.RunBackgroundJob(async () => { - long count = 0; - using (var tx = this.StateManager.CreateTransaction()) + try + { + using (ITransaction tx = this.StateManager.CreateTransaction()) + { + long count = await this.Store.GetCountAsync(tx); + ServiceFabricProviderEventSource.Tracing.LogStoreCount(this.storeName, count); + } + } + catch (FabricObjectClosedException) { - count = await this.Store.GetCountAsync(tx); + ServiceFabricProviderEventSource.Tracing.ExceptionWhileRunningBackgroundJob("LogMetrics", "Fabric object is closed while running the loop action"); } - - ServiceFabricProviderEventSource.Tracing.LogStoreCount(this.storeName, count); - }, initialDelay: metricsInterval, delayOnSuccess: metricsInterval, delayOnException: metricsInterval, actionName: $"Log Store Count of {this.storeName}", token: this.CancellationToken); + }, initialDelay: this.metricsInterval, delayOnSuccess: this.metricsInterval, delayOnException: this.metricsInterval, actionName: $"Log Store Count of {this.storeName}", token: this.CancellationToken); } } } diff --git a/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs index 7712a835d..9ebf2d164 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/ScheduledMessageProvider.cs @@ -28,15 +28,15 @@ namespace DurableTask.AzureServiceFabric.Stores class ScheduledMessageProvider : MessageProviderBase { - readonly SessionsProvider sessionsProvider; + readonly SessionProvider sessionProvider; readonly object @lock = new object(); ImmutableSortedSet> inMemorySet = ImmutableSortedSet>.Empty.WithComparer(TimerFiredEventComparer.Instance); DateTime nextActivationCheck; - public ScheduledMessageProvider(IReliableStateManager stateManager, string storeName, SessionsProvider sessionsProvider, CancellationToken token) : base(stateManager, storeName, token) + public ScheduledMessageProvider(IReliableStateManager stateManager, string storeName, SessionProvider sessionProvider, CancellationToken token) : base(stateManager, storeName, token) { - this.sessionsProvider = sessionsProvider; + this.sessionProvider = sessionProvider; } public override async Task StartAsync() @@ -138,7 +138,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => { using (var tx = this.StateManager.CreateTransaction()) { - modifiedSessions = await this.sessionsProvider.TryAppendMessageBatchAsync(tx, values); + modifiedSessions = await this.sessionProvider.TryAppendMessageBatchAsync(tx, values); await this.CompleteBatchAsync(tx, keys); await tx.CommitAsync(); } @@ -153,7 +153,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => { foreach (var sessionId in modifiedSessions) { - this.sessionsProvider.TryEnqueueSession(sessionId); + this.sessionProvider.TryEnqueueSession(sessionId); } } } diff --git a/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionMessageProvider.cs similarity index 89% rename from src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/SessionMessageProvider.cs index 241e57230..a61632237 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/SessionMessagesProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/SessionMessageProvider.cs @@ -19,9 +19,9 @@ namespace DurableTask.AzureServiceFabric.Stores using System.Threading.Tasks; using Microsoft.ServiceFabric.Data; - class SessionMessagesProvider : MessageProviderBase + class SessionMessageProvider : MessageProviderBase { - public SessionMessagesProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) + public SessionMessageProvider(IReliableStateManager stateManager, string storeName, CancellationToken token) : base(stateManager, storeName, token) { } diff --git a/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs b/src/DurableTask.AzureServiceFabric/Stores/SessionProvider.cs similarity index 93% rename from src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs rename to src/DurableTask.AzureServiceFabric/Stores/SessionProvider.cs index 4f2f1f648..c8a24c351 100644 --- a/src/DurableTask.AzureServiceFabric/Stores/SessionsProvider.cs +++ b/src/DurableTask.AzureServiceFabric/Stores/SessionProvider.cs @@ -24,6 +24,7 @@ namespace DurableTask.AzureServiceFabric.Stores using DurableTask.Core; using DurableTask.AzureServiceFabric.TaskHelpers; using DurableTask.AzureServiceFabric.Tracing; + using Microsoft.ServiceFabric.Data; /// @@ -69,15 +70,15 @@ namespace DurableTask.AzureServiceFabric.Stores /// can handle no messages scenario, it's simpler to ignore the difference between the above and new messages being sent /// after consumer reads messages but before the session is unlocked. /// - class SessionsProvider : MessageProviderBase + class SessionProvider : MessageProviderBase { ConcurrentQueue fetchQueue = new ConcurrentQueue(); ConcurrentDictionary lockedSessions = new ConcurrentDictionary(); - ConcurrentDictionary sessionMessageProviders - = new ConcurrentDictionary(OrchestrationInstanceComparer.Default); + ConcurrentDictionary sessionMessageProviders + = new ConcurrentDictionary(OrchestrationInstanceComparer.Default); - public SessionsProvider(IReliableStateManager stateManager, CancellationToken token) : base(stateManager, Constants.OrchestrationDictionaryName, token) + public SessionProvider(IReliableStateManager stateManager, CancellationToken token) : base(stateManager, Constants.OrchestrationDictionaryName, token) { } @@ -119,7 +120,7 @@ public async Task AcceptSessionAsync(TimeSpan receiveTimeout) throw new Exception(errorMessage); } } - }, uniqueActionIdentifier: $"{nameof(SessionsProvider)}.{nameof(AcceptSessionAsync)}, SessionId : {returnInstanceId}"); + }, uniqueActionIdentifier: $"{nameof(SessionProvider)}.{nameof(AcceptSessionAsync)}, SessionId : {returnInstanceId}"); } catch (Exception) { @@ -149,14 +150,14 @@ public async Task>> ReceiveSessionMessagesAs public async Task CompleteMessages(ITransaction transaction, OrchestrationInstance instance, List lockTokens) { - if (this.sessionMessageProviders.TryGetValue(instance, out SessionMessagesProvider sessionMessageProvider)) + if (this.sessionMessageProviders.TryGetValue(instance, out SessionMessageProvider sessionMessageProvider)) { ServiceFabricProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Number of completed messages {lockTokens.Count}"); await sessionMessageProvider.CompleteBatchAsync(transaction, lockTokens); } else { - ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(SessionsProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); + ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition($"{nameof(SessionProvider)}.{nameof(CompleteMessages)} : Did not find session messages provider instance for session : {instance}."); } } @@ -176,7 +177,7 @@ await RetryHelper.ExecuteWithRetryOnTransient(async () => await this.AppendMessageAsync(txn, newMessage); await txn.CommitAsync(); } - }, uniqueActionIdentifier: $"Orchestration = '{newMessage.TaskMessage.OrchestrationInstance}', Action = '{nameof(SessionsProvider)}.{nameof(AppendMessageAsync)}'"); + }, uniqueActionIdentifier: $"Orchestration = '{newMessage.TaskMessage.OrchestrationInstance}', Action = '{nameof(SessionProvider)}.{nameof(AppendMessageAsync)}'"); this.TryEnqueueSession(newMessage.TaskMessage.OrchestrationInstance); } @@ -248,7 +249,7 @@ public void TryUnlockSession(OrchestrationInstance instance, bool abandon = fals ServiceFabricProviderEventSource.Tracing.TraceMessage(instance.InstanceId, $"Session Unlock Begin, Abandon = {abandon}"); if (!this.lockedSessions.TryRemove(instance.InstanceId, out LockState lockState) || lockState == LockState.InFetchQueue) { - var errorMessage = $"{nameof(SessionsProvider)}.{nameof(TryUnlockSession)} : Trying to unlock the session {instance.InstanceId} which was not locked."; + var errorMessage = $"{nameof(SessionProvider)}.{nameof(TryUnlockSession)} : Trying to unlock the session {instance.InstanceId} which was not locked."; ServiceFabricProviderEventSource.Tracing.UnexpectedCodeCondition(errorMessage); throw new Exception(errorMessage); } @@ -291,7 +292,7 @@ public async Task GetSession(string instanceId) } } return null; - }, uniqueActionIdentifier: $"Orchestration InstanceId = {instanceId}, Action = {nameof(SessionsProvider)}.{nameof(GetSession)}"); + }, uniqueActionIdentifier: $"Orchestration InstanceId = {instanceId}, Action = {nameof(SessionProvider)}.{nameof(GetSession)}"); } public void TryEnqueueSession(OrchestrationInstance instance) @@ -322,15 +323,15 @@ public async Task DropSession(ITransaction txn, OrchestrationInstance instance) await this.Store.TryRemoveAsync(txn, instance.InstanceId); - this.sessionMessageProviders.TryRemove(instance, out SessionMessagesProvider _); + this.sessionMessageProviders.TryRemove(instance, out SessionMessageProvider _); var noWait = RetryHelper.ExecuteWithRetryOnTransient(() => this.StateManager.RemoveAsync(GetSessionMessagesDictionaryName(instance)), uniqueActionIdentifier: $"Orchestration = '{instance}', Action = 'DropSessionMessagesDictionaryBackgroundTask'"); } - async Task GetOrAddSessionMessagesInstance(OrchestrationInstance instance) + async Task GetOrAddSessionMessagesInstance(OrchestrationInstance instance) { - var newInstance = new SessionMessagesProvider(this.StateManager, GetSessionMessagesDictionaryName(instance), this.CancellationToken); + var newInstance = new SessionMessageProvider(this.StateManager, GetSessionMessagesDictionaryName(instance), this.CancellationToken); var sessionMessageProvider = this.sessionMessageProviders.GetOrAdd(instance, newInstance); await sessionMessageProvider.EnsureStoreInitialized(); return sessionMessageProvider; diff --git a/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs b/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs index 7e812d780..194a9ee97 100644 --- a/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs +++ b/src/DurableTask.AzureServiceFabric/Tracing/ServiceFabricProviderEventSource.cs @@ -24,7 +24,7 @@ namespace DurableTask.AzureServiceFabric.Tracing /// [EventSource( Guid = "9FF47541-6D50-4DDF-AF88-D9EF1807810C", - Name = "DurableTask-ServiceFabricProvider-V2")] + Name = "DurableTask-AzureServiceFabricProvider")] public sealed class ServiceFabricProviderEventSource : EventSource { /// diff --git a/tools/DurableTask.props b/tools/DurableTask.props index 80aed01d2..9a22edd60 100644 --- a/tools/DurableTask.props +++ b/tools/DurableTask.props @@ -43,9 +43,9 @@ ..\..\build_output\packages - 2.1.2 - 2.1.2 - 2.1.2 + 2.1.3 + 2.1.3 + 2.1.3 Microsoft Microsoft Durable Task Framework