diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index 06a0e9df0..e4c9dd8d5 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -46,10 +46,6 @@ internal class FindHelper // If running 'Install-PSResource Az, TestModule, NewTestModule', it will contain one parent and its dependencies. private ConcurrentDictionary> _packagesFound; - // Creates a new instance of depPkgsFound each time FindDependencyPackages() is called. - // This will eventually return the PSResourceInfo object to the main cmdlet class. - private ConcurrentDictionary depPkgsFound; - // Contains the latest found version of a particular package. private ConcurrentDictionary _knownLatestPkgVersion; @@ -1060,13 +1056,36 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R // After retrieving all packages find their dependencies if (_includeDependencies) { - foreach (PSResourceInfo currentPkg in parentPkgs) + // Resolving each parent package's dependency closure is independent work, so do it concurrently. + // yield return cannot be used inside Parallel.ForEach, so collect results into a thread-safe bag first. + ConcurrentBag dependencyPkgs = new ConcurrentBag(); + int processorCount = Environment.ProcessorCount; + int maxDegreeOfParallelism = processorCount * 4; + if (parentPkgs.Count > processorCount) + { + Parallel.ForEach(parentPkgs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, currentPkg => + { + foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository)) + { + dependencyPkgs.Add(pkgDep); + } + }); + } + else { - foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository)) + foreach (PSResourceInfo currentPkg in parentPkgs) { - yield return pkgDep; + foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository)) + { + dependencyPkgs.Add(pkgDep); + } } } + + foreach (PSResourceInfo pkgDep in dependencyPkgs) + { + yield return pkgDep; + } } } @@ -1158,15 +1177,17 @@ private string FormatPkgVersionString(PSResourceInfo pkg) internal IEnumerable FindDependencyPackages(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository) { - depPkgsFound = new ConcurrentDictionary(); + // Use a local instance so multiple parent packages can resolve their dependency closures concurrently + // without racing on shared state. + ConcurrentDictionary depPkgsFound = new ConcurrentDictionary(); _cmdletPassedIn.WriteDebug($"In FindHelper::FindDependencyPackages() - {currentPkg.Name}"); - FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository); + FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound); return depPkgsFound.Values.ToList(); } // Method 2 - internal void FindDependencyPackagesHelper(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository) + internal void FindDependencyPackagesHelper(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, ConcurrentDictionary depPkgsFound) { ConcurrentQueue errorMsgs = new ConcurrentQueue(); ConcurrentQueue verboseMsgs = new ConcurrentQueue(); @@ -1185,7 +1206,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response Parallel.ForEach(currentPkg.Dependencies, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, dep => { debugMsgs.Enqueue($"Finding dependency '{dep.Name}' version range '{dep.VersionRange}'"); - FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); }); } else @@ -1193,7 +1214,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response foreach (var dep in currentPkg.Dependencies) { debugMsgs.Enqueue($"Finding dependency '{dep.Name}' version range '{dep.VersionRange}'"); - FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } } @@ -1208,6 +1229,7 @@ private void FindDependencyPackageVersion( ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, + ConcurrentDictionary depPkgsFound, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, @@ -1228,7 +1250,7 @@ private void FindDependencyPackageVersion( else { // Find this version from the server - depPkg = FindDependencyWithLowerBound(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + depPkg = FindDependencyWithLowerBound(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } } else if (dep.VersionRange.HasLowerBound && dep.VersionRange.MinVersion.Equals(dep.VersionRange.MaxVersion)) @@ -1245,7 +1267,7 @@ private void FindDependencyPackageVersion( } else { - depPkg = FindDependencyWithSpecificVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + depPkg = FindDependencyWithSpecificVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } } else @@ -1261,7 +1283,7 @@ private void FindDependencyPackageVersion( } else { - depPkg = FindDependencyWithUpperBound(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + depPkg = FindDependencyWithUpperBound(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } } } @@ -1273,6 +1295,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion( ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, + ConcurrentDictionary depPkgsFound, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, @@ -1351,7 +1374,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion( // This will eventually return the PSResourceInfo object to the main cmdlet class. debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found"); depPkgsFound.TryAdd(key, depPkg); - FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository); + FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound); } } } @@ -1366,6 +1389,7 @@ private PSResourceInfo FindDependencyWithLowerBound( ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, + ConcurrentDictionary depPkgsFound, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, @@ -1419,7 +1443,7 @@ private PSResourceInfo FindDependencyWithLowerBound( // This will eventually return the PSResourceInfo object to the main cmdlet class. debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found"); depPkgsFound.TryAdd(key, depPkg); - FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository); + FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound); } } } @@ -1434,6 +1458,7 @@ private PSResourceInfo FindDependencyWithUpperBound( ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, + ConcurrentDictionary depPkgsFound, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, @@ -1490,7 +1515,7 @@ private PSResourceInfo FindDependencyWithUpperBound( // This will eventually return the PSResourceInfo object to the main cmdlet class. debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found"); depPkgsFound.TryAdd(key, depPkg); - FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository); + FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound); } } } diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index e3f95b616..fe01209be 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -516,73 +516,123 @@ private List InstallPackages( FindHelper findHelper) { _cmdletPassedIn.WriteDebug("In InstallHelper::InstallPackages()"); - + List pkgsSuccessfullyInstalled = new(); - // Install parent package to the temp directory, - // Get the dependencies from the installed package, - // Install all dependencies to temp directory. - // If a single dependency fails to install, roll back by deleting the temp directory. + // ---------- Phase 1 (pipeline thread): resolve each parent package and evaluate ShouldProcess ---------- + // ShouldProcess / -WhatIf / -Confirm and Write* must run on the pipeline thread, so all gating happens + // here, before any parallel download work begins. Packages that pass the gate become work items. + List workItems = new(); foreach (var parentPackage in pkgNamesToInstall) { - string tempInstallPath = CreateInstallationTempPath(); - - try + PSResourceInfo pkgToInstall = FindParentPackage( + searchVersionType: _versionType, + specificVersion: _nugetVersion, + versionRange: _versionRange, + pkgNameToInstall: parentPackage, + repository: repository, + currentServer: currentServer, + currentResponseUtil: currentResponseUtil, + pkgVersion: out string pkgVersion, + errRecord: out ErrorRecord findErrRecord); + + if (findErrRecord != null) { - // Hashtable has the key as the package name - // and value as a Hashtable of specific package info: - // packageName, { version = "", isScript = "", isModule = "", pkg = "", etc. } - // Install parent package to the temp directory. - ConcurrentDictionary packagesHash = BeginPackageInstall( - searchVersionType: _versionType, - specificVersion: _nugetVersion, - versionRange: _versionRange, - pkgNameToInstall: parentPackage, - repository: repository, - currentServer: currentServer, - currentResponseUtil: currentResponseUtil, - tempInstallPath: tempInstallPath, - skipDependencyCheck: skipDependencyCheck, - packagesHash: new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase), - warning: out string warning, - errRecord: out ErrorRecord errRecord); - - // At this point all packages are installed to temp path. - if (errRecord != null) + if (findErrRecord.FullyQualifiedErrorId.Equals("PackageNotFound")) { - if (errRecord.FullyQualifiedErrorId.Equals("PackageNotFound")) - { - _cmdletPassedIn.WriteVerbose(errRecord.Exception.Message); - } - else - { - _cmdletPassedIn.WriteError(errRecord); - } - - continue; + _cmdletPassedIn.WriteVerbose(findErrRecord.Exception.Message); } - if (warning != null) + else { - _cmdletPassedIn.WriteWarning(warning); + _cmdletPassedIn.WriteError(findErrRecord); } - if (packagesHash.Count == 0) - { - continue; - } + continue; + } + + if (pkgToInstall == null) + { + continue; + } + + // Check to see if the pkg is already installed (unless -Reinstall was specified). + if (!_reinstall && _packagesOnMachine.Contains($"{pkgToInstall.Name}{pkgVersion}")) + { + _cmdletPassedIn.WriteWarning($"Resource '{pkgToInstall.Name}' with version '{pkgVersion}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter"); + + // Remove from tracking list of packages to install. + _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase)); + + continue; + } + + // ShouldProcess gate (handles -WhatIf / -Confirm) on the pipeline thread. + string shouldProcessTarget = _savePkg + ? $"Package to save: '{pkgToInstall.Name}', version: '{pkgVersion}'" + : $"Package to install: '{pkgToInstall.Name}', version: '{pkgVersion}'"; + if (!_cmdletPassedIn.ShouldProcess(shouldProcessTarget)) + { + continue; + } + + workItems.Add(new ParentInstallWorkItem + { + PkgToInstall = pkgToInstall, + PkgVersion = pkgVersion, + TempInstallPath = CreateInstallationTempPath() + }); + } - Hashtable parentPkgInfo = packagesHash[parentPackage] as Hashtable; - PSResourceInfo parentPkgObj = parentPkgInfo["psResourceInfoPkg"] as PSResourceInfo; + if (workItems.Count == 0) + { + return pkgsSuccessfullyInstalled; + } + + // ---------- Phase 2 (parallel): download each parent + its dependencies to its own temp path ---------- + // This is network-bound work. No pipeline-thread calls are made here; all host messages are queued + // per work item and drained in Phase 3. Each work item downloads into its own temp path and its own + // packagesHash, so there is no shared mutable state between parents. + int processorCount = Environment.ProcessorCount; + if (workItems.Count > 1) + { + int maxDegreeOfParallelism = processorCount * 4; + Parallel.ForEach(workItems, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, workItem => + { + workItem.PackagesHash = DownloadParentAndDeps( + workItem.PkgToInstall, workItem.PkgVersion, workItem.TempInstallPath, repository, + currentServer, currentResponseUtil, skipDependencyCheck, + workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs, + out bool succeeded); + workItem.Succeeded = succeeded; + }); + } + else + { + ParentInstallWorkItem workItem = workItems[0]; + workItem.PackagesHash = DownloadParentAndDeps( + workItem.PkgToInstall, workItem.PkgVersion, workItem.TempInstallPath, repository, + currentServer, currentResponseUtil, skipDependencyCheck, + workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs, + out bool succeeded); + workItem.Succeeded = succeeded; + } - // If -WhatIf is passed in, early out. - if (_cmdletPassedIn.MyInvocation.BoundParameters.ContainsKey("WhatIf") && (SwitchParameter)_cmdletPassedIn.MyInvocation.BoundParameters["WhatIf"] == true) + // ---------- Phase 3 (pipeline thread): drain messages, move content to final location, record results ---------- + // If a single dependency fails to install, roll back that parent by deleting its temp directory. + foreach (ParentInstallWorkItem workItem in workItems) + { + try + { + Utils.WriteOutConcurrentQueue(_cmdletPassedIn, workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs); + + if (!workItem.Succeeded || workItem.PackagesHash == null || workItem.PackagesHash.Count == 0) { - return pkgsSuccessfullyInstalled; + continue; } // Parent package and dependencies are now installed to temp directory. // Try to move all package directories from temp directory to final destination. - if (!TryMoveInstallContent(tempInstallPath, scope, packagesHash)) + if (!TryMoveInstallContent(workItem.TempInstallPath, scope, workItem.PackagesHash)) { _cmdletPassedIn.WriteError(new ErrorRecord( new InvalidOperationException(), @@ -592,9 +642,9 @@ private List InstallPackages( } else { - foreach (string pkgName in packagesHash.Keys) + foreach (string pkgName in workItem.PackagesHash.Keys) { - Hashtable pkgInfo = packagesHash[pkgName] as Hashtable; + Hashtable pkgInfo = workItem.PackagesHash[pkgName] as Hashtable; pkgsSuccessfullyInstalled.Add(pkgInfo["psResourceInfoPkg"] as PSResourceInfo); // Add each pkg to _packagesOnMachine (ie pkgs fully installed on the machine). @@ -610,11 +660,11 @@ private List InstallPackages( ErrorCategory.InvalidOperation, _cmdletPassedIn)); - throw e; + throw; } finally { - DeleteInstallationTempPath(tempInstallPath); + DeleteInstallationTempPath(workItem.TempInstallPath); } } @@ -622,9 +672,27 @@ private List InstallPackages( } /// - /// Installs a single package to the temporary path. + /// Tracks the per-parent state used to parallelize parent-package installation across the three phases. + /// Each parent has its own temp path, result hash, and message queues so there is no shared mutable state + /// during the parallel download phase. + /// + private sealed class ParentInstallWorkItem + { + public PSResourceInfo PkgToInstall; + public string PkgVersion; + public string TempInstallPath; + public ConcurrentDictionary PackagesHash; + public bool Succeeded; + public readonly ConcurrentQueue ErrorMsgs = new(); + public readonly ConcurrentQueue WarningMsgs = new(); + public readonly ConcurrentQueue DebugMsgs = new(); + public readonly ConcurrentQueue VerboseMsgs = new(); + } + + /// + /// Resolves the parent package to install (find + version selection). Must run on the pipeline thread. /// - private ConcurrentDictionary BeginPackageInstall( + private PSResourceInfo FindParentPackage( VersionType searchVersionType, NuGetVersion specificVersion, VersionRange versionRange, @@ -632,15 +700,12 @@ private ConcurrentDictionary BeginPackageInstall( PSRepositoryInfo repository, ServerApiCall currentServer, ResponseUtil currentResponseUtil, - string tempInstallPath, - bool skipDependencyCheck, - ConcurrentDictionary packagesHash, - out string warning, + out string pkgVersion, out ErrorRecord errRecord) { - _cmdletPassedIn.WriteDebug("In InstallHelper::BeginPackageInstall()"); + _cmdletPassedIn.WriteDebug("In InstallHelper::FindParentPackage()"); FindResults responses = null; - warning = null; + pkgVersion = null; errRecord = null; // Find the parent package that needs to be installed @@ -652,7 +717,7 @@ private ConcurrentDictionary BeginPackageInstall( if (findVersionGlobbingErrRecord != null || responses.IsFindResultsEmpty()) { errRecord = findVersionGlobbingErrRecord; - return packagesHash; + return null; } break; @@ -664,7 +729,7 @@ private ConcurrentDictionary BeginPackageInstall( if (findVersionErrRecord != null) { errRecord = findVersionErrRecord; - return packagesHash; + return null; } break; @@ -675,7 +740,7 @@ private ConcurrentDictionary BeginPackageInstall( if (findNameErrRecord != null) { errRecord = findNameErrRecord; - return packagesHash; + return null; } break; @@ -721,11 +786,11 @@ private ConcurrentDictionary BeginPackageInstall( if (pkgToInstall == null) { - return packagesHash; + return null; } pkgToInstall.RepositorySourceLocation = repository.Uri.ToString(); - pkgToInstall.AdditionalMetadata.TryGetValue("NormalizedVersion", out string pkgVersion); + pkgToInstall.AdditionalMetadata.TryGetValue("NormalizedVersion", out pkgVersion); if (pkgVersion == null) { // Not all NuGet providers (e.g. Artifactory, possibly others) send NormalizedVersion in NuGet package responses. @@ -745,117 +810,67 @@ private ConcurrentDictionary BeginPackageInstall( } // Check to see if the pkg is already installed (ie the pkg is installed and the version satisfies the version range provided via param) - // TODO: can use cache for this - if (!_reinstall) - { - string currPkgNameVersion = $"{pkgToInstall.Name}{pkgVersion}"; - // Use HashSet lookup instead of Contains for O(1) performance - if (_packagesOnMachine.Contains(currPkgNameVersion)) - { - _cmdletPassedIn.WriteWarning($"Resource '{pkgToInstall.Name}' with version '{pkgVersion}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter"); - - // Remove from tracking list of packages to install. - _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase)); - - return packagesHash; - } - } - - if (packagesHash.ContainsKey(pkgToInstall.Name)) - { - return packagesHash; - } + // Note: the already-installed check and ShouldProcess gate are handled by the caller on the pipeline thread. + return pkgToInstall; + } + /// + /// Downloads the parent package and its dependencies to the temporary path. Safe to run on worker threads: + /// all host messages are routed to the provided queues and drained by the caller on the pipeline thread. + /// + private ConcurrentDictionary DownloadParentAndDeps( + PSResourceInfo pkgToInstall, + string pkgVersion, + string tempInstallPath, + PSRepositoryInfo repository, + ServerApiCall currentServer, + ResponseUtil currentResponseUtil, + bool skipDependencyCheck, + ConcurrentQueue errorMsgs, + ConcurrentQueue warningMsgs, + ConcurrentQueue debugMsgs, + ConcurrentQueue verboseMsgs, + out bool success) + { + debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps()"); + ConcurrentDictionary packagesHash = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - ConcurrentDictionary updatedPackagesHash = packagesHash; - - // -WhatIf processing. - if (_savePkg && !_cmdletPassedIn.ShouldProcess($"Package to save: '{pkgToInstall.Name}', version: '{pkgVersion}'")) - { - updatedPackagesHash.TryAdd(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase) - { - { "isModule", "" }, - { "isScript", "" }, - { "psResourceInfoPkg", pkgToInstall }, - { "tempDirNameVersionPath", tempInstallPath }, - { "pkgVersion", "" }, - { "scriptPath", "" }, - { "installPath", "" } - }); - } - else if (!_cmdletPassedIn.ShouldProcess($"Package to install: '{pkgToInstall.Name}', version: '{pkgVersion}'")) + List parentAndDeps = new List(); + if (!skipDependencyCheck) { - if (!updatedPackagesHash.ContainsKey(pkgToInstall.Name)) - { - updatedPackagesHash.TryAdd(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase) - { - { "isModule", "" }, - { "isScript", "" }, - { "psResourceInfoPkg", pkgToInstall }, - { "tempDirNameVersionPath", tempInstallPath }, - { "pkgVersion", "" }, - { "scriptPath", "" }, - { "installPath", "" } - }); - } + // List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method. + parentAndDeps.AddRange(_findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository)); + debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps(), found all dependencies"); } - else - { - // Concurrent updates - // Find all dependencies - if (!skipDependencyCheck) - { - // concurrency updates - List parentAndDeps = _findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository).ToList(); - // List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method - parentAndDeps.Add(pkgToInstall); - _cmdletPassedIn.WriteDebug("In InstallHelper::BeginPackageInstall(), found all dependencies"); + parentAndDeps.Add(pkgToInstall); - return InstallParentAndDependencyPackages(parentAndDeps, currentServer, tempInstallPath, packagesHash, updatedPackagesHash, pkgToInstall); - } - else { - // If we don't install dependencies, we're only installing the parent pkg so we can short circut and simply install the parent pkg. - Stream responseStream = currentServer.InstallPackage(pkgToInstall.Name, pkgVersion, true, out ErrorRecord installNameErrRecord); - - if (installNameErrRecord != null) - { - errRecord = installNameErrRecord; - return packagesHash; - } - ConcurrentQueue errorMsgs = new ConcurrentQueue(); - ConcurrentQueue warningMsgs = new ConcurrentQueue(); - ConcurrentQueue debugMsgs = new ConcurrentQueue(); - ConcurrentQueue verboseMsgs = new ConcurrentQueue(); - - bool installedToTempPathSuccessfully = _asNupkg ? TrySaveNupkgToTempPath(responseStream, tempInstallPath, pkgToInstall.Name, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs) : - TryInstallToTempPath(responseStream, tempInstallPath, pkgToInstall.Name, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - - Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - if (!installedToTempPathSuccessfully) - { - return packagesHash; - } - } - } + ConcurrentDictionary updatedPackagesHash = InstallParentAndDependencyPackages( + parentAndDeps, currentServer, tempInstallPath, packagesHash, packagesHash, pkgToInstall, + errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + success = errorMsgs.IsEmpty; return updatedPackagesHash; } - private ConcurrentDictionary InstallParentAndDependencyPackages(List parentAndDeps, ServerApiCall currentServer, string tempInstallPath, ConcurrentDictionary packagesHash, ConcurrentDictionary updatedPackagesHash, PSResourceInfo pkgToInstall) + private ConcurrentDictionary InstallParentAndDependencyPackages( + List parentAndDeps, + ServerApiCall currentServer, + string tempInstallPath, + ConcurrentDictionary packagesHash, + ConcurrentDictionary updatedPackagesHash, + PSResourceInfo pkgToInstall, + ConcurrentQueue errorMsgs, + ConcurrentQueue warningMsgs, + ConcurrentQueue debugMsgs, + ConcurrentQueue verboseMsgs) { - string warning = string.Empty; - ConcurrentQueue errorMsgs = new ConcurrentQueue(); - ConcurrentQueue verboseMsgs = new ConcurrentQueue(); - ConcurrentQueue debugMsgs = new ConcurrentQueue(); - ConcurrentQueue warningMsgs = new ConcurrentQueue(); - // TODO: figure out a good threshold and parallel count int processorCount = Environment.ProcessorCount; - _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}"); + debugMsgs.Enqueue($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}"); if (parentAndDeps.Count > processorCount) { - _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is greater than processor count"); + debugMsgs.Enqueue($"parentAndDeps.Count is greater than processor count"); // Set the maximum degree of parallelism to 32? (Invoke-Command has default of 32, that's where we got this number from) // If installing more than 3 packages, do so concurrently // If the number of dependencies is very small (e.g., ≤ CPU cores), parallelism may add overhead instead of improving speed. @@ -891,8 +906,7 @@ private ConcurrentDictionary InstallParentAndDependencyPackag } }); - Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - if (errorMsgs.Count > 0) + if (!errorMsgs.IsEmpty) { return packagesHash; } @@ -910,16 +924,13 @@ private ConcurrentDictionary InstallParentAndDependencyPackag if (installNameErrRecord != null) { - _cmdletPassedIn.WriteError(installNameErrRecord); + errorMsgs.Enqueue(installNameErrRecord); return packagesHash; } - //ErrorRecord tempSaveErrRecord = null, tempInstallErrRecord = null; bool installedToTempPathSuccessfully = _asNupkg ? TrySaveNupkgToTempPath(responseStream, tempInstallPath, pkgToInstallName, pkgToInstallVersion, pkgToBeInstalled, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs) : TryInstallToTempPath(responseStream, tempInstallPath, pkgToInstallName, pkgToInstallVersion, pkgToBeInstalled, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - if (!installedToTempPathSuccessfully) { return packagesHash; diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs index d8c6b5ffe..e89b7f623 100644 --- a/src/code/NuGetServerAPICalls.cs +++ b/src/code/NuGetServerAPICalls.cs @@ -63,10 +63,9 @@ public override Task FindVersionAsync(string packageName, string ve }); var filterBuilder = queryBuilder.FilterBuilder; - // We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned. - filterBuilder.AddCriterion($"Id eq '{packageName}'"); - filterBuilder.AddCriterion($"NormalizedVersion eq '{packageName}'"); - +// We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned. +filterBuilder.AddCriterion($"Id eq '{packageName}'"); +filterBuilder.AddCriterion($"NormalizedVersion eq '{version}'"); var requestUrl = $"{Repository.Uri}/FindPackagesById()?{queryBuilder.BuildQueryString()}"; string response = HttpRequestCallAsync(requestUrl, debugMsgs, out ErrorRecord errRecord); FindResults findResponse = new FindResults(stringResponse: new string[] { response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType); diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs index 234fc513a..0291dc5b4 100644 --- a/src/code/V3ServerAPICalls.cs +++ b/src/code/V3ServerAPICalls.cs @@ -750,7 +750,7 @@ private FindResults FindVersionHelper(string packageName, string version, string return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); } - //_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'"); +debugMsgs.Enqueue($"'{packageName}' version parsed as '{requiredVersion}'"); string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, errorMsgs, debugMsgs, verboseMsgs); if (errRecord != null)