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(); }
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); } })); }