Exemplo n.º 1
0
        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);
        }
Exemplo n.º 3
0
        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);
        }
Exemplo n.º 4
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();
            }
        }
Exemplo n.º 5
0
        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");
        }
Exemplo n.º 6
0
        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);
                }
            }
        }
Exemplo n.º 10
0
        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();
                }
            }
        }