public void Should_control_executions_queuing_and_rejections_per_specification_with_cancellations( int maxParallelization, int maxQueuingActions, int totalActions, string because, bool cancelQueuing, bool cancelExecuting) { if (totalActions < 0) throw new ArgumentOutOfRangeException(nameof(totalActions)); because = String.Format("MaxParallelization {0}; MaxQueuing {1}; TotalActions {2}; CancelQueuing {3}; CancelExecuting {4}: {5}", maxParallelization, maxQueuingActions, totalActions, cancelQueuing, cancelExecuting, because); BulkheadPolicy<ResultPrimitive> bulkhead = Policy.BulkheadAsync<ResultPrimitive>(maxParallelization, maxQueuingActions); // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). actions = new TraceableAction[totalActions]; for (int i = 0; i < totalActions; i++) { actions[i] = new TraceableAction(i, statusChanged, testOutputHelper); } // Throw all the delegates at the bulkhead simultaneously. Task<ResultPrimitive>[] tasks = new Task<ResultPrimitive>[totalActions]; for (int i = 0; i < totalActions; i++) { tasks[i] = actions[i].ExecuteOnBulkheadAsync<ResultPrimitive>(bulkhead); } testOutputHelper.WriteLine("Immediately after queueing..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. int expectedCompleted = 0; int expectedCancelled = 0; int expectedExecuting = Math.Min(totalActions, maxParallelization); int expectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); int expectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); int expectedBulkheadFree = maxParallelization - expectedExecuting; int expectedQueueFree = maxQueuingActions - expectedQueuing; try { actions.Count(a => a.Status == TraceableActionStatus.Faulted).Should().Be(0); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Executing).Should().Be(expectedExecuting, because + ", when checking expectedExecuting")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Should().Be(expectedQueuing, because + ", when checking expectedQueuing")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Rejected).Should().Be(expectedRejects, because + ", when checking expectedRejects")); actions.Count(a => a.Status == TraceableActionStatus.Completed).Should().Be(expectedCompleted, because + ", when checking expectedCompleted"); actions.Count(a => a.Status == TraceableActionStatus.Canceled).Should().Be(expectedCancelled, because + ", when checking expectedCancelled"); Within(shimTimeSpan, () => bulkhead.BulkheadAvailableCount.Should().Be(expectedBulkheadFree, because + ", when checking expectedBulkheadFree")); Within(shimTimeSpan, () => bulkhead.QueueAvailableCount.Should().Be(expectedQueueFree, because + ", when checking expectedQueueFree")); } finally { testOutputHelper.WriteLine("Expected initial state verified..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); } // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. while (expectedExecuting > 0) { if (cancelQueuing) { testOutputHelper.WriteLine("Cancelling a queueing task..."); actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); expectedCancelled++; expectedQueuing--; expectedQueueFree++; cancelQueuing = false; } else if (cancelExecuting) { testOutputHelper.WriteLine("Cancelling an executing task..."); actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); expectedCancelled++; if (expectedQueuing > 0) { expectedQueuing--; expectedQueueFree++; } else { expectedExecuting--; expectedBulkheadFree++; } cancelExecuting = false; } else // Complete an executing delegate. { testOutputHelper.WriteLine("Completing a task..."); actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); expectedCompleted++; if (expectedQueuing > 0) { expectedQueuing--; expectedQueueFree++; } else { expectedExecuting--; expectedBulkheadFree++; } } try { actions.Count(a => a.Status == TraceableActionStatus.Faulted).Should().Be(0); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Executing).Should().Be(expectedExecuting, because + ", when checking expectedExecuting")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Should().Be(expectedQueuing, because + ", when checking expectedQueuing")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Completed).Should().Be(expectedCompleted, because + ", when checking expectedCompleted")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Canceled).Should().Be(expectedCancelled, because + ", when checking expectedCancelled")); actions.Count(a => a.Status == TraceableActionStatus.Rejected).Should().Be(expectedRejects, because + ", when checking expectedRejects"); Within(shimTimeSpan, () => bulkhead.BulkheadAvailableCount.Should().Be(expectedBulkheadFree, because + ", when checking expectedBulkheadFree")); Within(shimTimeSpan, () => bulkhead.QueueAvailableCount.Should().Be(expectedQueueFree, because + ", when checking expectedQueueFree")); } finally { testOutputHelper.WriteLine("End of next loop iteration..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); } } EnsureNoUnbservedTaskExceptions(tasks); testOutputHelper.WriteLine("Verifying all tasks completed..."); Within(shimTimeSpan, () => tasks.All(t => t.IsCompleted).Should().BeTrue()); }
public void Should_control_executions_queuing_and_rejections_per_specification_with_cancellations( int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) { if (totalActions < 0) { throw new ArgumentOutOfRangeException(nameof(totalActions)); } scenario = String.Format("MaxParallelization {0}; MaxQueuing {1}; TotalActions {2}; CancelQueuing {3}; CancelExecuting {4}: {5}", maxParallelization, maxQueuingActions, totalActions, cancelQueuing, cancelExecuting, scenario); BulkheadPolicy <ResultPrimitive> bulkhead = Policy.Bulkhead <ResultPrimitive>(maxParallelization, maxQueuingActions); // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). actions = new TraceableAction[totalActions]; for (int i = 0; i < totalActions; i++) { actions[i] = new TraceableAction(i, statusChanged, testOutputHelper); } // Throw all the delegates at the bulkhead simultaneously. Task[] tasks = new Task[totalActions]; for (int i = 0; i < totalActions; i++) { tasks[i] = actions[i].ExecuteOnBulkhead(bulkhead); } testOutputHelper.WriteLine("Immediately after queueing..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. int expectedCompleted = 0; int expectedCancelled = 0; int expectedExecuting = Math.Min(totalActions, maxParallelization); int expectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); int expectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); int expectedBulkheadFree = maxParallelization - expectedExecuting; int expectedQueueFree = maxQueuingActions - expectedQueuing; try { actions.Count(a => a.Status == TraceableActionStatus.Faulted).Should().Be(0); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Executing).Should().Be(expectedExecuting, scenario + ", when checking expectedExecuting")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Should().Be(expectedQueuing, scenario + ", when checking expectedQueuing")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Rejected).Should().Be(expectedRejects, scenario + ", when checking expectedRejects")); actions.Count(a => a.Status == TraceableActionStatus.Completed).Should().Be(expectedCompleted, scenario + ", when checking expectedCompleted"); actions.Count(a => a.Status == TraceableActionStatus.Canceled).Should().Be(expectedCancelled, scenario + ", when checking expectedCancelled"); Within(shimTimeSpan, () => bulkhead.BulkheadAvailableCount.Should().Be(expectedBulkheadFree, scenario + ", when checking expectedBulkheadFree")); Within(shimTimeSpan, () => bulkhead.QueueAvailableCount.Should().Be(expectedQueueFree, scenario + ", when checking expectedQueueFree")); } finally { testOutputHelper.WriteLine("Expected initial state verified..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); } // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. while (expectedExecuting > 0) { if (cancelQueuing) { testOutputHelper.WriteLine("Cancelling a queueing task..."); actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); expectedCancelled++; expectedQueuing--; expectedQueueFree++; cancelQueuing = false; } else if (cancelExecuting) { testOutputHelper.WriteLine("Cancelling an executing task..."); actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); expectedCancelled++; if (expectedQueuing > 0) { expectedQueuing--; expectedQueueFree++; } else { expectedExecuting--; expectedBulkheadFree++; } cancelExecuting = false; } else // Complete an executing delegate. { testOutputHelper.WriteLine("Completing a task..."); actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); expectedCompleted++; if (expectedQueuing > 0) { expectedQueuing--; expectedQueueFree++; } else { expectedExecuting--; expectedBulkheadFree++; } } try { actions.Count(a => a.Status == TraceableActionStatus.Faulted).Should().Be(0); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Executing).Should().Be(expectedExecuting, scenario + ", when checking expectedExecuting")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Should().Be(expectedQueuing, scenario + ", when checking expectedQueuing")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Completed).Should().Be(expectedCompleted, scenario + ", when checking expectedCompleted")); Within(shimTimeSpan, () => actions.Count(a => a.Status == TraceableActionStatus.Canceled).Should().Be(expectedCancelled, scenario + ", when checking expectedCancelled")); actions.Count(a => a.Status == TraceableActionStatus.Rejected).Should().Be(expectedRejects, scenario + ", when checking expectedRejects"); Within(shimTimeSpan, () => bulkhead.BulkheadAvailableCount.Should().Be(expectedBulkheadFree, scenario + ", when checking expectedBulkheadFree")); Within(shimTimeSpan, () => bulkhead.QueueAvailableCount.Should().Be(expectedQueueFree, scenario + ", when checking expectedQueueFree")); } finally { testOutputHelper.WriteLine("End of next loop iteration..."); testOutputHelper.WriteLine("Bulkhead: {0} slots out of {1} available.", bulkhead.BulkheadAvailableCount, maxParallelization); testOutputHelper.WriteLine("Bulkhead queue: {0} slots out of {1} available.", bulkhead.QueueAvailableCount, maxQueuingActions); OutputActionStatuses(); } } EnsureNoUnbservedTaskExceptions(tasks); testOutputHelper.WriteLine("Verifying all tasks completed..."); Within(shimTimeSpan, () => tasks.All(t => t.IsCompleted).Should().BeTrue()); #endregion }
public void Should_control_executions_per_specification(int maxParallelization, int maxQueuingActions, int totalActions, bool cancelQueuing, bool cancelExecuting, string scenario) { if (totalActions < 0) { throw new ArgumentOutOfRangeException(nameof(totalActions)); } MaxParallelization = maxParallelization; MaxQueuingActions = maxQueuingActions; TotalActions = totalActions; Scenario = $"MaxParallelization {maxParallelization}; MaxQueuing {maxQueuingActions}; TotalActions {totalActions}; CancelQueuing {cancelQueuing}; CancelExecuting {cancelExecuting}: {scenario}"; IBulkheadPolicy bulkhead = GetBulkhead(maxParallelization, maxQueuingActions); using (bulkhead) { BulkheadForStats = bulkhead; // Set up delegates which we can track whether they've started; and control when we allow them to complete (to release their semaphore slot). Actions = new TraceableAction[totalActions]; for (int i = 0; i < totalActions; i++) { Actions[i] = new TraceableAction(i, StatusChangedEvent, TestOutputHelper); } // Throw all the delegates at the bulkhead simultaneously. Tasks = new Task[totalActions]; for (int i = 0; i < totalActions; i++) { Tasks[i] = ExecuteOnBulkhead(bulkhead, Actions[i]); } OutputStatus("Immediately after queueing..."); // Assert the expected distributions of executing, queuing, rejected and completed - when all delegates thrown at bulkhead. ExpectedCompleted = 0; ExpectedCancelled = 0; ExpectedExecuting = Math.Min(totalActions, maxParallelization); ExpectedRejects = Math.Max(0, totalActions - maxParallelization - maxQueuingActions); ExpectedQueuing = Math.Min(maxQueuingActions, Math.Max(0, totalActions - maxParallelization)); ExpectedBulkheadFree = maxParallelization - ExpectedExecuting; ExpectedQueueFree = maxQueuingActions - ExpectedQueuing; try { Within(CohesionTimeLimit, ActualsMatchExpecteds); } finally { OutputStatus("Expected initial state verified..."); } // Complete or cancel delegates one by one, and expect others to take their place (if a slot released and others remain queueing); until all work is done. while (ExpectedExecuting > 0) { if (cancelQueuing) { TestOutputHelper.WriteLine("Cancelling a queueing task..."); Actions.First(a => a.Status == TraceableActionStatus.QueueingForSemaphore).Cancel(); ExpectedCancelled++; ExpectedQueuing--; ExpectedQueueFree++; cancelQueuing = false; } else if (cancelExecuting) { TestOutputHelper.WriteLine("Cancelling an executing task..."); Actions.First(a => a.Status == TraceableActionStatus.Executing).Cancel(); ExpectedCancelled++; if (ExpectedQueuing > 0) { ExpectedQueuing--; ExpectedQueueFree++; } else { ExpectedExecuting--; ExpectedBulkheadFree++; } cancelExecuting = false; } else // Complete an executing delegate. { TestOutputHelper.WriteLine("Completing a task..."); Actions.First(a => a.Status == TraceableActionStatus.Executing).AllowCompletion(); ExpectedCompleted++; if (ExpectedQueuing > 0) { ExpectedQueuing--; ExpectedQueueFree++; } else { ExpectedExecuting--; ExpectedBulkheadFree++; } } try { Within(CohesionTimeLimit, ActualsMatchExpecteds); } finally { OutputStatus("End of next loop iteration..."); } } EnsureNoUnbservedTaskExceptions(); TestOutputHelper.WriteLine("Verifying all tasks completed..."); Within(CohesionTimeLimit, AllTasksCompleted); } }