private void Process(PersistentQueueMessage message) { HostStartedMessage hostStartedMessage = message as HostStartedMessage; if (hostStartedMessage != null) { _hostIndexer.ProcessHostStarted(hostStartedMessage); return; } FunctionCompletedMessage functionCompletedMessage = message as FunctionCompletedMessage; if (functionCompletedMessage != null) { _functionIndexer.ProcessFunctionCompleted(functionCompletedMessage); return; } FunctionStartedMessage functionStartedMessage = message as FunctionStartedMessage; if (functionStartedMessage != null) { _functionIndexer.ProcessFunctionStarted(functionStartedMessage); return; } string errorMessage = String.Format(CultureInfo.InvariantCulture, "Unknown message type '{0}'.", message.Type); throw new InvalidOperationException(errorMessage); }
public void ProcessHostStarted_UpdatesOrCreatesHostSnapshotIfLatest() { // Arrange const string hostId = "abc"; string[] expectedFunctionIds = new string[] { "a", "b" }; DateTimeOffset expectedHostVersion = DateTimeOffset.Now; IHostIndexManager hostIndexManager = CreateFakeHostIndexManager(); IHostIndexer product = CreateProductUnderTest(hostIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = expectedHostVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = expectedFunctionIds[0]}, new FunctionDescriptor { Id = expectedFunctionIds[1]} } }; // Act product.ProcessHostStarted(message); // Assert HostSnapshot hostSnapshot = hostIndexManager.Read(hostId); Assert.NotNull(hostSnapshot); Assert.Equal(expectedHostVersion, hostSnapshot.HostVersion); Assert.Equal(expectedFunctionIds, hostSnapshot.FunctionIds); }
public void JsonConvertRepeatedIdenticalChildConverterType_Roundtrips() { // Arrange HostStartedMessage expectedMessage = new HostStartedMessage { Functions = new FunctionDescriptor[] { new FunctionDescriptor { Parameters = new ParameterDescriptor[] { new CallerSuppliedParameterDescriptor { Name = "A" }, new CallerSuppliedParameterDescriptor { Name = "B" } } } } }; // Act PersistentQueueMessage message = JsonConvert.DeserializeObject <PersistentQueueMessage>( JsonConvert.SerializeObject(expectedMessage)); // Assert Assert.NotNull(message); Assert.IsType <HostStartedMessage>(message); HostStartedMessage typedMessage = (HostStartedMessage)message; ParameterDescriptor secondChildItem = typedMessage.Functions.Single().Parameters.FirstOrDefault(p => p.Name == "B"); Assert.IsType <CallerSuppliedParameterDescriptor>(secondChildItem); }
public void JsonConvertRepeatedIdenticalChildConverterType_Roundtrips() { // Arrange HostStartedMessage expectedMessage = new HostStartedMessage { Functions = new FunctionDescriptor[] { new FunctionDescriptor { Parameters = new ParameterDescriptor[] { new CallerSuppliedParameterDescriptor { Name = "A" }, new CallerSuppliedParameterDescriptor { Name = "B" } } } } }; // Act PersistentQueueMessage message = JsonConvert.DeserializeObject<PersistentQueueMessage>( JsonConvert.SerializeObject(expectedMessage)); // Assert Assert.NotNull(message); Assert.IsType<HostStartedMessage>(message); HostStartedMessage typedMessage = (HostStartedMessage)message; ParameterDescriptor secondChildItem = typedMessage.Functions.Single().Parameters.FirstOrDefault(p => p.Name == "B"); Assert.IsType<CallerSuppliedParameterDescriptor>(secondChildItem); }
public void ProcessHostStarted_UpdatesOrCreatesHostSnapshotIfLatest() { // Arrange const string hostId = "abc"; string[] expectedFunctionIds = new string[] { "a", "b" }; DateTimeOffset expectedHostVersion = DateTimeOffset.Now; IHostIndexManager hostIndexManager = CreateFakeHostIndexManager(); IHostIndexer product = CreateProductUnderTest(hostIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = expectedHostVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = expectedFunctionIds[0] }, new FunctionDescriptor { Id = expectedFunctionIds[1] } } }; // Act product.ProcessHostStarted(message); // Assert HostSnapshot hostSnapshot = hostIndexManager.Read(hostId); Assert.NotNull(hostSnapshot); Assert.Equal(expectedHostVersion, hostSnapshot.HostVersion); Assert.Equal(expectedFunctionIds, hostSnapshot.FunctionIds); }
public void ProcessHostStarted_IfHostConcurrentlyUpdated_DeletesKnownFunctionsIfLatest() { // Arrange const string hostId = "host"; string[] originalFunctionIds = new string[] { "a", "b" }; string[] finalFunctionIds = new string[] { "b", "d" }; IHostIndexManager concurrentlyRemoveFunctionsHostIndexManager = CreateStubHostIndexManager( existingSnapshot: CreateHostSnapshot(DateTimeOffset.MaxValue, finalFunctionIds), persistSucceeds: true); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); DateTimeOffset earlierHostVersion = DateTimeOffset.MinValue; AddFunctionToIndex(functionIndexManager, hostId, originalFunctionIds[0], earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, originalFunctionIds[1], earlierHostVersion); IHostIndexer product = CreateProductUnderTest(concurrentlyRemoveFunctionsHostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = DateTimeOffset.Now, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = originalFunctionIds[0] }, new FunctionDescriptor { Id = originalFunctionIds[1] }, new FunctionDescriptor { Id = "c" }, new FunctionDescriptor { Id = "d" } } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable <VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable <string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(finalFunctionIds, functionIds); }
public void ProcessHostStarted_UpdatesExistingFunctionsIfLatest() { // Arrange const string hostId = "host"; DateTimeOffset expectedVersion = DateTimeOffset.Now; string[] expectedFunctionIds = new string[] { "a", "b", "c" }; IHostIndexManager hostIndexManager = CreateStubHostIndexManager( CreateHostSnapshot(expectedVersion, expectedFunctionIds), persistSucceeds: true); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); DateTimeOffset earlierHostVersion = DateTimeOffset.MinValue; AddFunctionToIndex(functionIndexManager, hostId, expectedFunctionIds[0], earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, expectedFunctionIds[1], earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, expectedFunctionIds[2], earlierHostVersion); IHostIndexer product = CreateProductUnderTest(hostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = expectedVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = expectedFunctionIds[0] }, new FunctionDescriptor { Id = expectedFunctionIds[1] }, new FunctionDescriptor { Id = expectedFunctionIds[2] } } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable <VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable <string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(expectedFunctionIds, functionIds); IEnumerable <DateTimeOffset> versions = functions.Select(f => f.Version).ToArray(); Assert.Equal(new DateTimeOffset[] { expectedVersion, expectedVersion, expectedVersion }, versions); }
public void ProcessHostStarted_UpdatesOrCreatesFunctionIndexVersionIfLatest() { // Arrange DateTimeOffset expectedVersion = DateTimeOffset.Now; FakeFunctionIndexVersionManager functionIndexVersionManager = new FakeFunctionIndexVersionManager(); IHostIndexer product = CreateProductUnderTest(functionIndexVersionManager); HostStartedMessage message = new HostStartedMessage { EnqueuedOn = expectedVersion }; // Act product.ProcessHostStarted(message); // Assert Assert.Equal(expectedVersion, functionIndexVersionManager.Current); }
public void ProcessHostStarted_UpdatesOrCreatesFunctionIndexVersionIfLatest() { // Arrange DateTimeOffset expectedVersion = DateTimeOffset.Now; FakeFunctionIndexVersionManager functionIndexVersionManager = new FakeFunctionIndexVersionManager(); IHostIndexer product = CreateProductUnderTest(functionIndexVersionManager); HostStartedMessage message = new HostStartedMessage { EnqueuedOn = expectedVersion }; // Act product.ProcessHostStarted(message); // Assert Assert.Equal(expectedVersion, functionIndexVersionManager.Current); }
public void ProcessHostStarted_IfHostPreviouslyRemovedButProcessingAborted_DeletesKnownFunctionsIfLatest() { // Arrange const string hostId = "host"; DateTimeOffset hostVersion = DateTimeOffset.Now; string[] previouslyProcessedFunctionIds = new string[] { "a", "b", "c" }; IHostIndexManager concurrentlyRemoveFunctionsHostIndexManager = CreateStubHostIndexManager( existingSnapshot: null, persistSucceeds: false); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[0], hostVersion); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[1], hostVersion); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[2], hostVersion); IHostIndexer product = CreateProductUnderTest(concurrentlyRemoveFunctionsHostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = hostVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = previouslyProcessedFunctionIds[0] }, new FunctionDescriptor { Id = previouslyProcessedFunctionIds[1] }, new FunctionDescriptor { Id = previouslyProcessedFunctionIds[2] } } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable <VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable <string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(new string[0], functionIds); }
private static Task LogHostStartedAsync(IFunctionIndex functionIndex, HostOutputMessage hostOutputMessage, IHostInstanceLogger logger, CancellationToken cancellationToken) { IEnumerable <FunctionDescriptor> functions = functionIndex.ReadAllDescriptors(); HostStartedMessage message = new HostStartedMessage { HostInstanceId = hostOutputMessage.HostInstanceId, HostDisplayName = hostOutputMessage.HostDisplayName, SharedQueueName = hostOutputMessage.SharedQueueName, InstanceQueueName = hostOutputMessage.InstanceQueueName, Heartbeat = hostOutputMessage.Heartbeat, WebJobRunIdentifier = hostOutputMessage.WebJobRunIdentifier, Functions = functions }; return(logger.LogHostStartedAsync(message, cancellationToken)); }
public Task LogHostStartedAsync(HostStartedMessage message, CancellationToken cancellationToken) { return Task.FromResult(0); }
public void ProcessHostStarted(HostStartedMessage message) { if (message == null) { throw new ArgumentNullException("message"); } string hostId = message.SharedQueueName; DateTimeOffset hostVersion = message.EnqueuedOn; DateTime hostVersionUtc = hostVersion.UtcDateTime; IEnumerable<FunctionDescriptor> messageFunctions = message.Functions ?? Enumerable.Empty<FunctionDescriptor>(); HostSnapshot newSnapshot = new HostSnapshot { HostVersion = hostVersion, FunctionIds = CreateFunctionIds(messageFunctions) }; if (_hostIndexManager.UpdateOrCreateIfLatest(hostId, newSnapshot)) { IEnumerable<VersionedMetadata> existingFunctions = _functionIndexManager.List(hostId); IEnumerable<VersionedMetadata> removedFunctions = existingFunctions .Where((f) => !newSnapshot.FunctionIds.Any(i => f.Id == i)); foreach (VersionedMetadata removedFunction in removedFunctions) { // Remove all functions no longer in our list (unless they exist with a later host version than // ours). string fullId = new FunctionIdentifier(hostId, removedFunction.Id).ToString(); _functionIndexManager.DeleteIfLatest(fullId, hostVersion, removedFunction.ETag, removedFunction.Version); } HeartbeatDescriptor heartbeat = message.Heartbeat; IEnumerable<FunctionDescriptor> addedFunctions = messageFunctions .Where((d) => !existingFunctions.Any(f => d.Id == f.Id)); foreach (FunctionDescriptor addedFunction in addedFunctions) { // Create any functions just appearing in our list (or update existing ones if they're earlier than // ours). FunctionSnapshot snapshot = CreateFunctionSnapshot(hostId, heartbeat, addedFunction, hostVersion); _functionIndexManager.CreateOrUpdateIfLatest(snapshot); } // Update any functions appearing in both lists provided they're still earlier than ours (or create them // if they've since been deleted). var possiblyUpdatedFunctions = existingFunctions .Join(messageFunctions, (f) => f.Id, (d) => d.Id, (f, d) => new { Descriptor = d, HostVersion = f.Version, ETag = f.ETag }); foreach (var possiblyUpdatedFunction in possiblyUpdatedFunctions) { FunctionSnapshot snapshot = CreateFunctionSnapshot(hostId, heartbeat, possiblyUpdatedFunction.Descriptor, hostVersion); _functionIndexManager.UpdateOrCreateIfLatest(snapshot, possiblyUpdatedFunction.ETag, possiblyUpdatedFunction.HostVersion); } } // Delete any functions we may have added or updated that are no longer in the index. // The create and update calls above may have occured after another instance started processing a later // version of this host. If that instance had already read the existing function list before we added the // function, it would think the function had already been deleted. In the end, we can't leave a function // around unless it's still in the host index after we've added or updated it. HostSnapshot finalSnapshot = _hostIndexManager.Read(hostId); IEnumerable<FunctionDescriptor> functionsRemovedAfterThisHostVersion; if (finalSnapshot == null) { functionsRemovedAfterThisHostVersion = messageFunctions; } else if (finalSnapshot.HostVersion.UtcDateTime > hostVersionUtc) { // Note that we base the list of functions to delete on what's in the HostStartedMessage, not what's in // the addedFunctions and possibleUpdatedFunctions variables, as this instance could have been aborted // and resumed and could lose state like those local variables. functionsRemovedAfterThisHostVersion = messageFunctions.Where( f => !finalSnapshot.FunctionIds.Any((i) => f.Id == i)); } else { functionsRemovedAfterThisHostVersion = Enumerable.Empty<FunctionDescriptor>(); } foreach (FunctionDescriptor functionNoLongerInSnapshot in functionsRemovedAfterThisHostVersion) { string fullId = new FunctionIdentifier(hostId, functionNoLongerInSnapshot.Id).ToString(); _functionIndexManager.DeleteIfLatest(fullId, hostVersionUtc); } _functionIndexVersionManager.UpdateOrCreateIfLatest(newSnapshot.HostVersion); }
public Task LogHostStartedAsync(HostStartedMessage message, CancellationToken cancellationToken) { return(_queueWriter.EnqueueAsync(message, cancellationToken)); }
public Task LogHostStartedAsync(HostStartedMessage message, CancellationToken cancellationToken) { return(Task.FromResult(0)); }
public void ProcessHostStarted(HostStartedMessage message) { if (message == null) { throw new ArgumentNullException("message"); } string hostId = message.SharedQueueName; DateTimeOffset hostVersion = message.EnqueuedOn; DateTime hostVersionUtc = hostVersion.UtcDateTime; IEnumerable <FunctionDescriptor> messageFunctions = message.Functions ?? Enumerable.Empty <FunctionDescriptor>(); HostSnapshot newSnapshot = new HostSnapshot { HostVersion = hostVersion, FunctionIds = CreateFunctionIds(messageFunctions) }; if (_hostIndexManager.UpdateOrCreateIfLatest(hostId, newSnapshot)) { IEnumerable <VersionedMetadata> existingFunctions = _functionIndexManager.List(hostId); IEnumerable <VersionedMetadata> removedFunctions = existingFunctions .Where((f) => !newSnapshot.FunctionIds.Any(i => f.Id == i)); foreach (VersionedMetadata removedFunction in removedFunctions) { // Remove all functions no longer in our list (unless they exist with a later host version than // ours). string fullId = new FunctionIdentifier(hostId, removedFunction.Id).ToString(); _functionIndexManager.DeleteIfLatest(fullId, hostVersion, removedFunction.ETag, removedFunction.Version); } HeartbeatDescriptor heartbeat = message.Heartbeat; IEnumerable <FunctionDescriptor> addedFunctions = messageFunctions .Where((d) => !existingFunctions.Any(f => d.Id == f.Id)); foreach (FunctionDescriptor addedFunction in addedFunctions) { // Create any functions just appearing in our list (or update existing ones if they're earlier than // ours). FunctionSnapshot snapshot = CreateFunctionSnapshot(hostId, heartbeat, addedFunction, hostVersion); _functionIndexManager.CreateOrUpdateIfLatest(snapshot); } // Update any functions appearing in both lists provided they're still earlier than ours (or create them // if they've since been deleted). var possiblyUpdatedFunctions = existingFunctions .Join(messageFunctions, (f) => f.Id, (d) => d.Id, (f, d) => new { Descriptor = d, HostVersion = f.Version, ETag = f.ETag }); foreach (var possiblyUpdatedFunction in possiblyUpdatedFunctions) { FunctionSnapshot snapshot = CreateFunctionSnapshot(hostId, heartbeat, possiblyUpdatedFunction.Descriptor, hostVersion); _functionIndexManager.UpdateOrCreateIfLatest(snapshot, possiblyUpdatedFunction.ETag, possiblyUpdatedFunction.HostVersion); } } // Delete any functions we may have added or updated that are no longer in the index. // The create and update calls above may have occured after another instance started processing a later // version of this host. If that instance had already read the existing function list before we added the // function, it would think the function had already been deleted. In the end, we can't leave a function // around unless it's still in the host index after we've added or updated it. HostSnapshot finalSnapshot = _hostIndexManager.Read(hostId); IEnumerable <FunctionDescriptor> functionsRemovedAfterThisHostVersion; if (finalSnapshot == null) { functionsRemovedAfterThisHostVersion = messageFunctions; } else if (finalSnapshot.HostVersion.UtcDateTime > hostVersionUtc) { // Note that we base the list of functions to delete on what's in the HostStartedMessage, not what's in // the addedFunctions and possibleUpdatedFunctions variables, as this instance could have been aborted // and resumed and could lose state like those local variables. functionsRemovedAfterThisHostVersion = messageFunctions.Where( f => !finalSnapshot.FunctionIds.Any((i) => f.Id == i)); } else { functionsRemovedAfterThisHostVersion = Enumerable.Empty <FunctionDescriptor>(); } foreach (FunctionDescriptor functionNoLongerInSnapshot in functionsRemovedAfterThisHostVersion) { string fullId = new FunctionIdentifier(hostId, functionNoLongerInSnapshot.Id).ToString(); _functionIndexManager.DeleteIfLatest(fullId, hostVersionUtc); } _functionIndexVersionManager.UpdateOrCreateIfLatest(newSnapshot.HostVersion); }
public void ProcessHostStarted_DeletesRemovedFunctionsIfLatest() { // Arrange const string hostId = "host"; DateTimeOffset hostVersion = DateTimeOffset.Now; string[] expectedFunctionIds = new string[] { "a", "c" }; IHostIndexManager hostIndexManager = CreateStubHostIndexManager( CreateHostSnapshot(hostVersion, expectedFunctionIds), persistSucceeds: true); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); DateTimeOffset earlierHostVersion = DateTimeOffset.MinValue; AddFunctionToIndex(functionIndexManager, hostId, expectedFunctionIds[0], earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, "b", earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, expectedFunctionIds[1], earlierHostVersion); IHostIndexer product = CreateProductUnderTest(hostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = hostVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = expectedFunctionIds[0] }, new FunctionDescriptor { Id = expectedFunctionIds[1] }, } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable<VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable<string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(expectedFunctionIds, functionIds); }
public void ProcessHostStarted_IfHostPreviouslyRemovedButProcessingAborted_DeletesKnownFunctionsIfLatest() { // Arrange const string hostId = "host"; DateTimeOffset hostVersion = DateTimeOffset.Now; string[] previouslyProcessedFunctionIds = new string[] { "a", "b", "c" }; IHostIndexManager concurrentlyRemoveFunctionsHostIndexManager = CreateStubHostIndexManager( existingSnapshot: null, persistSucceeds: false); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[0], hostVersion); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[1], hostVersion); AddFunctionToIndex(functionIndexManager, hostId, previouslyProcessedFunctionIds[2], hostVersion); IHostIndexer product = CreateProductUnderTest(concurrentlyRemoveFunctionsHostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = hostVersion, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = previouslyProcessedFunctionIds[0] }, new FunctionDescriptor { Id = previouslyProcessedFunctionIds[1] }, new FunctionDescriptor { Id = previouslyProcessedFunctionIds[2] } } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable<VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable<string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(new string[0], functionIds); }
public Task LogHostStartedAsync(HostStartedMessage message, CancellationToken cancellationToken) { return _queueWriter.EnqueueAsync(message, cancellationToken); }
private static Task LogHostStartedAsync(IFunctionIndex functionIndex, HostOutputMessage hostOutputMessage, IHostInstanceLogger logger, CancellationToken cancellationToken) { IEnumerable<FunctionDescriptor> functions = functionIndex.ReadAllDescriptors(); HostStartedMessage message = new HostStartedMessage { HostInstanceId = hostOutputMessage.HostInstanceId, HostDisplayName = hostOutputMessage.HostDisplayName, SharedQueueName = hostOutputMessage.SharedQueueName, InstanceQueueName = hostOutputMessage.InstanceQueueName, Heartbeat = hostOutputMessage.Heartbeat, WebJobRunIdentifier = hostOutputMessage.WebJobRunIdentifier, Functions = functions }; return logger.LogHostStartedAsync(message, cancellationToken); }
public void ProcessHostStarted_IfHostConcurrentlyUpdated_DeletesKnownFunctionsIfLatest() { // Arrange const string hostId = "host"; string[] originalFunctionIds = new string[] { "a", "b" }; string[] finalFunctionIds = new string[] { "b", "d" }; IHostIndexManager concurrentlyRemoveFunctionsHostIndexManager = CreateStubHostIndexManager( existingSnapshot: CreateHostSnapshot(DateTimeOffset.MaxValue, finalFunctionIds), persistSucceeds: true); IFunctionIndexManager functionIndexManager = CreateFakeFunctionIndexManager(); DateTimeOffset earlierHostVersion = DateTimeOffset.MinValue; AddFunctionToIndex(functionIndexManager, hostId, originalFunctionIds[0], earlierHostVersion); AddFunctionToIndex(functionIndexManager, hostId, originalFunctionIds[1], earlierHostVersion); IHostIndexer product = CreateProductUnderTest(concurrentlyRemoveFunctionsHostIndexManager, functionIndexManager); HostStartedMessage message = new HostStartedMessage { SharedQueueName = hostId, EnqueuedOn = DateTimeOffset.Now, Functions = new FunctionDescriptor[] { new FunctionDescriptor { Id = originalFunctionIds[0] }, new FunctionDescriptor { Id = originalFunctionIds[1] }, new FunctionDescriptor { Id = "c" }, new FunctionDescriptor { Id = "d" } } }; // Act product.ProcessHostStarted(message); // Assert IEnumerable<VersionedMetadata> functions = functionIndexManager.List(hostId); Assert.NotNull(functions); // Guard IEnumerable<string> functionIds = functions.Select(f => f.Id).ToArray(); Assert.Equal(finalFunctionIds, functionIds); }