Skip to content

Multithreading migration: Group 8 — 3 COM & Shims tasks (Pattern B)#53119

Closed
SimaTian wants to merge 12 commits intodotnet:mainfrom
SimaTian:merge-group-8
Closed

Multithreading migration: Group 8 — 3 COM & Shims tasks (Pattern B)#53119
SimaTian wants to merge 12 commits intodotnet:mainfrom
SimaTian:merge-group-8

Conversation

@SimaTian
Copy link
Copy Markdown
Member

Summary

Migrate 3 COM and shim-related tasks to support multithreaded MSBuild execution. All 3 tasks perform file I/O and require full Pattern B migration (IMultiThreadableTask + TaskEnvironment).

Tasks Migrated

Task Key Changes
CreateComHost Absolutized ComHostSourcePath, ComHostDestinationPath, ClsidMapPath, type library map values
GenerateRegFreeComManifest Absolutized IntermediateAssembly, ClsidMapPath, ComManifestPath, type library map values
GenerateShims Absolutized apphost asset path, PackagedShimOutputDirectory, IntermediateAssembly; fixed ApphostsForShimRuntimeIdentifiers setter accessibility

Migration Details

All three tasks delegate to external library methods (ComHost.Create(), RegFreeComManifest.CreateManifestFromClsidmap(), HostWriter.CreateAppHost()) that perform file I/O using the paths provided. All paths are absolutized via TaskEnvironment.GetAbsolutePath() before being passed to these methods.

Type library dictionary values (TypeLibraries metadata) are also absolutized in a loop before use — these contain file paths that flow into COM registration file operations.

Tests Added

  • GivenACreateComHostMultiThreading.cs — path resolution test, CWD-independence parity test
  • GivenAGenerateRegFreeComManifestMultiThreading.cs — assembly resolution test, parity test
  • GivenAGenerateShimsMultiThreading.cs — parity test, boundary test, concurrent execution tests

Files Changed

  • src/Tasks/Microsoft.NET.Build.Tasks/CreateComHost.cs
  • src/Tasks/Microsoft.NET.Build.Tasks/GenerateRegFreeComManifest.cs
  • src/Tasks/Microsoft.NET.Build.Tasks/GenerateShims.cs
  • src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenACreateComHostMultiThreading.cs (new)
  • src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateRegFreeComManifestMultiThreading.cs (new)
  • src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateShimsMultiThreading.cs (new)

SimaTian and others added 4 commits February 22, 2026 16:27
Tasks: CreateComHost, GenerateRegFreeComManifest, GenerateShims.

Each task receives [MSBuildMultiThreadableTask] attribute, IMultiThreadableTask interface, TaskEnvironment property, and path absolutization. Includes multithreading tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace reflection-based TaskEnvironment assignment with direct property
  access in CreateComHost and GenerateRegFreeComManifest path-resolution tests
- Add Paths_AreResolvedRelativeToProjectDirectory test for GenerateShims,
  matching the pattern from CreateComHost and GenerateRegFreeComManifest

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change from 'private set' to 'set' to match the [Required] MSBuild property
contract and allow test assignment. Consistent with all neighboring task
input properties (ComHostSourcePath, IntermediateAssembly, etc.).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 22, 2026 20:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates three COM/shim-related MSBuild tasks to Pattern B multithreaded execution by implementing IMultiThreadableTask and resolving file-system paths via TaskEnvironment instead of relying on process current directory, with new unit tests intended to validate CWD-independence and concurrency behavior.

Changes:

  • Add IMultiThreadableTask + TaskEnvironment to CreateComHost, GenerateRegFreeComManifest, and GenerateShims.
  • Absolutize key input paths (and some type library metadata paths) before invoking external libraries that perform file I/O.
  • Add multithreading-focused unit tests for path resolution parity and concurrent execution.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/Tasks/Microsoft.NET.Build.Tasks/CreateComHost.cs Implements multithreaded task support and absolutizes COM host paths.
src/Tasks/Microsoft.NET.Build.Tasks/GenerateRegFreeComManifest.cs Implements multithreaded task support and absolutizes manifest-related paths.
src/Tasks/Microsoft.NET.Build.Tasks/GenerateShims.cs Implements multithreaded task support and absolutizes apphost/output/assembly paths.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenACreateComHostMultiThreading.cs Adds tests for CWD independence and concurrency (currently has cross-platform + deadlock risks).
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateRegFreeComManifestMultiThreading.cs Adds tests for path resolution parity and concurrency (currently has cross-platform + deadlock risks).
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateShimsMultiThreading.cs Adds tests for shims path resolution and concurrency (currently has cross-platform + deadlock risks).

Comment on lines +31 to +46
var apphostItem = new TaskItem("tools\\apphost.exe");
apphostItem.SetMetadata(MetadataKeys.RuntimeIdentifier, "linux-x64");

var task = new GenerateShims
{
BuildEngine = new MockBuildEngine(),
ApphostsForShimRuntimeIdentifiers = new ITaskItem[] { apphostItem },
IntermediateAssembly = "bin\\test.dll",
PackageId = "TestPackage",
PackageVersion = "1.0.0",
TargetFrameworkMoniker = ".NETCoreApp,Version=v8.0",
ToolCommandName = "test-tool",
ToolEntryPoint = "test-tool.dll",
PackagedShimOutputDirectory = "shims",
ShimRuntimeIdentifiers = new ITaskItem[] { new TaskItem("linux-x64") },
TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hard-codes Windows path separators in TaskItem ItemSpecs (e.g., tools\\apphost.exe, bin\\test.dll). On Unix these become different file names than the ones created with Path.Combine, so the test can fail. Construct these ItemSpecs with Path.Combine (or use /) to keep the test cross-platform.

Copilot uses AI. Check for mistakes.
TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
};

barrier.SignalAndWait();
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This concurrency test uses a Barrier without a timeout. If a worker throws before reaching SignalAndWait, the remaining workers can deadlock and hang CI. Add a timeout to SignalAndWait (and record a failure) or avoid barrier-based coordination.

Suggested change
barrier.SignalAndWait();
var signaled = barrier.SignalAndWait(System.TimeSpan.FromSeconds(30));
if (!signaled)
{
results.Add((false, "BarrierTimeout"));
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +53
// Absolutize type library paths
if (typeLibIdMap != null)
{
foreach (var key in typeLibIdMap.Keys.ToList())
{
typeLibIdMap[key] = TaskEnvironment.GetAbsolutePath(typeLibIdMap[key]);
}
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new loop absolutizes typeLibIdMap values, but typeLibIdMap is not used anywhere after TryCreateTypeLibraryIdDictionary succeeds. This makes the change effectively a no-op and adds confusing/unused work; either remove this absolutization (and potentially the dictionary creation if it’s only for validation), or wire the absolutized paths into the manifest generation logic if that was intended.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to 87
var resolvedApphostAssetPath = (string)TaskEnvironment.GetAbsolutePath(GetApphostAsset(ApphostsForShimRuntimeIdentifiers, runtimeIdentifier));

var packagedShimOutputDirectoryAndRid = Path.Combine(
PackagedShimOutputDirectory,
(string)TaskEnvironment.GetAbsolutePath(PackagedShimOutputDirectory),
runtimeIdentifier);
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackagedShimOutputDirectory is now absolutized before computing appHostDestinationFilePath, which means EmbeddedApphostPaths item specs become absolute when the input directory is relative. This may break consistency with GetEmbeddedApphostPaths (which still returns paths based on the raw PackagedShimOutputDirectory) and can cause incremental-build/publish item mismatches; consider keeping output item specs in the same form as before (while still using absolute paths for file I/O) or updating the expected-path computation accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +38
ComHostSourcePath = "source\\comhost.dll",
ComHostDestinationPath = "output\\comhost.dll",
ClsidMapPath = "source\\clsidmap.bin",
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hard-codes Windows path separators (e.g., source\\comhost.dll). On non-Windows agents this will be treated as a literal backslash in the filename, so the created files won’t be found and the test can fail/flap. Prefer Path.Combine(...) (or Path.Join) for ItemSpec strings in tests to keep them cross-platform.

Suggested change
ComHostSourcePath = "source\\comhost.dll",
ComHostDestinationPath = "output\\comhost.dll",
ClsidMapPath = "source\\clsidmap.bin",
ComHostSourcePath = Path.Combine("source", "comhost.dll"),
ComHostDestinationPath = Path.Combine("output", "comhost.dll"),
ClsidMapPath = Path.Combine("source", "clsidmap.bin"),

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +170
var barrier = new Barrier(parallelism);

Parallel.For(0, parallelism, new ParallelOptions { MaxDegreeOfParallelism = parallelism }, i =>
{
var projectDir = Path.Combine(Path.GetTempPath(), $"comhost-conc-{i}-{Guid.NewGuid():N}");
Directory.CreateDirectory(projectDir);
try
{
var sourceDir = Path.Combine(projectDir, "source");
var outputDir = Path.Combine(projectDir, "output");
Directory.CreateDirectory(sourceDir);
Directory.CreateDirectory(outputDir);
File.WriteAllText(Path.Combine(sourceDir, "comhost.dll"), "fake-comhost-pe");
File.WriteAllText(Path.Combine(sourceDir, "clsidmap.bin"), "fake-clsid");

var task = new CreateComHost
{
BuildEngine = new MockBuildEngine(),
ComHostSourcePath = Path.Combine("source", "comhost.dll"),
ComHostDestinationPath = Path.Combine("output", "comhost.dll"),
ClsidMapPath = Path.Combine("source", "clsidmap.bin"),
TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
};

barrier.SignalAndWait();
var result = task.Execute();
results.Add((result, "none"));
}
catch (Exception ex)
{
results.Add((false, ex.GetType().Name));
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Barrier.SignalAndWait() is used without a timeout; if any worker throws before reaching the barrier (e.g., due to I/O issues), the remaining workers can deadlock and hang the test run. Add a timeout (and fail when it expires) or switch to a non-blocking coordination primitive that can’t deadlock on early failure.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +38
IntermediateAssembly = $"bin\\{assemblyFileName}",
ComHostName = "test.comhost.dll",
ClsidMapPath = "bin\\clsidmap.bin",
ComManifestPath = "bin\\test.manifest",
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses bin\\... / clsidmap paths with Windows separators. On non-Windows this can resolve to a non-existent file because backslash is a valid filename character. Use Path.Combine("bin", ...) (or forward slashes) when constructing relative paths for cross-platform tests.

Suggested change
IntermediateAssembly = $"bin\\{assemblyFileName}",
ComHostName = "test.comhost.dll",
ClsidMapPath = "bin\\clsidmap.bin",
ComManifestPath = "bin\\test.manifest",
IntermediateAssembly = $"bin/{assemblyFileName}",
ComHostName = "test.comhost.dll",
ClsidMapPath = "bin/clsidmap.bin",
ComManifestPath = "bin/test.manifest",

Copilot uses AI. Check for mistakes.
TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
};

barrier.SignalAndWait();
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Barrier.SignalAndWait() is called without a timeout in this concurrency test. If any iteration fails before signaling the barrier, other iterations can block indefinitely and hang the test suite. Add a timeout and fail fast, or replace the barrier-based coordination with a safer approach.

Suggested change
barrier.SignalAndWait();
if (!barrier.SignalAndWait(System.TimeSpan.FromSeconds(30)))
{
results.Add((false, "BarrierTimeout"));
return;
}

Copilot uses AI. Check for mistakes.
…code

- Replace Barrier+Parallel.For with ManualResetEventSlim+Task.Run
- Replace Windows path separators with Path.Combine
- Remove dead typeLibIdMap absolutization in GenerateRegFreeComManifest
- Remove unnecessary (string) cast on GetAbsolutePath

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SimaTian SimaTian self-assigned this Feb 23, 2026
SimaTian and others added 4 commits February 23, 2026 17:08
- Add TaskEnvironment to EmptyShimRuntimeIdentifiers test
- Use non-empty PackagedShimOutputDirectory (GetAbsolutePath throws on empty)
- Catch FileNotFoundException for HostModel (ExcludeAssets=Runtime in .csproj)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… for merge-group-8

- Add TaskEnvironmentDefaults.cs for NETFRAMEWORK lazy-init fallback
- Apply lazy-init pattern to GenerateShims, CreateComHost, GenerateRegFreeComManifest
- Add empty ShimRuntimeIdentifiers early-return guard in GenerateShims

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The combining constructor AbsolutePath(string, AbsolutePath) used
Path.Combine without Path.GetFullPath, leaving '..' segments
unresolved. This caused output paths like 'dir\..\ClassLib\...'
instead of 'ClassLib\...', breaking string-based path comparisons
in downstream MSBuild targets and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SimaTian and others added 3 commits February 25, 2026 13:21
On .NET Framework, ProcessStartInfo defaults to UseShellExecute=true,
which prevents EnvironmentVariables from being applied.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Normalization is caller's responsibility, matching real MSBuild polyfill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use assembly-relative path instead of bare filename to avoid FileNotFound
when parallel tests change the process CWD via TaskTestEnvironment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SimaTian
Copy link
Copy Markdown
Member Author

Temporarily closing due to review-queue bottleneck — one big grouped PR has turned out harder to review than 5 individual task PRs (see #52936 thread).

Strategy update: tasks in this PR have been recorded in our internal postponed-tasks backlog and will be re-opened as individual per-task PRs once the current merge-group-2 and merge-group-5 splits are cleared. Note that some tasks in this group (ValidateExecutableReferences, ProcessFrameworkReferences, ResolveTargetingPackAssets) have already been delivered via individual PRs based on OrchardCore profiling priority — see #53942, #53943, #53944, #53945, #53946.

@SimaTian SimaTian closed this Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants