/// <summary> /// Tests that handler does not have to remove an item from the collection /// and that the handled item is the item on which the handler completed. /// </summary> private async Task HandleableCollectionHandleNoRemovalTestCore(bool useAsync) { using var collection = new HandleableCollection <int>(); Assert.Empty(collection); var shim = new HandleableCollectionApiShim <int>(collection, useAsync); AddRangeAndVerifyItems(collection, endInclusive: 4); const int expectedItem = 2; HandleableCollection <int> .Handler handler = (int item, out bool removeItem) => { // Do not remove any item (the purpose of this test is to handle an // item without removing it). removeItem = false; // Terminate handler on some item in the middle of the collection return(expectedItem == item); }; // Wait for handled item to be returned int handledItem = await shim.Handle(handler, DefaultPositiveVerificationTimeout); Assert.Equal(expectedItem, handledItem); Assert.Equal(new int[] { 0, 1, 2, 3, 4 }, collection); }
/// <summary> /// Tests that a non-default handler can remove items independently from /// handling an item and that the handled item is the item on which /// the handler completed. /// </summary> private async Task HandleableCollectionComplexHandlerTestCore(bool useAsync) { using var collection = new HandleableCollection <int>(); Assert.Empty(collection); var shim = new HandleableCollectionApiShim <int>(collection, useAsync); const int expectedItem = 7; AddRangeAndVerifyItems(collection, endInclusive: expectedItem); HandleableCollection <int> .Handler handler = (int item, out bool removeItem) => { removeItem = false; // Remove every third item if (item % 3 == 0) { removeItem = true; } // Terminate handler on last item return(expectedItem == item); }; int handledItem = await shim.Handle(handler, DefaultPositiveVerificationTimeout); Assert.Equal(expectedItem, handledItem); Assert.Equal(new int[] { 1, 2, 4, 5, 7 }, collection); }
/// <summary> /// Tests that the default handler handles one item at a time and /// removes each item after each successful handling. /// </summary> private async Task HandleableCollectionDefaultHandlerTestCore(bool useAsync) { using var collection = new HandleableCollection <int>(); Assert.Empty(collection); var shim = new HandleableCollectionApiShim <int>(collection, useAsync); AddRangeAndVerifyItems(collection, endInclusive: 14); int expectedCollectionCount = collection.Count(); for (int item = 0; item < 15; item++) { int handledItem = await shim.Handle(DefaultPositiveVerificationTimeout); expectedCollectionCount--; Assert.Equal(item, handledItem); Assert.Equal(expectedCollectionCount, collection.Count()); } Assert.Empty(collection); await shim.Handle(DefaultNegativeVerificationTimeout, expectTimeout : true); }
public async Task HandleableCollectionThrowsWhenDisposedTest() { var collection = new HandleableCollection <int>(); AddRangeAndVerifyItems(collection, endInclusive: 9); HandleableCollection <int> .Handler handler = (int item, out bool removeItem) => { removeItem = false; return(20 == item); }; using var cancellation = new CancellationTokenSource(DefaultPositiveVerificationTimeout); Task handleTask = Task.Run(() => collection.Handle(handler, DefaultPositiveVerificationTimeout)); Task handleAsyncTask = collection.HandleAsync(handler, cancellation.Token); // Task.Delay intentionally shorter than default timeout to check that Handle* // calls did not complete quickly. Task delayTask = Task.Delay(TimeSpan.FromSeconds(1)); Task completedTask = await Task.WhenAny(delayTask, handleTask, handleAsyncTask); // Check that the handle tasks didn't complete Assert.Equal(delayTask, completedTask); collection.Dispose(); // Incomplete calls from prior to disposal should throw ObjectDisposedException await Assert.ThrowsAsync <ObjectDisposedException>(() => handleTask); await Assert.ThrowsAsync <ObjectDisposedException>(() => handleAsyncTask); // New calls should throw ObjectDisposedException Assert.Throws <ObjectDisposedException>( () => collection.Add(10)); Assert.Throws <ObjectDisposedException>( () => collection.ClearItems()); Assert.Throws <ObjectDisposedException>( () => collection.Handle(DefaultPositiveVerificationTimeout)); Assert.Throws <ObjectDisposedException>( () => collection.Handle(handler, DefaultPositiveVerificationTimeout)); await Assert.ThrowsAsync <ObjectDisposedException>( () => collection.HandleAsync(cancellation.Token)); await Assert.ThrowsAsync <ObjectDisposedException>( () => collection.HandleAsync(handler, cancellation.Token)); Assert.Throws <ObjectDisposedException>( () => ((IEnumerable)collection).GetEnumerator()); Assert.Throws <ObjectDisposedException>( () => ((IEnumerable <int>)collection).GetEnumerator()); }
public async Task HandleableCollectionClearItemsTest() { using var collection = new HandleableCollection <int>(); Assert.Empty(collection); AddRangeAndVerifyItems(collection, endInclusive: 4); HandleableCollection <int> .Handler handler = (int value, out bool removeItem) => { if (value == 7) { removeItem = true; return(true); } removeItem = false; return(false); }; using var cancellation = new CancellationTokenSource(DefaultPositiveVerificationTimeout); Task handleAsyncTask = Task.Run(() => collection.HandleAsync(handler, cancellation.Token)); // Task.Delay intentionally shorter than default timeout to check that HandleAsync // calls did not complete quickly. Task delayTask = Task.Delay(TimeSpan.FromSeconds(1)); Task completedTask = await Task.WhenAny(delayTask, handleAsyncTask); // Check that the handle task didn't complete Assert.Equal(delayTask, completedTask); collection.ClearItems(); Assert.Empty(collection); // The remainder of the test checks that the previously registered handler is still // registered with the collection and was not removed by calling ClearItems. IList <(int, int)> itemsAndCounts = new List <(int, int)>(); itemsAndCounts.Add((6, 1)); itemsAndCounts.Add((7, 1)); // Item is consumed immediately, thus collection count does not change itemsAndCounts.Add((8, 2)); AddAndVerifyItems(collection, itemsAndCounts); // Task.Delay intentionally longer than default timeout to check that HandleAsync // does complete by handling a value. The delay Task is used in case the handler doesn't // handle a value and doesn't respect cancellation so as to not stall the test indefinitely. delayTask = Task.Delay(2 * DefaultPositiveVerificationTimeout); completedTask = await Task.WhenAny(delayTask, handleAsyncTask); // Check that the handle task did complete Assert.Equal(handleAsyncTask, completedTask); // Check that the value was removed Assert.Equal(new int[] { 6, 8 }, collection); }
/// <summary> /// Tests that handler can be added before an item is provided to the collection. /// </summary> private async Task HandleableCollectionHandleBeforeAddTestCore(bool useAsync) { using var collection = new TestHandleableCollection <int>(); Assert.Empty(collection); var shim = new HandleableCollectionApiShim <int>(collection, useAsync); // Register to be notified when handler is beginning to be processed Task handlerBeginTask = collection.WaitForHandlerBeginAsync(DefaultPositiveVerificationTimeout); const int expectedItem = 3; HandleableCollection <int> .Handler handler = (int item, out bool removeItem) => { // Terminate handler on some item in the middle of the collection if (expectedItem == item) { removeItem = true; return(true); } removeItem = false; return(false); }; // Create task that will start handling BEFORE an item is added Task <int> handleItemTask = Task.Run(() => shim.Handle(handler, DefaultPositiveVerificationTimeout)); // Wait for handler to begin processing Task delayTask = Task.Delay(5 * DefaultPositiveVerificationTimeout); Task completedTask = await Task.WhenAny(delayTask, handlerBeginTask); Assert.Equal(handlerBeginTask, completedTask); IList <(int, int)> itemsAndCounts = new List <(int, int)>() { (0, 1), (1, 2), (2, 3), (3, 3), // Item is consumed immediately, thus collection count does not change (4, 4), (5, 5) }; AddAndVerifyItems(collection, itemsAndCounts); // Wait for handled item to be returned int handledItem = await handleItemTask; Assert.Equal(expectedItem, handledItem); Assert.Equal(new int[] { 0, 1, 2, 4, 5 }, collection); }
/// <summary> /// Tests that handler does not have to remove an item from the collection /// and that the handled item is the item on which the handler completed. /// </summary> private async Task HandleableCollectionHandlerThrowsTestCore(bool useAsync) { using var collection = new HandleableCollection <int>(); Assert.Empty(collection); var shim = new HandleableCollectionApiShim <int>(collection, useAsync); AddRangeAndVerifyItems(collection, endInclusive: 4); HandleableCollection <int> .Handler handler = (int item, out bool removeItem) => { if (6 == item) { throw new InvalidOperationException(); } removeItem = false; return(false); }; Task <int> handleTask = Task.Run(() => shim.Handle(handler, DefaultPositiveVerificationTimeout)); // Task.Delay intentionally shorter than default timeout to check that Handle* // calls did not complete quickly. Task delayTask = Task.Delay(TimeSpan.FromSeconds(1)); Task completedTask = await Task.WhenAny(delayTask, handleTask); // Check that the handle task didn't complete Assert.Equal(delayTask, completedTask); IList <(int, int)> itemsAndCounts = new List <(int, int)>(); itemsAndCounts.Add((5, 6)); itemsAndCounts.Add((6, 7)); // Handler should fault on this task itemsAndCounts.Add((7, 8)); AddAndVerifyItems(collection, itemsAndCounts); // Task.Delay intentionally longer than default timeout to check that Handle* // does complete by handling a value. The delay Task is used in case the handler doesn't // handle a value and doesn't respect cancellation so as to not stall the test indefinitely. delayTask = Task.Delay(2 * DefaultPositiveVerificationTimeout); completedTask = await Task.WhenAny(delayTask, handleTask); // Check that the handle task faulted and the collection did not change Assert.Equal(handleTask, completedTask); await Assert.ThrowsAsync <InvalidOperationException>(() => handleTask); Assert.Equal(new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }, collection); }
/// <summary> /// Listens at the address for new connections. /// </summary> /// <param name="maxConnections">The maximum number of connections the server will support.</param> /// <param name="token">The token to monitor for cancellation requests.</param> /// <returns>A task that completes when the server is no longer listening at the address.</returns> private async Task ListenAsync(int maxConnections, CancellationToken token) { // This disposal shuts down the transport in case an exception is thrown. using var transport = IpcServerTransport.Create(_address, maxConnections, _enableTcpIpProtocol, TransportCallback); // This disposal shuts down the transport in case of cancellation; causes the transport // to not recreate the server stream before the AcceptAsync call observes the cancellation. using var _ = token.Register(() => transport.Dispose()); while (!token.IsCancellationRequested) { Stream stream = null; IpcAdvertise advertise = null; try { stream = await transport.AcceptAsync(token).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception) { // The advertise data could be incomplete if the runtime shuts down before completely writing // the information. Catch the exception and continue waiting for a new connection. } if (null != stream) { // Cancel parsing of advertise data after timeout period to // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever). using var parseCancellationSource = new CancellationTokenSource(); using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token); try { parseCancellationSource.CancelAfter(ParseAdvertiseTimeout); advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false); } catch (Exception) { stream.Dispose(); } } if (null != advertise) { Guid runtimeCookie = advertise.RuntimeInstanceCookie; int pid = unchecked ((int)advertise.ProcessId); // The valueFactory parameter of the GetOrAdd overload that uses Func<TKey, TValue> valueFactory // does not execute the factory under a lock thus it is not thread-safe. Create the collection and // use a thread-safe version of GetOrAdd; use equality comparison on the result to determine if // the new collection was added to the dictionary or if an existing one was returned. var newStreamCollection = new HandleableCollection <Stream>(); var streamCollection = _streamCollections.GetOrAdd(runtimeCookie, newStreamCollection); try { streamCollection.ClearItems(); streamCollection.Add(stream); if (newStreamCollection == streamCollection) { ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie); _endpointInfos.Add(new IpcEndpointInfo(endpoint, pid, runtimeCookie)); } else { newStreamCollection.Dispose(); } } catch (ObjectDisposedException) { // The stream collection could be disposed by RemoveConnection which would cause an // ObjectDisposedException to be thrown if trying to clear/add the stream. stream.Dispose(); } } } }
private static void AddAndVerifyItems <T>(HandleableCollection <T> collection, IEnumerable <(T, int)> itemsAndCounts)