Ejemplo n.º 1
0
        public ChangeFeedReader(IConfiguration configuration, IHubContext <ClientHub> signalRHubContext)
        {
            var cosmosDbConfiguration = ChangeFeedReader.BuildConfigurationForSection(configuration, "CosmosDB");

            this.cosmosDbConfiguration = cosmosDbConfiguration;
            this.signalRHubContext     = signalRHubContext;
            TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.Active;

            telemetryConfiguration.InstrumentationKey = ChangeFeedReader.DetectAppInsightsInstrumentationKey(configuration);
            telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
            telemetryConfiguration.TelemetryInitializers.Add(new HttpDependenciesParsingTelemetryInitializer());
            this.telemetryClient = new TelemetryClient();
        }
Ejemplo n.º 2
0
        public Task StartAsync(CancellationToken cancellation)
        {
            if (this.isRunning)
            {
                return(Task.CompletedTask);
            }

            if (this.documentClient == null)
            {
                this.telemetryClient.TrackEvent($"Creating DocumentClient...");
                this.documentClient = new DocumentClient(new Uri(this.cosmosDbConfiguration.Endpoint), this.cosmosDbConfiguration.MasterKey, ChangeFeedReader.BuildConnectionPolicy(this.cosmosDbConfiguration));
                this.collectionLink = UriFactory.CreateDocumentCollectionUri(this.cosmosDbConfiguration.DatabaseName, this.cosmosDbConfiguration.CollectionName);
                this.telemetryClient.TrackEvent($"DocumentClient ready.");
            }

            this.isRunning = true;
            TimeSpan feedPollDelay = TimeSpan.FromMilliseconds(this.cosmosDbConfiguration.PollingInterval.HasValue ? this.cosmosDbConfiguration.PollingInterval.Value : ChangeFeedReader.DefaultPollingIntervalInMilliseconds);

            return(Task.Run(async() =>
            {
                this.telemetryClient.TrackEvent($"ChangeFeedReader running.");
                ChangeFeedOptions options = new ChangeFeedOptions
                {
                    MaxItemCount = -1,
                    PartitionKeyRangeId = "0",
                };

                if (this.cosmosDbConfiguration.MaxItemCount.HasValue)
                {
                    options.MaxItemCount = this.cosmosDbConfiguration.MaxItemCount.Value;
                }

                while (this.isRunning)
                {
                    IDocumentQuery <Document> query = this.documentClient.CreateDocumentChangeFeedQuery(this.collectionLink, options);

                    do
                    {
                        ExceptionDispatchInfo exceptionDispatchInfo = null;
                        FeedResponse <Document> readChangesResponse = null;
                        var operation = this.telemetryClient.StartOperation(new RequestTelemetry()
                        {
                            Name = "ChangeFeedReader.ReadFeed"
                        });
                        DateTimeOffset feedDependencyStartTime = DateTimeOffset.UtcNow;
                        try
                        {
                            readChangesResponse = await query.ExecuteNextAsync <Document>();
                            this.telemetryClient.TrackMetric(new MetricTelemetry("CosmosDB.ChangeFeed.RU", readChangesResponse.RequestCharge));
                            this.telemetryClient.TrackDependency("CosmosDB.ChangeFeed", "ExecuteNextAsync", feedDependencyStartTime, DateTimeOffset.UtcNow.Subtract(feedDependencyStartTime), true);
                            options.RequestContinuation = readChangesResponse.ResponseContinuation;
                        }
                        catch (DocumentClientException ex)
                        {
                            exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
                            this.telemetryClient.TrackDependency("CosmosDB.ChangeFeed", "ExecuteNextAsync", feedDependencyStartTime, DateTimeOffset.UtcNow.Subtract(feedDependencyStartTime), false);
                        }

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

                            if ((HttpStatusCode)dcex.StatusCode == HttpStatusCode.NotFound && (SubStatusCode)ChangeFeedReader.GetSubStatusCode(dcex) != SubStatusCode.ReadSessionNotAvailable)
                            {
                                // Most likely, the database or collection was removed while we were enumerating.
                                this.telemetryClient.TrackException(dcex);
                                this.telemetryClient.StopOperation(operation);
                                this.isRunning = false;
                                break;
                            }
                            else if ((HttpStatusCode)dcex.StatusCode == HttpStatusCode.Gone)
                            {
                                SubStatusCode subStatusCode = (SubStatusCode)ChangeFeedReader.GetSubStatusCode(dcex);
                                this.telemetryClient.TrackException(dcex);
                            }
                            else if ((int)dcex.StatusCode == 429 ||
                                     (HttpStatusCode)dcex.StatusCode == HttpStatusCode.ServiceUnavailable)
                            {
                                this.telemetryClient.TrackEvent($"Retriable exception: {dcex.Message}");
                            }
                            else if (dcex.Message.Contains("Reduce page size and try again."))
                            {
                                this.telemetryClient.TrackEvent($"Page size error while reading the feed.");

                                // Temporary workaround to compare exception message, until server provides better way of handling this case.
                                if (!options.MaxItemCount.HasValue)
                                {
                                    options.MaxItemCount = DefaultMaxItemCount;
                                }
                                else if (options.MaxItemCount <= 1)
                                {
                                    this.telemetryClient.TrackEvent($"Cannot reduce maxItemCount further as it's already at {options.MaxItemCount}.");
                                    this.telemetryClient.TrackException(new Exception("Cannot reduce maxItemCount"));
                                }
                                else
                                {
                                    options.MaxItemCount /= 2;
                                    this.telemetryClient.TrackEvent($"Reducing maxItemCount, new value: {options.MaxItemCount}.");
                                }
                            }
                            else
                            {
                                this.telemetryClient.TrackException(dcex);
                            }

                            if (dcex.RetryAfter != TimeSpan.Zero)
                            {
                                this.telemetryClient.TrackTrace($"Exception requires retryAfter, sleeping {dcex.RetryAfter.TotalMilliseconds} ms.");
                                await Task.Delay(dcex.RetryAfter, cancellation);
                            }
                        }

                        if (readChangesResponse != null)
                        {
                            var results = readChangesResponse.ToList();
                            if (results.Count > 0)
                            {
                                var lsn = results.First().GetPropertyValue <long>("_lsn");
                                this.telemetryClient.TrackTrace($"Detected {results.Count} documents. First _lsn {lsn}");
                                DateTimeOffset signalRDependencyStartTime = DateTimeOffset.UtcNow;
                                try
                                {
                                    var response = results.Select((d) => new
                                    {
                                        items = d.GetPropertyValue <List <Pixel> >("items"),
                                        _lsn = d.GetPropertyValue <long>("_lsn"),
                                    });

                                    await this.signalRHubContext.Clients.All.SendAsync("Changes", JsonConvert.SerializeObject(response));
                                    this.telemetryClient.TrackDependency("SignalR", "SendAsync", signalRDependencyStartTime, DateTimeOffset.UtcNow.Subtract(signalRDependencyStartTime), true);
                                }
                                catch (Exception ex)
                                {
                                    this.telemetryClient.TrackException(ex);
                                    this.telemetryClient.TrackDependency("SignalR", "SendAsync", signalRDependencyStartTime, DateTimeOffset.UtcNow.Subtract(signalRDependencyStartTime), false);
                                }
                                this.telemetryClient.StopOperation(operation);
                            }
                            else
                            {
                                this.telemetryClient.TrackTrace($"No changes, sleeping {feedPollDelay.TotalMilliseconds} ms.");
                                this.telemetryClient.StopOperation(operation);
                                await Task.Delay(feedPollDelay, cancellation);
                            }
                        }
                        else
                        {
                            this.telemetryClient.StopOperation(operation);
                        }
                    }while (query.HasMoreResults && this.isRunning);
                }
            }));
        }