Skip to content

Commit 8a2bf80

Browse files
authored
Add concurrent queues to necessary methods (#2064)
1 parent 2abcb8e commit 8a2bf80

1 file changed

Lines changed: 45 additions & 17 deletions

File tree

‎src/code/InstallHelper.cs‎

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,27 @@ private ConcurrentDictionary<string, Hashtable> DownloadParentAndDeps(
874874
if (!skipDependencyCheck)
875875
{
876876
// List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method.
877-
parentAndDeps.AddRange(_findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
877+
IEnumerable<PSResourceInfo> depPkgs = _findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
878878
debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps(), found all dependencies");
879+
880+
// Do not reinstall dependencies that are already installed, unless -Reinstall was specified or an explicit
881+
// -TargetFramework/-RuntimeIdentifier requires re-downloading to merge additional platform content.
882+
// _packagesOnMachine is only mutated on the pipeline thread after this parallel phase, so reading it here is safe.
883+
bool skipInstalledDeps = !_reinstall && string.IsNullOrEmpty(_targetFramework) && string.IsNullOrEmpty(_runtimeIdentifier);
884+
foreach (PSResourceInfo depPkg in depPkgs)
885+
{
886+
if (skipInstalledDeps && _packagesOnMachine != null)
887+
{
888+
string depPkgVersion = string.IsNullOrEmpty(depPkg.Prerelease) ? depPkg.Version.ToString() : $"{depPkg.Version}-{depPkg.Prerelease}";
889+
if (_packagesOnMachine.Contains($"{depPkg.Name}{depPkgVersion}"))
890+
{
891+
verboseMsgs.Enqueue($"Dependency '{depPkg.Name}' with version '{depPkgVersion}' is already installed.");
892+
continue;
893+
}
894+
}
895+
896+
parentAndDeps.Add(depPkg);
897+
}
879898
}
880899

881900
parentAndDeps.Add(pkgToInstall);
@@ -1068,7 +1087,7 @@ private bool TryInstallToTempPath(
10681087
var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName, pkgVersion);
10691088
Directory.CreateDirectory(tempDirNameVersion);
10701089

1071-
if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out ErrorRecord error))
1090+
if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, warningMsgs, debugMsgs, out ErrorRecord error))
10721091
{
10731092
Utils.EnqueueIfNotNull(errorMsgs, error);
10741093
return false;
@@ -1296,7 +1315,16 @@ private bool TrySaveNupkgToTempPath(
12961315
/// - Root-level RID folder entries (e.g., win-x64/) based on the current platform's RID
12971316
/// - lib/{tfm}/ entries to only extract the best matching Target Framework Moniker
12981317
/// </summary>
1299-
private bool TryExtractToDirectory(string zipPath, string extractPath, out ErrorRecord error)
1318+
/// <remarks>
1319+
/// This method may run on a background thread (via Parallel.ForEach), so it must not call
1320+
/// cmdlet Write* methods directly. Messages are enqueued and written out by the pipeline thread.
1321+
/// </remarks>
1322+
private bool TryExtractToDirectory(
1323+
string zipPath,
1324+
string extractPath,
1325+
ConcurrentQueue<string> warningMsgs,
1326+
ConcurrentQueue<string> debugMsgs,
1327+
out ErrorRecord error)
13001328
{
13011329
error = null;
13021330
// Normalize the path
@@ -1321,24 +1349,24 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
13211349
bestLibFramework = NuGetFramework.ParseFolder(_targetFramework);
13221350
if (bestLibFramework == null || bestLibFramework.IsUnsupported)
13231351
{
1324-
_cmdletPassedIn.WriteDebug($"Could not parse specified TargetFramework '{_targetFramework}', falling back to auto-detection.");
1325-
bestLibFramework = GetBestLibFramework(archive);
1352+
debugMsgs.Enqueue($"Could not parse specified TargetFramework '{_targetFramework}', falling back to auto-detection.");
1353+
bestLibFramework = GetBestLibFramework(archive, debugMsgs);
13261354
}
13271355
else
13281356
{
1329-
_cmdletPassedIn.WriteDebug($"Using user-specified TargetFramework: {bestLibFramework.GetShortFolderName()}");
1357+
debugMsgs.Enqueue($"Using user-specified TargetFramework: {bestLibFramework.GetShortFolderName()}");
13301358
}
13311359
}
13321360
else
13331361
{
1334-
bestLibFramework = GetBestLibFramework(archive);
1362+
bestLibFramework = GetBestLibFramework(archive, debugMsgs);
13351363
}
13361364

13371365
// Warn if TFM filtering is skipping assemblies for a different .NET lineage
13381366
// (e.g., installing on PS7/.NET 8 but package also has net472 for WinPS 5.1)
13391367
if (bestLibFramework != null)
13401368
{
1341-
WarnIfCrossLineageTfmSkipped(archive, bestLibFramework);
1369+
WarnIfCrossLineageTfmSkipped(archive, bestLibFramework, warningMsgs);
13421370
}
13431371

13441372
foreach (ZipArchiveEntry entry in archive.Entries.Where(entry => entry.CompressedLength > 0))
@@ -1351,15 +1379,15 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
13511379

13521380
if (!includeEntry)
13531381
{
1354-
_cmdletPassedIn.WriteDebug($"Skipping runtime entry not matching target platform: {entry.FullName}");
1382+
debugMsgs.Enqueue($"Skipping runtime entry not matching target platform: {entry.FullName}");
13551383
continue;
13561384
}
13571385
}
13581386

13591387
// TFM filtering: for lib/ entries, only extract the best matching TFM
13601388
if (bestLibFramework != null && !ShouldIncludeLibEntry(entry.FullName, bestLibFramework))
13611389
{
1362-
_cmdletPassedIn.WriteDebug($"Skipping lib entry not matching target framework: {entry.FullName}");
1390+
debugMsgs.Enqueue($"Skipping lib entry not matching target framework: {entry.FullName}");
13631391
continue;
13641392
}
13651393

@@ -1412,7 +1440,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
14121440
.Select(s => s[1])
14131441
.Distinct(StringComparer.OrdinalIgnoreCase);
14141442
string available = string.Join(", ", availableTfms);
1415-
_cmdletPassedIn.WriteWarning(
1443+
warningMsgs.Enqueue(
14161444
$"The specified TargetFramework '{_targetFramework}' was not found in this package. " +
14171445
$"No lib/ assemblies were installed. Available TFMs: {(string.IsNullOrEmpty(available) ? "none" : available)}");
14181446
}
@@ -1427,7 +1455,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
14271455
if (!hasMatchingRid)
14281456
{
14291457
string available = string.Join(", ", availableRids);
1430-
_cmdletPassedIn.WriteWarning(
1458+
warningMsgs.Enqueue(
14311459
$"The specified RuntimeIdentifier '{_runtimeIdentifier}' was not found in this package. " +
14321460
$"No platform-specific assets were installed. Available RIDs: {(string.IsNullOrEmpty(available) ? "none" : available)}");
14331461
}
@@ -1454,7 +1482,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
14541482
/// </summary>
14551483
/// <param name="archive">The zip archive to analyze.</param>
14561484
/// <returns>The best matching NuGetFramework, or null if no lib/ folders exist or no match is found.</returns>
1457-
private NuGetFramework GetBestLibFramework(ZipArchive archive)
1485+
private NuGetFramework GetBestLibFramework(ZipArchive archive, ConcurrentQueue<string> debugMsgs)
14581486
{
14591487
// Collect all TFMs from lib/ folder entries
14601488
var libFrameworks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -1504,14 +1532,14 @@ private NuGetFramework GetBestLibFramework(ZipArchive archive)
15041532

15051533
if (bestMatch != null)
15061534
{
1507-
_cmdletPassedIn.WriteDebug($"Selected best matching TFM: {bestMatch.GetShortFolderName()} (from {string.Join(", ", libFrameworks)})");
1535+
debugMsgs.Enqueue($"Selected best matching TFM: {bestMatch.GetShortFolderName()} (from {string.Join(", ", libFrameworks)})");
15081536
}
15091537

15101538
return bestMatch;
15111539
}
15121540
catch (Exception e)
15131541
{
1514-
_cmdletPassedIn.WriteDebug($"TFM selection failed, extracting all lib/ folders: {e.Message}");
1542+
debugMsgs.Enqueue($"TFM selection failed, extracting all lib/ folders: {e.Message}");
15151543
return null;
15161544
}
15171545
}
@@ -1609,7 +1637,7 @@ private static NuGetFramework GetCurrentFramework()
16091637
/// .NET Framework assemblies (net472), those will be skipped — which means the module
16101638
/// won't work if later used on Windows PowerShell 5.1.
16111639
/// </summary>
1612-
private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bestFramework)
1640+
private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bestFramework, ConcurrentQueue<string> warningMsgs)
16131641
{
16141642
try
16151643
{
@@ -1653,7 +1681,7 @@ private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bes
16531681
string skippedList = string.Join(", ", skippedLineageTfms);
16541682
string otherHost = bestIsNetCore ? "Windows PowerShell 5.1" : "PowerShell 7+";
16551683
string skippedTfm = skippedLineageTfms.First();
1656-
_cmdletPassedIn.WriteWarning(
1684+
warningMsgs.Enqueue(
16571685
$"This package contains assemblies for {skippedList} which were not installed because " +
16581686
$"the current runtime selected {bestFramework.GetShortFolderName()}. " +
16591687
$"If you also use this module on {otherHost}, run: Install-PSResource -Name <ModuleName> -TargetFramework '{skippedTfm}'");

0 commit comments

Comments
 (0)