Esempio n. 1
0
        private async Task <long> GetRemainingWorkAsync(ILease existingLease)
        {
            ChangeFeedOptions options = new ChangeFeedOptions
            {
                MaxItemCount        = 1,
                PartitionKeyRangeId = existingLease.PartitionId,
                RequestContinuation = existingLease.ContinuationToken,
                StartFromBeginning  = string.IsNullOrEmpty(existingLease.ContinuationToken),
            };
            IChangeFeedDocumentQuery <Document> query    = this.feedDocumentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options);
            IFeedResponse <Document>            response = null;

            try
            {
                response = await query.ExecuteNextAsync <Document>().ConfigureAwait(false);

                long parsedLSNFromSessionToken = TryConvertToNumber(ExtractLsnFromSessionToken(response.SessionToken));
                long lastQueryLSN = response.Count > 0
                    ? TryConvertToNumber(GetFirstDocument(response).GetPropertyValue <string>(LSNPropertyName)) - 1
                    : parsedLSNFromSessionToken;
                if (lastQueryLSN < 0)
                {
                    return(1);
                }

                long partitionRemainingWork = parsedLSNFromSessionToken - lastQueryLSN;
                return(partitionRemainingWork < 0 ? 0 : partitionRemainingWork);
            }
            catch (Exception clientException)
            {
                Logger.WarnException($"GetEstimateWork > exception: partition '{existingLease.PartitionId}'", clientException);
                throw;
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost"/> class.
        /// </summary>
        /// <param name="hostName">Unique name for this host.</param>
        /// <param name="documentCollectionLocation">Specifies location of the DocumentDB collection to monitor changes for.</param>
        /// <param name="auxCollectionLocation">Specifies location of auxiliary data for load-balancing instances of <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost" />.</param>
        /// <param name="changeFeedOptions">Options to pass to the Microsoft.AzureDocuments.DocumentClient.CreateChangeFeedQuery API.</param>
        /// <param name="hostOptions">Additional options to control load-balancing of <see cref="DocumentDB.ChangeFeedProcessor.ChangeFeedEventHost" /> instances.</param>
        public ChangeFeedEventHost(
            string hostName,
            DocumentCollectionInfo documentCollectionLocation,
            DocumentCollectionInfo auxCollectionLocation,
            ChangeFeedOptions changeFeedOptions,
            ChangeFeedHostOptions hostOptions)
        {
            if (documentCollectionLocation == null)
            {
                throw new ArgumentException("documentCollectionLocation");
            }
            if (documentCollectionLocation.Uri == null)
            {
                throw new ArgumentException("documentCollectionLocation.Uri");
            }
            if (string.IsNullOrWhiteSpace(documentCollectionLocation.DatabaseName))
            {
                throw new ArgumentException("documentCollectionLocation.DatabaseName");
            }
            if (string.IsNullOrWhiteSpace(documentCollectionLocation.CollectionName))
            {
                throw new ArgumentException("documentCollectionLocation.CollectionName");
            }
            if (hostOptions.MinPartitionCount > hostOptions.MaxPartitionCount)
            {
                throw new ArgumentException("hostOptions.MinPartitionCount cannot be greater than hostOptions.MaxPartitionCount");
            }

            this.collectionLocation             = CanoninicalizeCollectionInfo(documentCollectionLocation);
            this.changeFeedOptions              = changeFeedOptions;
            this.options                        = hostOptions;
            this.HostName                       = hostName;
            this.auxCollectionLocation          = CanoninicalizeCollectionInfo(auxCollectionLocation);
            this.partitionKeyRangeIdToWorkerMap = new ConcurrentDictionary <string, WorkerData>();
        }
Esempio n. 3
0
        public async Task StartAsync_Retries()
        {
            var mockExecutor = new Mock <ITriggeredFunctionExecutor>();
            var collInfo     = new DocumentCollectionInfo {
                Uri = new Uri("http://fakeaccount"), MasterKey = "c29tZV9rZXk=", DatabaseName = "FakeDb", CollectionName = "FakeColl"
            };
            var leaseInfo = new DocumentCollectionInfo {
                Uri = new Uri("http://fakeaccount"), MasterKey = "c29tZV9rZXk=", DatabaseName = "FakeDb", CollectionName = "leases"
            };
            var hostOptions = new ChangeFeedHostOptions();
            var feedOptions = new ChangeFeedOptions();

            var listener = new MockListener(mockExecutor.Object, collInfo, leaseInfo, hostOptions, feedOptions, NullLogger.Instance);

            // Ensure that we can call StartAsync() multiple times to retry if there is an error.
            for (int i = 0; i < 3; i++)
            {
                var ex = await Assert.ThrowsAnyAsync <InvalidOperationException>(() => listener.StartAsync(CancellationToken.None));

                Assert.Equal("Failed to register!", ex.Message);
            }

            // This should succeed
            await listener.StartAsync(CancellationToken.None);
        }
        public static async Task StartChangeFeedHost()
        {
            var config = ServiceLocator.GetService <IConfiguration>();

            string hostName = config.MonitoredCollectionName + "_monitor_" + Guid.NewGuid().ToString();
            DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo
            {
                Uri            = config.DatabaseAccountUri,
                MasterKey      = config.DatabaseAccountKey,
                DatabaseName   = config.MonitoredDatabaseName,
                CollectionName = config.MonitoredCollectionName
            };
            DocumentCollectionInfo leaseCollectionLocation = new DocumentCollectionInfo
            {
                Uri            = config.DatabaseAccountUri,
                MasterKey      = config.DatabaseAccountKey,
                DatabaseName   = config.ChangeTrackingDatabaseName,
                CollectionName = config.ChangeTrackingLeaseCollectionName
            };

            Console.WriteLine("Main program: Creating ChangeFeedEventHost...");

            ChangeFeedOptions     feedOptions     = new ChangeFeedOptions();
            ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions();



            ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, feedOptions, feedHostOptions);
            await host.RegisterObserverAsync <DocumentFeedObserver>();

            Console.WriteLine("Main program: press Enter to stop...");
            Console.ReadLine();
            await host.UnregisterObserversAsync();
        }
Esempio n. 5
0
        public ChangeFeedQuery(DocumentClient client, ResourceType resourceType, string resourceLink, ChangeFeedOptions feedOptions)
        {
            Debug.Assert(client != null);

            this.client       = client;
            this.resourceType = resourceType;
            this.resourceLink = resourceLink;
            this.feedOptions  = feedOptions ?? new ChangeFeedOptions();

            if (feedOptions.PartitionKey != null && !string.IsNullOrEmpty(feedOptions.PartitionKeyRangeId))
            {
                throw new ArgumentException(RMResources.PartitionKeyAndPartitionKeyRangeRangeIdBothSpecified, "feedOptions");
            }

            bool canUseStartFromBeginning = true;

            if (feedOptions.RequestContinuation != null)
            {
                this.nextIfNoneMatch     = feedOptions.RequestContinuation;
                canUseStartFromBeginning = false;
            }

            if (feedOptions.StartTime.HasValue)
            {
                this.ifModifiedSince     = this.ConvertToHttpTime(feedOptions.StartTime.Value);
                canUseStartFromBeginning = false;
            }

            if (canUseStartFromBeginning && !feedOptions.StartFromBeginning)
            {
                this.nextIfNoneMatch = IfNoneMatchAllHeaderValue;
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Registers change feed observer to update changes read on change feed to destination
        /// collection. Deregisters change feed observer and closes process when enter key is pressed
        /// </summary>
        /// <returns>A Task to allow asynchronous execution</returns>
        public async Task RunChangeFeedHostAsync()
        {
            string hostName = Guid.NewGuid().ToString();

            // monitored collection info
            DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo
            {
                Uri            = new Uri(this.monitoredUri),
                MasterKey      = this.monitoredSecretKey,
                DatabaseName   = this.monitoredDbName,
                CollectionName = this.monitoredCollectionName
            };

            // lease collection info
            DocumentCollectionInfo leaseCollectionLocation = new DocumentCollectionInfo
            {
                Uri            = new Uri(this.leaseUri),
                MasterKey      = this.leaseSecretKey,
                DatabaseName   = this.leaseDbName,
                CollectionName = this.leaseCollectionName
            };

            // destination collection info
            DocumentCollectionInfo destCollInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(this.destUri),
                MasterKey      = this.destSecretKey,
                DatabaseName   = this.destDbName,
                CollectionName = this.destCollectionName
            };

            // Customizable change feed option and host options
            ChangeFeedOptions feedOptions = new ChangeFeedOptions();

            // ie customize StartFromBeginning so change feed reads from beginning
            // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning
            feedOptions.StartFromBeginning = true;


            ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions();

            // ie. customizing lease renewal interval to 15 seconds
            // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
            feedHostOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15);

            using (DocumentClient destClient = new DocumentClient(destCollInfo.Uri, destCollInfo.MasterKey))
            {
                DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(destClient, destCollInfo);

                ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, feedOptions, feedHostOptions);

                await host.RegisterObserverFactoryAsync(docObserverFactory);

                Console.WriteLine("Running... Press enter to stop.");
                Console.ReadLine();

                await host.UnregisterObserversAsync();
            }
        }
 public PartitionProcessor(IChangeFeedObserver observer, IChangeFeedDocumentQuery <Document> query, ChangeFeedOptions options, ProcessorSettings settings, IPartitionCheckpointer checkpointer)
 {
     this.observer     = observer;
     this.settings     = settings;
     this.checkpointer = checkpointer;
     this.options      = options;
     this.query        = query;
 }
 public CosmosDBTriggerBinding(ParameterInfo parameter, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, ChangeFeedOptions changeFeedOptions, ILogger logger)
 {
     _documentCollectionLocation = documentCollectionLocation;
     _leaseCollectionLocation    = leaseCollectionLocation;
     _leaseHostOptions           = leaseHostOptions;
     _parameter         = parameter;
     _logger            = logger;
     _changeFeedOptions = changeFeedOptions;
 }
        public CosmosDBTriggerListener(ITriggeredFunctionExecutor executor, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, ChangeFeedOptions changeFeedOptions, ILogger logger)
        {
            this._logger   = logger;
            this._executor = executor;
            this._hostName = Guid.NewGuid().ToString();

            this._monitorCollection = documentCollectionLocation;
            this._leaseCollection   = leaseCollectionLocation;
            this._leaseHostOptions  = leaseHostOptions;
            this._changeFeedOptions = changeFeedOptions;
        }
Esempio n. 10
0
        /// <summary>
        /// Registers change feed observer to update changes read on change feed to destination
        /// collection. Deregisters change feed observer and closes process when enter key is pressed
        /// </summary>
        /// <returns>A Task to allow asynchronous execution</returns>

        public async Task RunChangeFeedHostAsync()
        {
            string hostName = Guid.NewGuid().ToString();

            // monitored collection info
            DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(this.monitoredUri),
                MasterKey      = this.monitoredSecretKey,
                DatabaseName   = this.monitoredDbName,
                CollectionName = this.monitoredCollectionName
            };


            DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(this.leaseUri),
                MasterKey      = this.leaseSecretKey,
                DatabaseName   = this.leaseDbName,
                CollectionName = this.leaseCollectionName
            };
            DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory();
            ChangeFeedOptions           feedOptions        = new ChangeFeedOptions();

            /* ie customize StartFromBeginning so change feed reads from beginning
             *  can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning
             */

            feedOptions.StartFromBeginning = true;

            ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions();

            // ie. customizing lease renewal interval to 15 seconds
            // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
            feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15);
            ChangeFeedProcessorBuilder builder = new ChangeFeedProcessorBuilder();

            builder
            .WithHostName(hostName)
            .WithFeedCollection(documentCollectionInfo)
            .WithLeaseCollection(leaseCollectionInfo)
            .WithProcessorOptions(feedProcessorOptions)
            .WithObserverFactory(new DocumentFeedObserverFactory());

            //    .WithObserver<DocumentFeedObserver>();  or just pass a observer

            var result = await builder.BuildAsync();

            await result.StartAsync();

            Console.Read();
            await result.StopAsync();
        }
Esempio n. 11
0
        /// <summary>
        /// Registers change feed observer to update changes read on change feed to destination
        /// collection. Deregisters change feed observer and closes process when enter key is pressed
        /// </summary>
        /// <returns>A Task to allow asynchronous execution</returns>
        public async Task RunChangeFeed()
        {
            string hostName = Guid.NewGuid().ToString();

            // monitored collection info
            DocumentCollectionInfo starshipCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(ConfigurationManager.AppSettings["SqlApi.EndpointUrl"]),
                MasterKey      = ConfigurationManager.AppSettings["SqlApi.AuthorizationKey"],
                DatabaseName   = ConfigurationManager.AppSettings["SqlApi.DatabaseName"],
                CollectionName = ConfigurationManager.AppSettings["SqlApi.StarshipCollectionName"]
            };

            // lease collection info
            DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(ConfigurationManager.AppSettings["SqlApi.EndpointUrl"]),
                MasterKey      = ConfigurationManager.AppSettings["SqlApi.AuthorizationKey"],
                DatabaseName   = ConfigurationManager.AppSettings["SqlApi.DatabaseName"],
                CollectionName = ConfigurationManager.AppSettings["SqlApi.LeaseCollectionName"]
            };

            await CreateCollectionIfNotExistsAsync(starshipCollectionInfo);
            await CreateCollectionIfNotExistsAsync(leaseCollectionInfo);

            // Customizable change feed option and host options
            ChangeFeedOptions feedOptions = new ChangeFeedOptions();

            // ie customize StartFromBeginning so change feed reads from beginning
            // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning
            feedOptions.StartFromBeginning = true;

            ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions();

            // ie. customizing lease renewal interval to 15 seconds
            // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
            feedHostOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15);

            using (DocumentClient client = new DocumentClient(starshipCollectionInfo.Uri, starshipCollectionInfo.MasterKey))
            {
                DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(client, starshipCollectionInfo);

                ChangeFeedEventHost host = new ChangeFeedEventHost(hostName, starshipCollectionInfo, leaseCollectionInfo, feedOptions, feedHostOptions);

                await host.RegisterObserverFactoryAsync(docObserverFactory).ConfigureAwait(false);

                Console.WriteLine("Running... Press enter to stop.");
                Console.ReadLine();

                await host.UnregisterObserversAsync();
            }
        }
Esempio n. 12
0
        public async Task <long> GetEstimatedRemainingWork()
        {
            long remainingWork      = 0;
            bool hasAtLeastOneLease = false;

            ChangeFeedOptions options = new ChangeFeedOptions
            {
                MaxItemCount = 1,
            };

            foreach (ILease existingLease in await this.leaseManager.ListAllLeasesAsync().ConfigureAwait(false))
            {
                hasAtLeastOneLease          = true;
                options.PartitionKeyRangeId = existingLease.PartitionId;
                options.RequestContinuation = existingLease.ContinuationToken;
                options.StartFromBeginning  = string.IsNullOrEmpty(existingLease.ContinuationToken);

                IChangeFeedDocumentQuery <Document> query    = this.feedDocumentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options);
                IFeedResponse <Document>            response = null;

                try
                {
                    response = await query.ExecuteNextAsync <Document>().ConfigureAwait(false);

                    long parsedLSNFromSessionToken = TryConvertToNumber(ExtractLSNFromSessionToken(response.SessionToken));
                    long lastQueryLSN = response.Count > 0 ?
                                        TryConvertToNumber(GetFirstDocument(response).GetPropertyValue <string>(LSNPropertyName)) - 1
                        : parsedLSNFromSessionToken;
                    if (lastQueryLSN < 0)
                    {
                        // Could not parse LSN from document, we cannot determine the amount of changes but since the query returned 1 document, we know it's at least 1
                        remainingWork += 1;
                        continue;
                    }

                    long partitionRemainingWork = parsedLSNFromSessionToken - lastQueryLSN;
                    remainingWork += partitionRemainingWork < 0 ? 0 : partitionRemainingWork;
                }
                catch (DocumentClientException clientException)
                {
                    Logger.WarnException("GetEstimateWork > exception: partition '{0}'", clientException, existingLease.PartitionId);
                }
            }

            if (!hasAtLeastOneLease)
            {
                return(1);
            }

            return(remainingWork);
        }
        /// <summary>
        /// Asynchronously checks the current existing leases and calculates an estimate of remaining work per leased partitions.
        /// </summary>
        /// <returns>An estimate amount of remaining documents to be processed</returns>
        public async Task <long> GetEstimatedRemainingWork()
        {
            await this.InitializeAsync();

            long remaining            = 0;
            ChangeFeedOptions options = new ChangeFeedOptions
            {
                MaxItemCount = 1
            };

            foreach (DocumentServiceLease existingLease in await this.leaseManager.ListLeases())
            {
                options.PartitionKeyRangeId = existingLease.PartitionId;
                options.RequestContinuation = existingLease.ContinuationToken;
                IDocumentQuery <Document> query    = this.documentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options);
                FeedResponse <Document>   response = null;

                try
                {
                    response = await query.ExecuteNextAsync <Document>();

                    long parsedLSNFromSessionToken = TryConvertToNumber(ParseAmountFromSessionToken(response.SessionToken));
                    long lastSequenceNumber        = response.Count > 0 ? TryConvertToNumber(response.First().GetPropertyValue <string>(LSNPropertyName)) : parsedLSNFromSessionToken;
                    long partitionRemaining        = parsedLSNFromSessionToken - lastSequenceNumber;
                    remaining += partitionRemaining < 0 ? 0 : partitionRemaining;
                }
                catch (DocumentClientException ex)
                {
                    ExceptionDispatchInfo   exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
                    DocumentClientException dcex = (DocumentClientException)exceptionDispatchInfo.SourceException;
                    if ((StatusCode.NotFound == (StatusCode)dcex.StatusCode && SubStatusCode.ReadSessionNotAvailable != (SubStatusCode)GetSubStatusCode(dcex)) ||
                        StatusCode.Gone == (StatusCode)dcex.StatusCode)
                    {
                        // We are not explicitly handling Splits here to avoid any collision with an Observer that might have picked this up and managing the split
                        TraceLog.Error(string.Format("GetEstimateWork > Partition {0}: resource gone (subStatus={1}).", existingLease.PartitionId, GetSubStatusCode(dcex)));
                    }
                    else if (StatusCode.TooManyRequests == (StatusCode)dcex.StatusCode ||
                             StatusCode.ServiceUnavailable == (StatusCode)dcex.StatusCode)
                    {
                        TraceLog.Warning(string.Format("GetEstimateWork > Partition {0}: retriable exception : {1}", existingLease.PartitionId, dcex.Message));
                    }
                    else
                    {
                        TraceLog.Error(string.Format("GetEstimateWork > Partition {0}: Unhandled exception", ex.Error.Message));
                    }
                }
            }

            return(remaining);
        }
        private static async Task RunChangeFeedHostAsync()
        {
            // monitored collection info
            DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(cosmosSettings.DbUrl),
                MasterKey      = cosmosSettings.AuthorizationKey,
                DatabaseName   = cosmosSettings.DbName,
                CollectionName = cosmosSettings.CollectionName
            };

            DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(cosmosSettings.DbUrl),
                MasterKey      = cosmosSettings.AuthorizationKey,
                DatabaseName   = cosmosSettings.DbName,
                CollectionName = cosmosSettings.LeaseCollectionName
            };
            DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory();
            ChangeFeedOptions           feedOptions        = new ChangeFeedOptions
            {
                /* ie customize StartFromBeginning so change feed reads from beginning
                 *  can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning
                 */
                StartFromBeginning = true
            };

            ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions();

            // ie. customizing lease renewal interval to 15 seconds
            // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
            feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15);
            ChangeFeedProcessorBuilder builder = new ChangeFeedProcessorBuilder();

            builder
            .WithHostName(hostName)
            .WithFeedCollection(documentCollectionInfo)
            .WithLeaseCollection(leaseCollectionInfo)
            .WithProcessorOptions(feedProcessorOptions)
            .WithObserverFactory(new DocumentFeedObserverFactory());
            //.WithObserver<DocumentFeedObserver>();  If no factory then just pass an observer

            var result = await builder.BuildAsync();

            await result.StartAsync();

            Console.Read();
            await result.StopAsync();
        }
Esempio n. 15
0
        private async void OnStarted()
        // private void OnStarted()
        {
            _logger.LogInformation("OnStarted has been called.");

            // Perform post-startup activities here
            _logger.LogInformation("Log directory is " + localLogdirectory);

            string hostName = Guid.NewGuid().ToString();

            DocumentCollectionInfo documentCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(_configuration["az_cosmos_uri"]),
                MasterKey      = _configuration["az_cosmos_key"],
                DatabaseName   = _configuration["az_cosmos_db_name"],
                CollectionName = _configuration["az_cosmos_collection_name"]
            };

            DocumentCollectionInfo leaseCollectionInfo = new DocumentCollectionInfo
            {
                Uri            = new Uri(_configuration["az_cosmos_uri"]),
                MasterKey      = _configuration["az_cosmos_key"],
                DatabaseName   = _configuration["az_cosmos_db_name"],
                CollectionName = _configuration["az_cosmos_lease_collection_name"]
            };

            DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory();
            ChangeFeedOptions           feedOptions        = new ChangeFeedOptions();

            feedOptions.StartFromBeginning = true;

            ChangeFeedProcessorOptions feedProcessorOptions = new ChangeFeedProcessorOptions();

            feedProcessorOptions.LeaseRenewInterval = TimeSpan.FromSeconds(15);

            this.builder
            .WithHostName(hostName)
            .WithFeedCollection(documentCollectionInfo)
            .WithLeaseCollection(leaseCollectionInfo)
            .WithProcessorOptions(feedProcessorOptions)
            .WithObserverFactory(new DocumentFeedObserverFactory());   //;
//               .WithObserver<DocumentFeedObserver>();  //or just pass a observer

            iChangeFeedProcessor = await this.builder.BuildAsync();

            await iChangeFeedProcessor.StartAsync();
        }
Esempio n. 16
0
        public IPartitionProcessor Create(ILease lease, ILeaseCheckpointer leaseCheckpointer, IChangeFeedObserver observer)
        {
            if (observer == null)
            {
                throw new ArgumentNullException(nameof(observer));
            }
            if (lease == null)
            {
                throw new ArgumentNullException(nameof(lease));
            }
            if (leaseCheckpointer == null)
            {
                throw new ArgumentNullException(nameof(leaseCheckpointer));
            }

            var settings = new ProcessorSettings
            {
                CollectionSelfLink = this.collectionSelfLink,
                StartContinuation  = !string.IsNullOrEmpty(lease.ContinuationToken) ?
                                     lease.ContinuationToken :
                                     this.changeFeedProcessorOptions.StartContinuation,
                PartitionKeyRangeId = lease.PartitionId,
                FeedPollDelay       = this.changeFeedProcessorOptions.FeedPollDelay,
                MaxItemCount        = this.changeFeedProcessorOptions.MaxItemCount,
                StartFromBeginning  = this.changeFeedProcessorOptions.StartFromBeginning,
                StartTime           = this.changeFeedProcessorOptions.StartTime,
                SessionToken        = this.changeFeedProcessorOptions.SessionToken,
            };

            var checkpointer = new PartitionCheckpointer(leaseCheckpointer, lease);

            var changeFeedOptions = new ChangeFeedOptions
            {
                MaxItemCount        = settings.MaxItemCount,
                PartitionKeyRangeId = settings.PartitionKeyRangeId,
                SessionToken        = settings.SessionToken,
                StartFromBeginning  = settings.StartFromBeginning,
                RequestContinuation = settings.StartContinuation,
                StartTime           = settings.StartTime,
            };
            var changeFeedQuery = this.documentClient.CreateDocumentChangeFeedQuery(settings.CollectionSelfLink, changeFeedOptions);

            changeFeedQuery = new ChangeFeedQueryTimeoutDecorator(changeFeedQuery, this.healthMonitor, this.changeFeedProcessorOptions.ChangeFeedTimeout, lease);

            return(new PartitionProcessor(observer, changeFeedQuery, changeFeedOptions, settings, checkpointer));
        }
        public CosmosDBTriggerListener(ITriggeredFunctionExecutor executor, DocumentCollectionInfo documentCollectionLocation, DocumentCollectionInfo leaseCollectionLocation, ChangeFeedHostOptions leaseHostOptions, int?maxItemCount)
        {
            this.executor = executor;
            string hostName = Guid.NewGuid().ToString();

            monitorCollection = documentCollectionLocation;
            leaseCollection   = leaseCollectionLocation;

            ChangeFeedOptions changeFeedOptions = new ChangeFeedOptions();

            if (maxItemCount.HasValue)
            {
                changeFeedOptions.MaxItemCount = maxItemCount;
            }

            this.host = new ChangeFeedEventHost(hostName, documentCollectionLocation, leaseCollectionLocation, changeFeedOptions, leaseHostOptions);
        }
Esempio n. 18
0
        public PartitionProcessor(IChangeFeedObserver observer, IChangeFeedDocumentClient documentClient, ProcessorSettings settings, IPartitionCheckpointer checkpointer)
        {
            this.observer     = observer;
            this.settings     = settings;
            this.checkpointer = checkpointer;
            this.options      = new ChangeFeedOptions
            {
                MaxItemCount        = settings.MaxItemCount,
                PartitionKeyRangeId = settings.PartitionKeyRangeId,
                SessionToken        = settings.SessionToken,
                StartFromBeginning  = settings.StartFromBeginning,
                RequestContinuation = settings.RequestContinuation,
                StartTime           = settings.StartTime,
            };

            this.query = documentClient.CreateDocumentChangeFeedQuery(settings.CollectionSelfLink, this.options);
        }
        public FeedProcessorCore(ChangeFeedObserver <T> observer, CosmosContainer container, ProcessorSettings settings, PartitionCheckpointer checkpointer)
        {
            this.observer     = observer;
            this.settings     = settings;
            this.checkpointer = checkpointer;
            this.options      = new ChangeFeedOptions
            {
                MaxItemCount        = settings.MaxItemCount,
                PartitionKeyRangeId = settings.LeaseToken,
                SessionToken        = settings.SessionToken,
                StartFromBeginning  = settings.StartFromBeginning,
                RequestContinuation = settings.StartContinuation,
                StartTime           = settings.StartTime,
            };

            this.query = container.Client.DocumentClient.CreateDocumentChangeFeedQuery(container.LinkUri.ToString(), this.options);
        }
        public PartitionExceptionsTests()
        {
            processorSettings = new ProcessorSettings
            {
                CollectionSelfLink  = "selfLink",
                FeedPollDelay       = TimeSpan.FromMilliseconds(16),
                MaxItemCount        = 5,
                PartitionKeyRangeId = "keyRangeId",
                StartContinuation   = "initialToken"
            };

            var document = new Document();

            documents = new List <Document> {
                document
            };

            feedResponse = Mock.Of <IFeedResponse <Document> >();
            Mock.Get(feedResponse)
            .Setup(response => response.Count)
            .Returns(documents.Count);
            Mock.Get(feedResponse)
            .Setup(response => response.ResponseContinuation)
            .Returns("token");
            Mock.Get(feedResponse)
            .Setup(response => response.GetEnumerator())
            .Returns(documents.GetEnumerator());

            documentQuery = Mock.Of <IChangeFeedDocumentQuery <Document> >();
            Mock.Get(documentQuery)
            .Setup(query => query.HasMoreResults)
            .Returns(false);
            var options = new ChangeFeedOptions();

            observer = Mock.Of <IChangeFeedObserver>();
            Mock.Get(observer)
            .Setup(feedObserver => feedObserver
                   .ProcessChangesAsync(It.IsAny <ChangeFeedObserverContext>(), It.IsAny <IReadOnlyList <Document> >(), It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask)
            .Callback(cancellationTokenSource.Cancel);

            var checkPointer = new Mock <IPartitionCheckpointer>();

            partitionProcessor = new PartitionProcessor(observer, documentQuery, options, processorSettings, checkPointer.Object);
        }
Esempio n. 21
0
        public void ValidateLegacyOptionsAreUsed()
        {
            DateTime startTime   = DateTime.Now;
            var      feedOptions = new ChangeFeedOptions
            {
                MaxItemCount        = 1,
                StartFromBeginning  = true,
                StartTime           = startTime,
                RequestContinuation = "RequestContinuation",
                SessionToken        = "SessionToken",
            };

            var checkpointFrequency = new CheckpointFrequency {
                ExplicitCheckpoint = true
            };
            var hostOptions = new ChangeFeedHostOptions
            {
                LeaseRenewInterval          = TimeSpan.FromSeconds(2),
                LeaseAcquireInterval        = TimeSpan.FromSeconds(3),
                LeaseExpirationInterval     = TimeSpan.FromSeconds(4),
                FeedPollDelay               = TimeSpan.FromSeconds(5),
                CheckpointFrequency         = checkpointFrequency,
                LeasePrefix                 = "LeasePrefix",
                MinPartitionCount           = 6,
                MaxPartitionCount           = 7,
                QueryPartitionsMaxBatchSize = 8,
            };

            var processorOptions = ChangeFeedEventHost.CreateProcessorOptions(feedOptions, hostOptions);

            Assert.Equal(1, processorOptions.MaxItemCount);
            Assert.True(processorOptions.StartFromBeginning);
            Assert.Equal(startTime, processorOptions.StartTime);
            Assert.Equal("RequestContinuation", processorOptions.RequestContinuation);
            Assert.Equal("SessionToken", processorOptions.SessionToken);
            Assert.Equal(TimeSpan.FromSeconds(2), processorOptions.LeaseRenewInterval);
            Assert.Equal(TimeSpan.FromSeconds(3), processorOptions.LeaseAcquireInterval);
            Assert.Equal(TimeSpan.FromSeconds(4), processorOptions.LeaseExpirationInterval);
            Assert.Equal(TimeSpan.FromSeconds(5), processorOptions.FeedPollDelay);
            Assert.Equal(checkpointFrequency, processorOptions.CheckpointFrequency);
            Assert.Equal("LeasePrefix", processorOptions.LeasePrefix);
            Assert.Equal(6, processorOptions.MinPartitionCount);
            Assert.Equal(7, processorOptions.MaxPartitionCount);
            Assert.Equal(8, processorOptions.QueryPartitionsMaxBatchSize);
        }
Esempio n. 22
0
        static async Task<Tuple<long, double>> ProcessPartitionKeyRangeId(Uri sourceCollectionUri, PartitionKeyRange pkRange, string continuation)
        {
            long totalCount = 0;
            double totalRequestCharge = 0;

            List<Task> tasks = new List<Task>();

            ChangeFeedOptions options = new ChangeFeedOptions
            {
                PartitionKeyRangeId = pkRange.Id,
                StartFromBeginning = true,
                RequestContinuation = continuation,
                //MaxItemCount = -1
                MaxItemCount = 1000
            };

            using (var query = sourceClient.CreateDocumentChangeFeedQuery(sourceCollectionUri, options))
            {
                do
                {
                    var readChangesResponse = await query.ExecuteNextAsync<Document>();

                    totalCount += readChangesResponse.Count;
                    totalRequestCharge += readChangesResponse.RequestCharge;

                    Console.WriteLine("Count of documents in this page : {0}", readChangesResponse.Count);
                    Debug.WriteLine("Count of documents in this page : {0}", readChangesResponse.Count);
                    //Console.WriteLine("Request charge for these documents : {0}", readChangesResponse.RequestCharge);

                    if (readChangesResponse.Count > 0)
                    {
                        foreach (Document changedDocument in readChangesResponse)
                        {
                            tasks.Add(targetClient.UpsertDocumentAsync(sourceCollectionUri, changedDocument));
                        }

                        await Task.WhenAll(tasks);

                        checkpoints[pkRange.Id] = readChangesResponse.ResponseContinuation;
                    }
                } while (query.HasMoreResults);
            }

            return Tuple.Create(totalCount, totalRequestCharge);
        }
Esempio n. 23
0
        internal static ChangeFeedProcessorOptions CreateProcessorOptions(ChangeFeedOptions feedOptions, ChangeFeedHostOptions hostOptions)
        {
            Debug.Assert(feedOptions != null, nameof(feedOptions));
            Debug.Assert(hostOptions != null, nameof(hostOptions));

            if (!string.IsNullOrEmpty(feedOptions.PartitionKeyRangeId))
            {
                throw new ArgumentException("changeFeedOptions.PartitionKeyRangeId must be null or empty string.", nameof(feedOptions.PartitionKeyRangeId));
            }

            if (feedOptions.PartitionKey != null)
            {
                throw new ArgumentException("changeFeedOptions.PartitionKey must be null.", nameof(feedOptions.PartitionKey));
            }

            if (hostOptions.DiscardExistingLeases)
            {
                throw new ArgumentException("hostOptions.DiscardExistingLeases is no longer supported.", nameof(hostOptions.DiscardExistingLeases));
            }

            return(new ChangeFeedProcessorOptions
            {
                MaxItemCount = feedOptions.MaxItemCount,
                StartFromBeginning = feedOptions.StartFromBeginning,
                StartTime = feedOptions.StartTime,
                RequestContinuation = feedOptions.RequestContinuation,
                SessionToken = feedOptions.SessionToken,

                LeaseRenewInterval = hostOptions.LeaseRenewInterval,
                LeaseAcquireInterval = hostOptions.LeaseAcquireInterval,
                LeaseExpirationInterval = hostOptions.LeaseExpirationInterval,
                FeedPollDelay = hostOptions.FeedPollDelay,
                CheckpointFrequency = hostOptions.CheckpointFrequency,
                LeasePrefix = hostOptions.LeasePrefix,
                MinPartitionCount = hostOptions.MinPartitionCount,
                MaxPartitionCount = hostOptions.MaxPartitionCount,
                DiscardExistingLeases = hostOptions.DiscardExistingLeases,
                QueryPartitionsMaxBatchSize = hostOptions.QueryPartitionsMaxBatchSize,
            });
        }
Esempio n. 24
0
        public async Task <Dictionary <string, int> > GetTopPartitionKeys(Connection connection, DocumentCollection collection, string partitionKeyRangeId, int sampleCount = 100)
        {
            var stats        = new Dictionary <string, int>();
            var partitionKey = collection.PartitionKey.Paths[0].TrimStart('/');
            var readCount    = 0;
            var client       = GetClient(connection);
            var options      = new ChangeFeedOptions {
                StartFromBeginning = true, MaxItemCount = -1, PartitionKeyRangeId = partitionKeyRangeId
            };

            while (readCount < sampleCount)
            {
                var results = await client.CreateDocumentChangeFeedQuery(collection.AltLink, options)
                              .ExecuteNextAsync <Document>().ConfigureAwait(false);

                if (results.Count == 0)
                {
                    break;
                }

                foreach (var document in results)
                {
                    var key = document.GetPropertyValue <string>(partitionKey) ?? "N/A";

                    if (stats.ContainsKey(key))
                    {
                        stats[key]++;
                    }
                    else
                    {
                        stats.Add(key, 1);
                    }
                }

                readCount += results.Count;
            }

            return(stats);
        }
        private async Task StartChangeFeedHost()
        {
            ConnectionPolicy ConnectionPolicy = new ConnectionPolicy
            {
                ConnectionMode     = ConnectionMode.Direct,
                ConnectionProtocol = Protocol.Tcp,
                RequestTimeout     = new TimeSpan(1, 0, 0),
                MaxConnectionLimit = 1000,
                RetryOptions       = new RetryOptions
                {
                    MaxRetryAttemptsOnThrottledRequests = 10,
                    MaxRetryWaitTimeInSeconds           = 60
                }
            };


            //Customizable change feed option and host options
            ChangeFeedOptions feedOptions = new ChangeFeedOptions();

            // ie customize StartFromBeginning so change feed reads from beginning
            // can customize MaxItemCount, PartitonKeyRangeId, RequestContinuation, SessionToken and StartFromBeginning
            feedOptions.StartTime = this.Config.ChangeFeedStartTime;
            var feedHostOptions = new ChangeFeedHostOptions
            {
                // ie. customizing lease renewal interval to 15 seconds
                // can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
                LeaseRenewInterval = TimeSpan.FromSeconds(15)
            };

            string hostName = Guid.NewGuid().ToString();
            DocumentFeedObserverFactory docObserverFactory = new DocumentFeedObserverFactory(this.Config);

            this.ChangeFeedEventHost = new ChangeFeedEventHost(hostName, Config.SourceDocumentCollectionInfo, this.Config.LeaseDocumentCollectionInfo, feedOptions, feedHostOptions);

            await this.ChangeFeedEventHost.RegisterObserverFactoryAsync(docObserverFactory);

            Console.WriteLine("Running... Press enter to stop.");
        }
Esempio n. 26
0
            private async Task <string> ReadStreamAsync(string partitionKeyRangeId, string continuation, DataWriter sw)
            {
                var feedOptions = new ChangeFeedOptions
                {
                    PartitionKeyRangeId = partitionKeyRangeId,
                    RequestContinuation = continuation,
                    StartFromBeginning  = options.StartFromBeginning
                };

                var count = 0;

                using (var query = client.CreateDocumentChangeFeedQuery(collection.SelfLink, feedOptions))
                {
                    do
                    {
                        var response = await query.ExecuteNextAsync().ConfigureAwait(false);

                        if (response.Count > 0)
                        {
                            foreach (var item in response)
                            {
                                await sw.WriteAsync(JToken.FromObject(item)).ConfigureAwait(false);

                                count++;
                            }
                        }

                        continuation = response.ResponseContinuation;
                    }while (query.HasMoreResults);
                }

                if (count > 0)
                {
                    Log.Information("Wrote {Count} changes to {Collection}", count, collection.Id);
                }

                return(continuation);
            }
Esempio n. 27
0
        private static async Task Run()
        {
            ChangeFeedOptions feedOptions = new ChangeFeedOptions {
                StartFromBeginning = true
            };
            // Can customize LeaseRenewInterval, LeaseAcquireInterval, LeaseExpirationInterval, FeedPollDelay
            ChangeFeedHostOptions feedHostOptions = new ChangeFeedHostOptions {
                LeaseRenewInterval = TimeSpan.FromSeconds(15)
            };
            DocumentCollectionInfo documentCollectionLocation = new DocumentCollectionInfo
            {
                Uri            = new Uri(Connection.EndpointUrl),
                CollectionName = "FamilyCollection",
                // ConnectionPolicy = new ConnectionPolicy{},
                DatabaseName = "FamilyDB",
                MasterKey    = Connection.PrimaryKey
            };
            ChangeFeedEventHost       changeFeedEventHost       = new ChangeFeedEventHost("hostName", documentCollectionLocation, documentCollectionLocation, feedOptions, feedHostOptions);
            ChangeFeedObserverFactory changeFeedObserverFactory = new ChangeFeedObserverFactory();
            await changeFeedEventHost.RegisterObserverFactoryAsync(changeFeedObserverFactory);

            await changeFeedEventHost.UnregisterObserversAsync();
        }
Esempio n. 28
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeFeedEventHost"/> class.
        /// </summary>
        /// <param name="hostName">Unique name for this host.</param>
        /// <param name="feedCollectionLocation">Specifies location of the Cosmos DB collection to monitor changes for.</param>
        /// <param name="leaseCollectionLocation">Specifies location of auxiliary data for load-balancing instances of <see cref="ChangeFeedEventHost" />.</param>
        /// <param name="changeFeedOptions">Options to pass to the DocumentClient.CreateDocumentChangeFeedQuery API.</param>
        /// <param name="changeFeedHostOptions">Additional options to control load-balancing of <see cref="ChangeFeedEventHost" /> instances.</param>
        public ChangeFeedEventHost(
            string hostName,
            DocumentCollectionInfo feedCollectionLocation,
            DocumentCollectionInfo leaseCollectionLocation,
            ChangeFeedOptions changeFeedOptions,
            ChangeFeedHostOptions changeFeedHostOptions)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException(nameof(hostName));
            }
            if (feedCollectionLocation == null)
            {
                throw new ArgumentNullException(nameof(feedCollectionLocation));
            }
            if (leaseCollectionLocation == null)
            {
                throw new ArgumentNullException(nameof(leaseCollectionLocation));
            }
            if (changeFeedOptions == null)
            {
                throw new ArgumentNullException(nameof(changeFeedOptions));
            }
            if (changeFeedHostOptions == null)
            {
                throw new ArgumentNullException(nameof(changeFeedHostOptions));
            }

            ChangeFeedEventHost.TraceLogProvider.OpenNestedContext(hostName);

            this.builder
            .WithHostName(hostName)
            .WithFeedCollection(feedCollectionLocation)
            .WithProcessorOptions(ChangeFeedEventHost.CreateProcessorOptions(changeFeedOptions, changeFeedHostOptions))
            .WithLeaseCollection(leaseCollectionLocation);
        }
        async Task IPartitionObserver <DocumentServiceLease> .OnPartitionAcquiredAsync(DocumentServiceLease lease)
        {
            Debug.Assert(lease != null && !string.IsNullOrEmpty(lease.Owner), "lease");
            TraceLog.Informational(string.Format("Host '{0}' partition {1}: acquired!", this.HostName, lease.PartitionId));

#if DEBUG
            Interlocked.Increment(ref this.partitionCount);
#endif

            IChangeFeedObserver       observer = this.observerFactory.CreateObserver();
            ChangeFeedObserverContext context  = new ChangeFeedObserverContext {
                PartitionKeyRangeId = lease.PartitionId
            };
            CancellationTokenSource cancellation = new CancellationTokenSource();

            // Create ChangeFeedOptions to use for this worker.
            ChangeFeedOptions options = new ChangeFeedOptions
            {
                MaxItemCount        = this.changeFeedOptions.MaxItemCount,
                PartitionKeyRangeId = this.changeFeedOptions.PartitionKeyRangeId,
                SessionToken        = this.changeFeedOptions.SessionToken,
                StartFromBeginning  = this.changeFeedOptions.StartFromBeginning,
                RequestContinuation = this.changeFeedOptions.RequestContinuation
            };

            var workerTask = await Task.Factory.StartNew(async() =>
            {
                ChangeFeedObserverCloseReason?closeReason = null;
                try
                {
                    try
                    {
                        await observer.OpenAsync(context);
                    }
                    catch (Exception ex)
                    {
                        TraceLog.Error(string.Format("IChangeFeedObserver.OpenAsync exception: {0}", ex));
                        closeReason = ChangeFeedObserverCloseReason.ObserverError;
                        throw;
                    }

                    options.PartitionKeyRangeId = lease.PartitionId;
                    if (!string.IsNullOrEmpty(lease.ContinuationToken))
                    {
                        options.RequestContinuation = lease.ContinuationToken;
                    }

                    CheckpointStats checkpointStats = null;
                    if (!this.statsSinceLastCheckpoint.TryGetValue(lease.PartitionId, out checkpointStats) || checkpointStats == null)
                    {
                        // It could be that the lease was created by different host and we picked it up.
                        checkpointStats = this.statsSinceLastCheckpoint.AddOrUpdate(
                            lease.PartitionId,
                            new CheckpointStats(),
                            (partitionId, existingStats) => existingStats);
                        Trace.TraceWarning(string.Format("Added stats for partition '{0}' for which the lease was picked up after the host was started.", lease.PartitionId));
                    }

                    IDocumentQuery <Document> query = this.documentClient.CreateDocumentChangeFeedQuery(this.collectionSelfLink, options);

                    TraceLog.Verbose(string.Format("Worker start: partition '{0}', continuation '{1}'", lease.PartitionId, lease.ContinuationToken));

                    string lastContinuation = options.RequestContinuation;

                    try
                    {
                        while (this.isShutdown == 0)
                        {
                            do
                            {
                                ExceptionDispatchInfo exceptionDispatchInfo = null;
                                FeedResponse <Document> response            = null;

                                try
                                {
                                    response         = await query.ExecuteNextAsync <Document>();
                                    lastContinuation = response.ResponseContinuation;
                                }
                                catch (DocumentClientException ex)
                                {
                                    exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
                                }

                                if (exceptionDispatchInfo != null)
                                {
                                    DocumentClientException dcex = (DocumentClientException)exceptionDispatchInfo.SourceException;

                                    if (StatusCode.NotFound == (StatusCode)dcex.StatusCode && SubStatusCode.ReadSessionNotAvailable != (SubStatusCode)GetSubStatusCode(dcex))
                                    {
                                        // Most likely, the database or collection was removed while we were enumerating.
                                        // Shut down. The user will need to start over.
                                        // Note: this has to be a new task, can't await for shutdown here, as shudown awaits for all worker tasks.
                                        TraceLog.Error(string.Format("Partition {0}: resource gone (subStatus={1}). Aborting.", context.PartitionKeyRangeId, GetSubStatusCode(dcex)));
                                        await Task.Factory.StartNew(() => this.StopAsync(ChangeFeedObserverCloseReason.ResourceGone));
                                        break;
                                    }
                                    else if (StatusCode.Gone == (StatusCode)dcex.StatusCode)
                                    {
                                        SubStatusCode subStatusCode = (SubStatusCode)GetSubStatusCode(dcex);
                                        if (SubStatusCode.PartitionKeyRangeGone == subStatusCode)
                                        {
                                            bool isSuccess = await HandleSplitAsync(context.PartitionKeyRangeId, lastContinuation, lease.Id);
                                            if (!isSuccess)
                                            {
                                                TraceLog.Error(string.Format("Partition {0}: HandleSplit failed! Aborting.", context.PartitionKeyRangeId));
                                                await Task.Factory.StartNew(() => this.StopAsync(ChangeFeedObserverCloseReason.ResourceGone));
                                                break;
                                            }

                                            // Throw LeaseLostException so that we take the lease down.
                                            throw new LeaseLostException(lease, exceptionDispatchInfo.SourceException, true);
                                        }
                                        else if (SubStatusCode.Splitting == subStatusCode)
                                        {
                                            TraceLog.Warning(string.Format("Partition {0} is splitting. Will retry to read changes until split finishes. {1}", context.PartitionKeyRangeId, dcex.Message));
                                        }
                                        else
                                        {
                                            exceptionDispatchInfo.Throw();
                                        }
                                    }
                                    else if (StatusCode.TooManyRequests == (StatusCode)dcex.StatusCode ||
                                             StatusCode.ServiceUnavailable == (StatusCode)dcex.StatusCode)
                                    {
                                        TraceLog.Warning(string.Format("Partition {0}: retriable exception : {1}", context.PartitionKeyRangeId, dcex.Message));
                                    }
                                    else
                                    {
                                        exceptionDispatchInfo.Throw();
                                    }

                                    await Task.Delay(dcex.RetryAfter != TimeSpan.Zero ? dcex.RetryAfter : this.options.FeedPollDelay, cancellation.Token);
                                }

                                if (response != null)
                                {
                                    if (response.Count > 0)
                                    {
                                        List <Document> docs = new List <Document>();
                                        docs.AddRange(response);

                                        try
                                        {
                                            context.FeedResponse = response;
                                            await observer.ProcessChangesAsync(context, docs);
                                        }
                                        catch (Exception ex)
                                        {
                                            TraceLog.Error(string.Format("IChangeFeedObserver.ProcessChangesAsync exception: {0}", ex));
                                            closeReason = ChangeFeedObserverCloseReason.ObserverError;
                                            throw;
                                        }
                                        finally
                                        {
                                            context.FeedResponse = null;
                                        }
                                    }

                                    checkpointStats.ProcessedDocCount += (uint)response.Count;

                                    if (IsCheckpointNeeded(lease, checkpointStats))
                                    {
                                        lease = await CheckpointAsync(lease, response.ResponseContinuation, context);
                                        checkpointStats.Reset();
                                    }
                                    else if (response.Count > 0)
                                    {
                                        TraceLog.Informational(string.Format("Checkpoint: not checkpointing for partition {0}, {1} docs, new continuation '{2}' as frequency condition is not met", lease.PartitionId, response.Count, response.ResponseContinuation));
                                    }
                                }
                            }while (query.HasMoreResults && this.isShutdown == 0);

                            if (this.isShutdown == 0)
                            {
                                await Task.Delay(this.options.FeedPollDelay, cancellation.Token);
                            }
                        } // Outer while (this.isShutdown == 0) loop.

                        closeReason = ChangeFeedObserverCloseReason.Shutdown;
                    }
                    catch (TaskCanceledException)
                    {
                        Debug.Assert(cancellation.IsCancellationRequested, "cancellation.IsCancellationRequested");
                        TraceLog.Informational(string.Format("Cancel signal received for partition {0} worker!", context.PartitionKeyRangeId));
                    }
                }
                catch (LeaseLostException ex)
                {
                    closeReason = ex.IsGone ? ChangeFeedObserverCloseReason.LeaseGone : ChangeFeedObserverCloseReason.LeaseLost;
                }
                catch (Exception ex)
                {
                    TraceLog.Error(string.Format("Partition {0} exception: {1}", context.PartitionKeyRangeId, ex));
                    if (!closeReason.HasValue)
                    {
                        closeReason = ChangeFeedObserverCloseReason.Unknown;
                    }
                }

                if (closeReason.HasValue)
                {
                    TraceLog.Informational(string.Format("Releasing lease for partition {0} due to an error, reason: {1}!", context.PartitionKeyRangeId, closeReason.Value));

                    // Note: this has to be a new task, because OnPartitionReleasedAsync awaits for worker task.
                    await Task.Factory.StartNew(async() => await this.partitionManager.TryReleasePartitionAsync(context.PartitionKeyRangeId, true, closeReason.Value));
                }

                TraceLog.Informational(string.Format("Partition {0}: worker finished!", context.PartitionKeyRangeId));
            });

            var newWorkerData = new WorkerData(workerTask, observer, context, cancellation);
            this.partitionKeyRangeIdToWorkerMap.AddOrUpdate(context.PartitionKeyRangeId, newWorkerData, (string id, WorkerData d) => { return(newWorkerData); });
        }
 public IChangeFeedDocumentQuery <Document> CreateDocumentChangeFeedQuery(string collectionLink, ChangeFeedOptions feedOptions)
 {
     return(new QoSMeteringChangeFeedDocumentQuery(_inner.CreateDocumentChangeFeedQuery(collectionLink, feedOptions), feedOptions.PartitionKeyRangeId, _meter));
 }