public async Task CountDocumentsInCollection_TwoHosts() { await this.InitializeDocumentsAsync(); int partitionKeyRangeCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); Assert.True(partitionKeyRangeCount > 1, "Prerequisite failed: expected monitored collection with at least 2 partitions."); int processedCount = 0; var allDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var host1 = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true }, new ChangeFeedHostOptions { MaxPartitionCount = partitionKeyRangeCount / 2 }); await host1.RegisterObserverFactoryAsync(observerFactory); var host2 = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true }, new ChangeFeedHostOptions { MaxPartitionCount = partitionKeyRangeCount - partitionKeyRangeCount / 2 }); await host2.RegisterObserverFactoryAsync(observerFactory); allDocsProcessed.WaitOne(changeWaitTimeout + changeWaitTimeout); try { Assert.True(documentCount == processedCount, $"Wrong processedCount {documentCount} {processedCount}"); } finally { await host1.UnregisterObserversAsync(); await host2.UnregisterObserversAsync(); } }
public async Task WhenLeasesHaveContinuationTokenNullReturn0() { // Cleanup the test collection to avoid other tests' documents causing issues with StartFromBeginning await this.ResetTestCollection(); int documentCount = 1; int partitionCount = await IntegrationTestsHelper.GetPartitionCount(this.ClassData.monitoredCollectionInfo); int openedCount = 0, processedCount = 0; var allObserversStarted = new ManualResetEvent(false); var allDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( context => { int newCount = Interlocked.Increment(ref openedCount); if (newCount == partitionCount) { allObserversStarted.Set(); } return(Task.CompletedTask); }, null, (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var hostName = Guid.NewGuid().ToString(); // We create a host to initialize the leases with ContinuationToken null var host = new ChangeFeedEventHost( hostName, this.ClassData.monitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = false }, new ChangeFeedHostOptions()); // Initialize leases await host.RegisterObserverFactoryAsync(observerFactory); // Stop host, this leaves the leases with ContinuationToken null state await host.UnregisterObserversAsync(); // Since the leases have ContinuationToken null state, the estimator will use StartFromBeginning and pick-up the changes that happened from the start long estimation = await host.GetEstimatedRemainingWork(); Assert.Equal(0, estimation); }
public async Task Schema_DefaultsToNoLeaseToken() { TestObserverFactory observerFactory = new TestObserverFactory( openProcessor: null, (FeedProcessing.IChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { return(Task.CompletedTask); }); IChangeFeedProcessor changeFeedProcessorBuilder = await new ChangeFeedProcessorBuilder() .WithObserverFactory(observerFactory) .WithHostName("smoke_test") .WithFeedCollection(this.MonitoredCollectionInfo) .WithLeaseCollection(this.LeaseCollectionInfo) .BuildAsync(); await changeFeedProcessorBuilder.StartAsync(); await this.WaitUntilLeaseStoreIsInitializedAsync(new CancellationTokenSource(5000).Token); await changeFeedProcessorBuilder.StopAsync(); // Verify that no leases have LeaseToken (V3 contract) int leasesProcessed = 0; using (DocumentClient client = new DocumentClient(this.LeaseCollectionInfo.Uri, this.LeaseCollectionInfo.MasterKey, this.LeaseCollectionInfo.ConnectionPolicy)) { Uri collectionUri = UriFactory.CreateDocumentCollectionUri(this.LeaseCollectionInfo.DatabaseName, this.LeaseCollectionInfo.CollectionName); IDocumentQuery <JObject> query = client.CreateDocumentQuery <JObject>(collectionUri, "SELECT * FROM c").AsDocumentQuery(); while (query.HasMoreResults) { foreach (JObject lease in await query.ExecuteNextAsync()) { string leaseId = lease.Value <string>("id"); if (leaseId.Contains(".info")) { // These are the store initialization marks continue; } Assert.NotNull(lease.Value <string>("PartitionId")); Assert.Null(lease.Value <string>("LeaseToken")); leasesProcessed++; } } } Assert.True(leasesProcessed > 0); }
public async Task CountDocumentsInCollection_ProcessChangesThrows() { await this.InitializeDocumentsAsync(); int processedCount = 0; var allDocsProcessed = new ManualResetEvent(false); bool isFirstChangeNotification = false; // Make sure there was at least one throw. int throwCount = 0; var observerFactory = new TestObserverFactory((ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { bool shouldThrow = (isFirstChangeNotification || new Random().Next(0, 1) == 1) && throwCount < 10; isFirstChangeNotification = false; if (shouldThrow) { Interlocked.Increment(ref throwCount); throw new Exception("Error injection exception from observer!"); } int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); allDocsProcessed.WaitOne(changeWaitTimeout + changeWaitTimeout + changeWaitTimeout); try { Assert.Equal(documentCount, processedCount); } finally { await host.UnregisterObserversAsync(); } }
public async Task CountDocumentsInCollection_NormalCase() { await this.InitializeDocumentsAsync(); int partitionKeyRangeCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); int openedCount = 0, closedCount = 0, processedCount = 0; var allDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( context => { Interlocked.Increment(ref openedCount); return(Task.CompletedTask); }, (context, reason) => { Interlocked.Increment(ref closedCount); return(Task.CompletedTask); }, (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); allDocsProcessed.WaitOne(changeWaitTimeout + changeWaitTimeout); try { Assert.True(partitionKeyRangeCount == openedCount, "Wrong openedCount"); Assert.True(documentCount == processedCount, $"Wrong processedCount {documentCount} {processedCount}"); } finally { await host.UnregisterObserversAsync(); } Assert.True(partitionKeyRangeCount == closedCount, "Wrong closedCount"); }
public async Task StopAtFullSpeed() { int partitionKeyRangeCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); int openedCount = 0, closedCount = 0, processedCount = 0; var quarterDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( context => { Interlocked.Increment(ref openedCount); return(Task.CompletedTask); }, (context, reason) => { Interlocked.Increment(ref closedCount); return(Task.CompletedTask); }, (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount >= documentCount / 4) { quarterDocsProcessed.Set(); } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true, MaxItemCount = 2 }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); quarterDocsProcessed.WaitOne(changeWaitTimeout + changeWaitTimeout); await host.UnregisterObserversAsync(); Assert.True(partitionKeyRangeCount == openedCount, "Wrong closedCount"); Assert.True(partitionKeyRangeCount == closedCount, "Wrong closedCount"); }
public async Task TestStartTime() { var collectionUri = UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName); using (var client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { await client.CreateDocumentAsync(collectionUri, JsonConvert.DeserializeObject("{\"id\": \"doc1\"}")); // In worst case (long transaction, heavy load, the atomicity of StartTime is 5 sec). // For this case (different transactions) it's OK to wait timestamp precision time. await Task.Delay(TimeSpan.FromSeconds(1)); DateTime timeInBeweeen = DateTime.Now; await Task.Delay(TimeSpan.FromSeconds(1)); await client.CreateDocumentAsync(collectionUri, JsonConvert.DeserializeObject("{\"id\": \"doc2\"}")); int partitionCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); var allDocsProcessed = new ManualResetEvent(false); var processedDocs = new List <Document>(); var observerFactory = new TestObserverFactory( null, null, (context, docs) => { processedDocs.AddRange(docs); foreach (var doc in docs) { if (doc.Id == "doc2") { allDocsProcessed.Set(); } } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartTime = timeInBeweeen }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); var isStartOk = allDocsProcessed.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout); try { Assert.True(isStartOk, "Timed out waiting for docs to process"); Assert.True(1 == processedDocs.Count, "Wrong processed count"); Assert.True("doc2" == processedDocs[0].Id, "Wrong doc.id"); } finally { await host.UnregisterObserversAsync(); } } }
public async Task CountAddedDocuments() { int partitionCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); int openedCount = 0, processedCount = 0; var allObserversStarted = new ManualResetEvent(false); var allDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( context => { int newCount = Interlocked.Increment(ref openedCount); if (newCount == partitionCount) { allObserversStarted.Set(); } return(Task.CompletedTask); }, null, (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = false }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); var isStartOk = allObserversStarted.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout); Assert.True(isStartOk, "Timed out waiting for observres to start"); using (var client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { await IntegrationTestsHelper.CreateDocumentsAsync( client, UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName), documentCount); } allDocsProcessed.WaitOne(IntegrationTest.changeWaitTimeout); try { Assert.True(documentCount == processedCount, "Wrong processedCount"); } finally { await host.UnregisterObserversAsync(); } }
public async Task TestReducePageSizeScenario() { // Use different colleciton: we need 1-partition collection to make sure all docs get to same partition. var databaseUri = UriFactory.CreateDatabaseUri(this.MonitoredCollectionInfo.DatabaseName); DocumentCollectionInfo monitoredCollectionInfo = new DocumentCollectionInfo(this.MonitoredCollectionInfo); monitoredCollectionInfo.CollectionName = this.MonitoredCollectionInfo.CollectionName + "_" + Guid.NewGuid().ToString(); var collectionUri = UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, monitoredCollectionInfo.CollectionName); var monitoredCollection = new DocumentCollection { Id = monitoredCollectionInfo.CollectionName }; using (var client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { await client.CreateDocumentCollectionAsync(databaseUri, monitoredCollection, new RequestOptions { OfferThroughput = 10000 }); try { // Create some docs to make sure that one separate response is returned for 1st execute of query before retries. // These are to make sure continuation token is passed along during retries. var sproc = new StoredProcedure { Id = "createTwoDocs", Body = @"function(startIndex) { for (var i = 0; i < 2; ++i) __.createDocument( __.getSelfLink(), { id: 'doc' + (i + startIndex).toString(), value: 'y'.repeat(1500000) }, err => { if (err) throw err;} );}" }; var sprocUri = UriFactory.CreateStoredProcedureUri(this.MonitoredCollectionInfo.DatabaseName, monitoredCollection.Id, sproc.Id); await client.CreateStoredProcedureAsync(collectionUri, sproc); await client.ExecuteStoredProcedureAsync <object>(sprocUri, 0); // Create 3 docs each 1.5MB. All 3 do not fit into MAX_RESPONSE_SIZE (4 MB). 2nd and 3rd are in same transaction. var content = string.Format("{{\"id\": \"doc2\", \"value\": \"{0}\"}}", new string('x', 1500000)); await client.CreateDocumentAsync(collectionUri, JsonConvert.DeserializeObject(content)); await client.ExecuteStoredProcedureAsync <object>(sprocUri, 3); var allDocsProcessed = new ManualResetEvent(false); int processedDocCount = 0; string accumulator = string.Empty; var observerFactory = new TestObserverFactory( null, null, (context, docs) => { processedDocCount += docs.Count; foreach (var doc in docs) { accumulator += doc.Id.ToString() + "."; } if (processedDocCount == 5) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var host = new ChangeFeedEventHost( Guid.NewGuid().ToString(), monitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = true, MaxItemCount = 6 }, new ChangeFeedHostOptions()); await host.RegisterObserverFactoryAsync(observerFactory); var isStartOk = allDocsProcessed.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout); try { Assert.True(isStartOk, "Timed out waiting for docs to process"); Assert.Equal("doc0.doc1.doc2.doc3.doc4.", accumulator); } finally { await host.UnregisterObserversAsync(); } } finally { await client.DeleteDocumentCollectionAsync(collectionUri); } } }
public async Task Schema_OnV2MigrationMaintainLeaseToken() { const int batchSize = 10; int partitionCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); int openedCount = 0; ManualResetEvent allObserversStarted = new ManualResetEvent(false); List <int> expectedIds = Enumerable.Range(0, batchSize * 2).ToList(); ManualResetEvent firstSetOfResultsProcessed = new ManualResetEvent(false); ManualResetEvent secondSetOfResultsProcessed = new ManualResetEvent(false); List <int> receivedIds = new List <int>(); TestObserverFactory observerFactory = new TestObserverFactory( context => { int newCount = Interlocked.Increment(ref openedCount); if (newCount == partitionCount) { allObserversStarted.Set(); } return(Task.CompletedTask); }, (FeedProcessing.IChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { foreach (Document doc in docs) { receivedIds.Add(int.Parse(doc.Id)); } if (receivedIds.Count == batchSize) { firstSetOfResultsProcessed.Set(); } if (receivedIds.Count == batchSize * 2) { secondSetOfResultsProcessed.Set(); } return(Task.CompletedTask); }); IChangeFeedProcessor changeFeedProcessorBuilder = await new ChangeFeedProcessorBuilder() .WithObserverFactory(observerFactory) .WithHostName("smoke_test") .WithFeedCollection(this.MonitoredCollectionInfo) .WithLeaseCollection(this.LeaseCollectionInfo) .BuildAsync(); await changeFeedProcessorBuilder.StartAsync(); await this.WaitUntilLeaseStoreIsInitializedAsync(new CancellationTokenSource(5000).Token); // Inserting some documents using (DocumentClient client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { Uri collectionUri = UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName); foreach (int id in expectedIds.Take(10)) { await client.CreateDocumentAsync(collectionUri, new { id = id.ToString() }); } } Assert.True(firstSetOfResultsProcessed.WaitOne(IntegrationTest.changeWaitTimeout), "Timed out waiting for first set of items to be received."); await changeFeedProcessorBuilder.StopAsync(); // At this point we have leases for V2, so we will simulate V3 by manually adding LeaseToken and removing PartitionId using (DocumentClient client = new DocumentClient(this.LeaseCollectionInfo.Uri, this.LeaseCollectionInfo.MasterKey, this.LeaseCollectionInfo.ConnectionPolicy)) { Uri collectionUri = UriFactory.CreateDocumentCollectionUri(this.LeaseCollectionInfo.DatabaseName, this.LeaseCollectionInfo.CollectionName); IDocumentQuery <JObject> query = client.CreateDocumentQuery <JObject>(collectionUri, "SELECT * FROM c").AsDocumentQuery(); while (query.HasMoreResults) { foreach (JObject lease in await query.ExecuteNextAsync()) { string leaseId = lease.Value <string>("id"); if (leaseId.Contains(".info")) { // These are the store initialization marks continue; } // create the LeaseToken property lease.Add("LeaseToken", lease.Value <string>("PartitionId")); lease.Remove("PartitionId"); await client.UpsertDocumentAsync(collectionUri, lease); } } } // Now all leases are V3 leases, start another processor that should migrate to V2 schema and maintain LeaseToken for compatibility openedCount = 0; allObserversStarted.Reset(); changeFeedProcessorBuilder = await new ChangeFeedProcessorBuilder() .WithObserverFactory(observerFactory) .WithHostName("smoke_test") .WithFeedCollection(this.MonitoredCollectionInfo) .WithLeaseCollection(this.LeaseCollectionInfo) .BuildAsync(); await changeFeedProcessorBuilder.StartAsync(); Assert.True(allObserversStarted.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout), "Timed out waiting for observres to start"); // Create the rest of the documents using (DocumentClient client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { Uri collectionUri = UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName); foreach (int id in expectedIds.TakeLast(10)) { await client.CreateDocumentAsync(collectionUri, new { id = id.ToString() }); } } Assert.True(secondSetOfResultsProcessed.WaitOne(IntegrationTest.changeWaitTimeout), "Timed out waiting for second set of items to be received."); await changeFeedProcessorBuilder.StopAsync(); // Verify we processed all items (including when using the V3 leases) Assert.True(!expectedIds.Except(receivedIds).Any() && expectedIds.Count == expectedIds.Count); // Verify the after-migration leases have both PartitionId and LeaseToken with the same value using (DocumentClient client = new DocumentClient(this.LeaseCollectionInfo.Uri, this.LeaseCollectionInfo.MasterKey, this.LeaseCollectionInfo.ConnectionPolicy)) { Uri collectionUri = UriFactory.CreateDocumentCollectionUri(this.LeaseCollectionInfo.DatabaseName, this.LeaseCollectionInfo.CollectionName); IDocumentQuery <JObject> query = client.CreateDocumentQuery <JObject>(collectionUri, "SELECT * FROM c").AsDocumentQuery(); while (query.HasMoreResults) { foreach (JObject lease in await query.ExecuteNextAsync()) { string leaseId = lease.Value <string>("id"); if (leaseId.Contains(".info")) { // These are the store initialization marks continue; } Assert.NotNull(lease.Value <string>("PartitionId")); Assert.Null(lease.Value <string>("LeaseToken")); } } } }
public async Task CountPendingDocuments() { int documentCount = 1; int partitionCount = await IntegrationTestsHelper.GetPartitionCount(this.MonitoredCollectionInfo); int openedCount = 0, processedCount = 0; var allObserversStarted = new ManualResetEvent(false); var allDocsProcessed = new ManualResetEvent(false); var observerFactory = new TestObserverFactory( context => { int newCount = Interlocked.Increment(ref openedCount); if (newCount == partitionCount) { allObserversStarted.Set(); } return(Task.CompletedTask); }, null, (ChangeFeedObserverContext context, IReadOnlyList <Document> docs) => { int newCount = Interlocked.Add(ref processedCount, docs.Count); if (newCount == documentCount) { allDocsProcessed.Set(); } return(Task.CompletedTask); }); var hostName = Guid.NewGuid().ToString(); var host = new ChangeFeedEventHost( hostName, this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = false }, new ChangeFeedHostOptions()); // Initialize leases await host.RegisterObserverFactoryAsync(observerFactory); // Verify that 0 is returned on empty collection long estimation = await host.GetEstimatedRemainingWork(); Assert.Equal(0, estimation); using (var client = new DocumentClient(this.MonitoredCollectionInfo.Uri, this.MonitoredCollectionInfo.MasterKey, this.MonitoredCollectionInfo.ConnectionPolicy)) { await IntegrationTestsHelper.CreateDocumentsAsync( client, UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName), 1); var isStartOk = allObserversStarted.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout); Assert.True(isStartOk, "Timed out waiting for observer to start"); allDocsProcessed.WaitOne(IntegrationTest.changeWaitTimeout); // Halt the processor temporarily await host.UnregisterObserversAsync(); estimation = await host.GetEstimatedRemainingWork(); Assert.Equal(0, estimation); await IntegrationTestsHelper.CreateDocumentsAsync( client, UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName), 1); estimation = await host.GetEstimatedRemainingWork(); Assert.Equal(1, estimation); await IntegrationTestsHelper.CreateDocumentsAsync( client, UriFactory.CreateDocumentCollectionUri(this.MonitoredCollectionInfo.DatabaseName, this.MonitoredCollectionInfo.CollectionName), 10); estimation = await host.GetEstimatedRemainingWork(); Assert.Equal(11, estimation); // Create a new host to process pending changes var newHost = new ChangeFeedEventHost( hostName, this.MonitoredCollectionInfo, this.LeaseCollectionInfo, new ChangeFeedOptions { StartFromBeginning = false }, new ChangeFeedHostOptions()); openedCount = 0; processedCount = 0; allObserversStarted.Reset(); allDocsProcessed.Reset(); await newHost.RegisterObserverFactoryAsync(observerFactory); isStartOk = allObserversStarted.WaitOne(IntegrationTest.changeWaitTimeout + IntegrationTest.changeWaitTimeout); Assert.True(isStartOk, "Timed out waiting for observer to start"); allDocsProcessed.WaitOne(IntegrationTest.changeWaitTimeout); try { estimation = await newHost.GetEstimatedRemainingWork(); Assert.Equal(0, estimation); } finally { await newHost.UnregisterObserversAsync(); } } }