Example #1
0
        private void QueueWork(IReadOnlyList <SourceResource> sources, PackageIdentity package, bool ignoreExceptions, bool isInstalledPackage)
        {
            IReadOnlyList <string> configuredPackageSources = null;

            if (_areNamespacesEnabled)
            {
                configuredPackageSources = _context.PackageNamespacesConfiguration.GetConfiguredPackageSources(package.Id);

                if (configuredPackageSources != null)
                {
                    var packageSourcesAtPrefix = string.Join(", ", configuredPackageSources);
                    _context.Log.LogDebug(StringFormatter.Log_PackageNamespaceMatchFound((package.Id), packageSourcesAtPrefix));
                }
                else
                {
                    _context.Log.LogDebug(StringFormatter.Log_PackageNamespaceNoMatchFound((package.Id)));
                }
            }

            // No-op if the id has already been searched for
            // Exact versions are not added to the list since we may need to search for the full
            // set of packages for that id later if it becomes part of the closure later.
            if (package.HasVersion || _idsSearched.Add(package.Id))
            {
                foreach (SourceResource source in sources)
                {
                    if (_areNamespacesEnabled && configuredPackageSources != null && !configuredPackageSources.Contains(source.Source.PackageSource.Name, StringComparer.CurrentCultureIgnoreCase))
                    {
                        // This package's id prefix is not defined in current package source, let's skip.
                        continue;
                    }

                    // Keep track of the order in which these were made
                    var requestId = GetNextRequestId();

                    var request = new GatherRequest(source, package, ignoreExceptions, requestId, isInstalledPackage);

                    // Order is important here
                    _gatherRequests.Enqueue(request);
                }
            }
        }
        /// <summary>
        /// Asynchronously returns a <see cref="DownloadResourceResult" /> for a given package identity
        /// and enumerable of source repositories.
        /// </summary>
        /// <param name="sources">An enumerable of source repositories.</param>
        /// <param name="packageIdentity">A package identity.</param>
        /// <param name="downloadContext">A package download context.</param>
        /// <param name="globalPackagesFolder">A global packages folder path.</param>
        /// <param name="logger">A logger.</param>
        /// <param name="token">A cancellation token.</param>
        /// <returns>A task that represents the asynchronous operation.
        /// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="DownloadResourceResult" />
        /// instance.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="sources" />
        /// is either <c>null</c> or empty.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageIdentity" />
        /// is either <c>null</c> or empty.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="downloadContext" />
        /// is either <c>null</c> or empty.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" />
        /// is either <c>null</c> or empty.</exception>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="token" />
        /// is cancelled.</exception>
        public static async Task <DownloadResourceResult> GetDownloadResourceResultAsync(
            IEnumerable <SourceRepository> sources,
            PackageIdentity packageIdentity,
            PackageDownloadContext downloadContext,
            string globalPackagesFolder,
            ILogger logger,
            CancellationToken token)
        {
            if (sources == null)
            {
                throw new ArgumentNullException(nameof(sources));
            }

            if (packageIdentity == null)
            {
                throw new ArgumentNullException(nameof(packageIdentity));
            }

            if (downloadContext == null)
            {
                throw new ArgumentNullException(nameof(downloadContext));
            }

            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            var failedTasks = new List <Task <DownloadResourceResult> >();
            var tasksLookup = new Dictionary <Task <DownloadResourceResult>, SourceRepository>();

            var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);

            try
            {
                // Create a group of local sources that will go first, then everything else.
                var groups     = new Queue <List <SourceRepository> >();
                var localGroup = new List <SourceRepository>();
                var otherGroup = new List <SourceRepository>();

                foreach (var source in sources)
                {
                    if (source.PackageSource.IsLocal)
                    {
                        localGroup.Add(source);
                    }
                    else
                    {
                        otherGroup.Add(source);
                    }
                }

                groups.Enqueue(localGroup);
                groups.Enqueue(otherGroup);

                bool isPackageNamespaceEnabled = downloadContext.PackageNamespacesConfiguration?.AreNamespacesEnabled == true;
                IReadOnlyList <string> configuredPackageSources = null;

                if (isPackageNamespaceEnabled)
                {
                    configuredPackageSources = downloadContext.PackageNamespacesConfiguration.GetConfiguredPackageSources(packageIdentity.Id);

                    if (configuredPackageSources != null)
                    {
                        var packageSourcesAtPrefix = string.Join(", ", configuredPackageSources);
                        logger.LogDebug(StringFormatter.Log_PackageNamespaceMatchFound(packageIdentity.Id, packageSourcesAtPrefix));
                    }
                    else
                    {
                        logger.LogDebug(StringFormatter.Log_PackageNamespaceNoMatchFound(packageIdentity.Id));
                    }
                }

                while (groups.Count > 0)
                {
                    token.ThrowIfCancellationRequested();

                    var sourceGroup = groups.Dequeue();
                    var tasks       = new List <Task <DownloadResourceResult> >();

                    foreach (SourceRepository source in sourceGroup)
                    {
                        if (isPackageNamespaceEnabled)
                        {
                            if (configuredPackageSources != null &&
                                !configuredPackageSources.Contains(source.PackageSource.Name, StringComparer.CurrentCultureIgnoreCase))
                            {
                                // This package's id prefix is not defined in current package source, let's skip.
                                continue;
                            }
                        }

                        var task = GetDownloadResourceResultAsync(
                            source,
                            packageIdentity,
                            downloadContext,
                            globalPackagesFolder,
                            logger,
                            linkedTokenSource.Token);

                        tasksLookup.Add(task, source);
                        tasks.Add(task);
                    }

                    while (tasks.Any())
                    {
                        var completedTask = await Task.WhenAny(tasks);

                        if (completedTask.Status == TaskStatus.RanToCompletion)
                        {
                            tasks.Remove(completedTask);

                            // Cancel the other tasks, since, they may still be running
                            linkedTokenSource.Cancel();

                            if (tasks.Any())
                            {
                                // NOTE: Create a Task out of remainingTasks which waits for all the tasks to complete
                                // and disposes the linked token source safely. One of the tasks could try to access
                                // its incoming CancellationToken to register a callback. If the linkedTokenSource was
                                // disposed before being accessed, it will throw an ObjectDisposedException.
                                // At the same time, we do not want to wait for all the tasks to complete before
                                // before this method returns with a DownloadResourceResult.
                                var remainingTasks = Task.Run(async() =>
                                {
                                    try
                                    {
                                        await Task.WhenAll(tasks);
                                    }
                                    catch
                                    {
                                        // Any exception from one of the remaining tasks is not actionable.
                                        // And, this code is running on the threadpool and the task is not awaited on.
                                        // Catch all and do nothing.
                                    }
                                    finally
                                    {
                                        linkedTokenSource.Dispose();
                                    }
                                });
                            }

                            return(completedTask.Result);
                        }
                        else
                        {
                            token.ThrowIfCancellationRequested();

                            // In this case, completedTask did not run to completion.
                            // That is, it faulted or got canceled. Remove it, and try Task.WhenAny again
                            tasks.Remove(completedTask);
                            failedTasks.Add(completedTask);
                        }
                    }
                }

                // no matches were found
                var errors = new StringBuilder();

                errors.AppendLine(string.Format(CultureInfo.CurrentCulture,
                                                Strings.UnknownPackageSpecificVersion, packageIdentity.Id,
                                                packageIdentity.Version.ToNormalizedString()));

                foreach (var task in failedTasks)
                {
                    string message;

                    if (task.Exception == null)
                    {
                        message = task.Status.ToString();
                    }
                    else
                    {
                        message = ExceptionUtilities.DisplayMessage(task.Exception);
                    }

                    errors.AppendLine($"  {tasksLookup[task].PackageSource.Source}: {message}");
                }

                throw new FatalProtocolException(errors.ToString());
            }
            catch
            {
                linkedTokenSource.Dispose();
                throw;
            }
        }