public async Task OrchestrationConcurrency(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); Func <Task> orchestrationStarter = async delegate() { var timeout = TimeSpan.FromSeconds(10); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Approval), timeout); await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); // Don't send any notification - let the internal timeout expire }; int iterations = 50; var tasks = new Task[iterations]; for (int i = 0; i < iterations; i++) { tasks[i] = orchestrationStarter(); } // The 50 orchestrations above (which each delay for 10 seconds) should all complete in less than 60 seconds. Task parallelOrchestrations = Task.WhenAll(tasks); Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(60)); Task winner = await Task.WhenAny(parallelOrchestrations, timeoutTask); Assert.AreEqual(parallelOrchestrations, winner); await host.StopAsync(); } }
public async Task RecreateFailedInstance(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); string singletonInstanceId = $"HelloSingleton_{Guid.NewGuid():N}"; var client = await host.StartOrchestrationAsync( typeof(Orchestrations.SayHelloWithActivity), input : null, // this will cause the orchestration to fail instanceId : singletonInstanceId); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Failed, status?.OrchestrationStatus); client = await host.StartOrchestrationAsync( typeof(Orchestrations.SayHelloWithActivity), input : "NotNull", instanceId : singletonInstanceId); status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual("Hello, NotNull!", JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task LargeTextMessagePayloads(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Generate a medium random string payload const int TargetPayloadSize = 128 * 1024; // 128 KB const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 {}/<>.-"; var sb = new StringBuilder(); var random = new Random(); while (Encoding.Unicode.GetByteCount(sb.ToString()) < TargetPayloadSize) { for (int i = 0; i < 1000; i++) { sb.Append(Chars[random.Next(Chars.Length)]); } } string message = sb.ToString(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Echo), message); var status = await client.WaitForCompletionAsync(TimeSpan.FromMinutes(2)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(message, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task TaskReturnsVoid_OrchestratorFails() { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(false)) { await host.StartAsync(); TaskActivity activity = TestOrchestrationHost.MakeActivity <int, Task>( delegate(TaskContext ctx, int input) { return(null); }); TestInstance <int> instance = await host.StartInlineOrchestration( input : 123, orchestrationName : "TestOrchestration", implementation : (ctx, input) => ctx.ScheduleTask <int>("Activity", "", input), activities : ("Activity", activity)); // The expectedOutput value is the string that's passed into the InvalidOperationException await Task.WhenAll(instance.WaitForCompletion( expectedStatus: OrchestrationStatus.Completed, expectedOutput: 0)); await host.StopAsync(); } }
public async Task ActorOrchestration(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); int initialValue = 0; var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Counter), initialValue); // Need to wait for the instance to start before sending events to it. // TODO: This requirement may not be ideal and should be revisited. await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); // Perform some operations await client.RaiseEventAsync("operation", "incr"); // TODO: Sleeping to avoid a race condition where multiple ContinueAsNew messages // are processed by the same instance at the same time, resulting in a corrupt // storage failure in DTFx. await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "decr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); // Make sure it's still running and didn't complete early (or fail). var status = await client.GetStatusAsync(); Assert.IsTrue( status?.OrchestrationStatus == OrchestrationStatus.Running || status?.OrchestrationStatus == OrchestrationStatus.ContinuedAsNew); // The end message will cause the actor to complete itself. await client.RaiseEventAsync("operation", "end"); status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(3, JToken.Parse(status?.Output)); // When using ContinueAsNew, the original input is discarded and replaced with the most recent state. Assert.AreNotEqual(initialValue, JToken.Parse(status?.Input)); await host.StopAsync(); } }
public async Task ExtendedSessions_SessionTimeout() { const int SessionTimeoutInseconds = 5; using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost( enableExtendedSessions: true, extendedSessionTimeoutInSeconds: SessionTimeoutInseconds)) { await host.StartAsync(); string singletonInstanceId = $"SingletonCounter_{DateTime.Now:o}"; // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestrationAsync( typeof(Orchestrations.Counter), input : 0, instanceId : singletonInstanceId); var status = await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Running, status?.OrchestrationStatus); Assert.AreEqual("0", status?.Input); Assert.AreEqual(null, status?.Output); // Delay long enough for the session to expire await Task.Delay(TimeSpan.FromSeconds(SessionTimeoutInseconds + 1)); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(TimeSpan.FromSeconds(2)); // Make sure it's still running and didn't complete early (or fail). status = await client.GetStatusAsync(); Assert.IsTrue( status?.OrchestrationStatus == OrchestrationStatus.Running || status?.OrchestrationStatus == OrchestrationStatus.ContinuedAsNew); // The end message will cause the actor to complete itself. await client.RaiseEventAsync("operation", "end"); status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(1, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task LargeFanOutOrchestration(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.FanOutFanIn), 1000); var status = await client.WaitForCompletionAsync(TimeSpan.FromMinutes(5)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); await host.StopAsync(); } }
public async Task ParallelOrchestration(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.DiskUsage), Environment.CurrentDirectory); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(90)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(Environment.CurrentDirectory, JToken.Parse(status?.Input)); Assert.IsTrue(long.Parse(status?.Output) > 0L); await host.StopAsync(); } }
public async Task HandledActivityException(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Empty string input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestrationAsync(typeof(Orchestrations.TryCatchLoop), 5); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(5, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task UnhandledOrchestrationException(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Empty string input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Throw), ""); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Failed, status?.OrchestrationStatus); Assert.IsTrue(status?.Output.Contains("null") == true); await host.StopAsync(); } }
public async Task SequentialOrchestration() { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions: false)) { await host.StartAsync(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Factorial), 10); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(10, JToken.Parse(status?.Input)); Assert.AreEqual(3628800, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task HelloWorldOrchestration_Activity(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.SayHelloWithActivity), "World"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual("World", JToken.Parse(status?.Input)); Assert.AreEqual("Hello, World!", JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task UnhandledActivityException(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); string message = "Kah-BOOOOM!!!"; var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Throw), message); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Failed, status?.OrchestrationStatus); Assert.IsTrue(status?.Output.Contains(message) == true); await host.StopAsync(); } }
public async Task SequentialOrchestrationNoReplay() { // Enable extended sesisons to ensure that the orchestration never gets replayed using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions: true)) { await host.StartAsync(); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.FactorialNoReplay), 10); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(10, JToken.Parse(status?.Input)); Assert.AreEqual(3628800, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task FanOutToTableStorage(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); int iterations = 100; var client = await host.StartOrchestrationAsync(typeof(Orchestrations.MapReduceTableStorage), iterations); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(120)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(iterations, int.Parse(status?.Output)); await host.StopAsync(); } }
public async Task RestartOrchestrationWithExternalEvents() { using TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions: false); await host.StartAsync(); // This is the local method we'll use to continuously recreate the same instance. // It doesn't matter that the orchestration name is different as long as the instance ID is the same. Task <TestInstance <string> > CreateInstance(int i) => host.StartInlineOrchestration( input: "Hello, world!", instanceId: "Singleton", orchestrationName: $"EchoOrchestration{i}", implementation: (ctx, input) => Task.FromResult(input)); // Start the first iteration and wait for it to complete. TestInstance <string> instance = await CreateInstance(0); await instance.WaitForCompletion(); int restartIterations = 10; int externalEventCount = 10; // We'll keep track of the execution IDs to make 100% sure we get new instances every iteration. var executionIds = new HashSet <string> { instance.ExecutionId }; // Simultaneously send a bunch of external events as well as a "recreate" message. Repeat this // several times. Need to ensure that the orchestration can always be restarted reliably. It // doesn't matter whether the orchestration handles the external events or not. for (int i = 1; i <= restartIterations; i++) { List <Task> concurrentTasks = Enumerable.Range(0, externalEventCount).Select(j => instance.RaiseEventAsync("DummyEvent", j)).ToList(); Task <TestInstance <string> > recreateInstanceTask = CreateInstance(i); concurrentTasks.Add(recreateInstanceTask); await Task.WhenAll(concurrentTasks); // Wait for the newly created instance to complete. instance = await recreateInstanceTask; await instance.WaitForCompletion(); // Ensure that this is a new execution ID. Assert.IsTrue(executionIds.Add(instance.ExecutionId)); } }
public async Task RecreateTerminatedInstance(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); string singletonInstanceId = $"SingletonCounter_{Guid.NewGuid():N}"; // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestrationAsync( typeof(Orchestrations.Counter), input : -1, instanceId : singletonInstanceId); // Need to wait for the instance to start before we can terminate it. await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Terminated, status?.OrchestrationStatus); Assert.AreEqual("-1", status?.Input); Assert.AreEqual("sayōnara", status?.Output); client = await host.StartOrchestrationAsync( typeof(Orchestrations.Counter), input : 0, instanceId : singletonInstanceId); status = await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Running, status?.OrchestrationStatus); Assert.AreEqual("0", status?.Input); await host.StopAsync(); } }
public async Task TerminateOrchestration(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Counter), 0); // Need to wait for the instance to start before we can terminate it. // TODO: This requirement may not be ideal and should be revisited. await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Terminated, status?.OrchestrationStatus); Assert.AreEqual("sayōnara", status?.Output); await host.StopAsync(); } }
public async Task LargeBinaryStringMessagePayloads(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Construct string message from large binary file of size 826KB string originalFileName = "large.jpeg"; string currentDirectory = Directory.GetCurrentDirectory(); string originalFilePath = Path.Combine(currentDirectory, originalFileName); byte[] readBytes = File.ReadAllBytes(originalFilePath); string message = Convert.ToBase64String(readBytes); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Echo), message); var status = await client.WaitForCompletionAsync(TimeSpan.FromMinutes(1)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual(message, JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task TimerExpiration(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); var timeout = TimeSpan.FromSeconds(10); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Approval), timeout); // Need to wait for the instance to start before sending events to it. // TODO: This requirement may not be ideal and should be revisited. await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); // Don't send any notification - let the internal timeout expire var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(20)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); Assert.AreEqual("Expired", JToken.Parse(status?.Output)); await host.StopAsync(); } }
public async Task RecreateRunningInstance(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost( enableExtendedSessions, extendedSessionTimeoutInSeconds: 15)) { await host.StartAsync(); string singletonInstanceId = $"SingletonCounter_{DateTime.Now:o}"; // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestrationAsync( typeof(Orchestrations.Counter), input : 0, instanceId : singletonInstanceId); var status = await client.WaitForStartupAsync(TimeSpan.FromSeconds(10)); Assert.AreEqual(OrchestrationStatus.Running, status?.OrchestrationStatus); Assert.AreEqual("0", status?.Input); Assert.AreEqual(null, status?.Output); client = await host.StartOrchestrationAsync( typeof(Orchestrations.Counter), input : 99, instanceId : singletonInstanceId); // Note that with extended sessions, the startup time may take longer because the dispatcher // will wait for the current extended session to expire before the new create message is accepted. status = await client.WaitForStartupAsync(TimeSpan.FromSeconds(20)); Assert.AreEqual(OrchestrationStatus.Running, status?.OrchestrationStatus); Assert.AreEqual("99", status?.Input); await host.StopAsync(); } }
public async Task LargeBinaryByteMessagePayloads(bool enableExtendedSessions) { using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions)) { await host.StartAsync(); // Construct byte array from large binary file of size 826KB string originalFileName = "large.jpeg"; string currentDirectory = Directory.GetCurrentDirectory(); string originalFilePath = Path.Combine(currentDirectory, originalFileName); byte[] readBytes = File.ReadAllBytes(originalFilePath); var client = await host.StartOrchestrationAsync(typeof(Orchestrations.EchoBytes), readBytes); var status = await client.WaitForCompletionAsync(TimeSpan.FromMinutes(1)); Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus); byte[] outputBytes = JToken.Parse(status?.Output).ToObject <byte[]>(); Assert.IsTrue(readBytes.SequenceEqual(outputBytes), "Original message byte array and returned messages byte array are not equal"); await host.StopAsync(); } }
public async Task ConcurrentOrchestrationStarts(bool useSameInstanceId) { using TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost( enableExtendedSessions: false, modifySettingsAction: settings => settings.ThrowExceptionOnInvalidDedupeStatus = false); await host.StartAsync(); var results = new ConcurrentBag <string>(); // We want a number sufficiently high that it results in multiple message batches const int MaxConcurrency = 40; TaskActivity activity = TestOrchestrationHost.MakeActivity( delegate(TaskContext ctx, string input) { string result = $"Hello, {input}!"; results.Add(result); return(result); }); // Use the same instance name for all instances Func <int, string> instanceIdGenerator; Func <int, string> inputGenerator; if (useSameInstanceId) { instanceIdGenerator = _ => $"ConcurrentInstance_SINGLETON"; inputGenerator = _ => "World"; } else { instanceIdGenerator = i => $"ConcurrentInstance_{i:00}"; inputGenerator = i => $"{i:00}"; } List <TestInstance <string> > instances = await host.StartInlineOrchestrations( MaxConcurrency, instanceIdGenerator, inputGenerator, orchestrationName : "SayHelloOrchestration", version : string.Empty, implementation : (ctx, input) => ctx.ScheduleTask <string>("SayHello", "", input), activities : ("SayHello", activity)); Assert.AreEqual(MaxConcurrency, instances.Count); // All returned objects point to the same orchestration instance OrchestrationState[] finalStates = await Task.WhenAll(instances.Select( i => i.WaitForCompletion(timeout: TimeSpan.FromMinutes(2), expectedOutputRegex: @"Hello, \w+!"))); if (useSameInstanceId) { // Make sure each instance is exactly the same string firstInstanceJson = JsonConvert.SerializeObject(finalStates[0]); foreach (OrchestrationState state in finalStates.Skip(1)) { string json = JsonConvert.SerializeObject(state); Assert.AreEqual(firstInstanceJson, json, "Expected that all instances have the same data."); } } else { // Make sure each instance is different Assert.AreEqual(MaxConcurrency, finalStates.Select(s => s.OrchestrationInstance.InstanceId).Distinct().Count()); Assert.AreEqual(MaxConcurrency, finalStates.Select(s => s.OrchestrationInstance.ExecutionId).Distinct().Count()); Assert.AreEqual(MaxConcurrency, finalStates.Select(s => s.Input).Distinct().Count()); Assert.AreEqual(MaxConcurrency, finalStates.Select(s => s.Output).Distinct().Count()); } await host.StopAsync(); }