private async void OnProductChanged(object sender, ProductChangeEventArgs e)
 {
     await _subscriberStream.WriteAsync(e.Product);
 }
        public async Task <DateTimeOffset> ProduceAsync(
            ChannelWriter <CatalogLeafItem> channel,
            DateTimeOffset minCursor,
            DateTimeOffset maxCursor,
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("Fetching catalog index...");
            var client       = _factory.CreateCatalogClient();
            var catalogIndex = await client.GetIndexAsync(cancellationToken);

            var pages = catalogIndex.GetPagesInBounds(minCursor, maxCursor);

            var maxPages = _options.Value.MaxPages;

            if (maxPages.HasValue)
            {
                pages = pages.Take(maxPages.Value).ToList();
            }

            if (!pages.Any() || minCursor == maxCursor)
            {
                _logger.LogInformation("No pending leaf items on the catalog.");
                channel.Complete();
                return(minCursor);
            }

            var work    = new ConcurrentBag <CatalogPageItem>(pages);
            var workers = Math.Min(_options.Value.ProducerWorkers, pages.Count);

            _logger.LogInformation(
                "Fetching {Pages} catalog pages using {ProducerWorkers} workers...",
                pages.Count,
                workers);

            var tasks = Enumerable
                        .Repeat(0, workers)
                        .Select(async _ =>
            {
                await Task.Yield();

                while (work.TryTake(out var pageItem))
                {
                    var done = false;
                    while (!done)
                    {
                        try
                        {
                            _logger.LogDebug("Processing catalog page {PageUrl}...", pageItem.CatalogPageUrl);
                            var page = await client.GetPageAsync(pageItem.CatalogPageUrl, cancellationToken);

                            foreach (var leaf in page.Items)
                            {
                                // Don't process leaves that are not within the cursors.
                                if (leaf.CommitTimestamp <= minCursor)
                                {
                                    continue;
                                }
                                if (leaf.CommitTimestamp > maxCursor)
                                {
                                    continue;
                                }

                                if (!channel.TryWrite(leaf))
                                {
                                    await channel.WriteAsync(leaf, cancellationToken);
                                }
                            }

                            _logger.LogDebug("Processed catalog page {PageUrl}.", pageItem.CatalogPageUrl);
                            done = true;
                        }
                        catch (Exception e) when(!cancellationToken.IsCancellationRequested)
                        {
                            _logger.LogError(e, "Retrying catalog page {PageUrl} in 5 seconds...", pageItem.CatalogPageUrl);
                            await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
                        }
                    }
                }
            });

            await Task.WhenAll(tasks);

            var cursor = pages.Last().CommitTimestamp;

            _logger.LogInformation("Fetched catalog pages up to cursor {Cursor}", cursor);
            channel.Complete();

            return(cursor);
        }
Beispiel #3
0
 static async Task PingPongWithoutNewTask(ChannelWriter <int> out_, ChannelReader <int> in_)
 {
     await out_.WriteAsync(1 + await in_.ReadAsync());
 }
        public static Task <long> WriteAllConcurrentlyAsync <T>(this ChannelWriter <T> target,
                                                                int maxConcurrency, IEnumerable <ValueTask <T> > source, bool complete = false, CancellationToken cancellationToken = default)
        {
            if (target is null)
            {
                throw new ArgumentNullException(nameof(target));
            }
            if (source is null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (maxConcurrency < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxConcurrency), maxConcurrency, "Must be at least 1.");
            }
            Contract.EndContractBlock();

            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <long>(cancellationToken));
            }

            if (maxConcurrency == 1)
            {
                return(target.WriteAllAsync(source, complete, true, cancellationToken).AsTask());
            }

            var shouldWait = target
                             .WaitToWriteAndThrowIfClosedAsync("The target channel was closed before writing could begin.", cancellationToken)
                             .AsTask();    // ValueTasks can only have a single await.

            var errorTokenSource = new CancellationTokenSource();
            var errorToken       = errorTokenSource.Token;
            var enumerator       = source.GetEnumerator();
            var writers          = new Task <long> [maxConcurrency];

            for (var w = 0; w < maxConcurrency; w++)
            {
                writers[w] = WriteAllAsyncCore();
            }

            return(Task
                   .WhenAll(writers)
                   .ContinueWith(t =>
            {
                errorTokenSource.Dispose();
                if (complete)
                {
                    target.Complete(t.Exception);
                }

                if (t.IsFaulted)
                {
                    return Task.FromException <long>(t.Exception);
                }
                if (t.IsCanceled)
                {
                    return Task.FromCanceled <long>(cancellationToken);
                }
                return Task.FromResult(t.Result.Sum());
            },
                                 CancellationToken.None,
                                 TaskContinuationOptions.ExecuteSynchronously,
                                 TaskScheduler.Current)
                   .Unwrap());

            // returns false if there's no more (wasn't cancelled).
            async Task <long> WriteAllAsyncCore()
            {
                await Task.Yield();

                try
                {
                    await shouldWait.ConfigureAwait(false);

                    long count = 0;
                    var  next  = new ValueTask();
                    var  potentiallyCancelled = true;                    // if it completed and actually returned false, no need to bubble the cancellation since it actually completed.
                    while (!errorToken.IsCancellationRequested &&
                           !cancellationToken.IsCancellationRequested &&
                           (potentiallyCancelled = TryMoveNextSynchronized(enumerator, out var e)))
                    {
                        var value = await e.ConfigureAwait(false);

                        await next.ConfigureAwait(false);

                        count++;
                        next = target.TryWrite(value)                         // do this to avoid unneccesary early cancel.
                                                        ? new ValueTask()
                                                        : target.WriteAsync(value, cancellationToken);
                    }
                    await next.ConfigureAwait(false);

                    if (potentiallyCancelled)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                    return(count);
                }
                catch
                {
                    errorTokenSource.Cancel();
                    throw;
                }
            }
        }
Beispiel #5
0
 public override async Task RefreshAsync()
 {
     await writer.WriteAsync(1, cancellationTokenSource.Token);
 }