Skip to content

Migrate FindItemsFromPackages to IMultiThreadableTask#53952

Closed
SimaTian wants to merge 2 commits intodotnet:mainfrom
SimaTian:migrate-find-items-from-packages
Closed

Migrate FindItemsFromPackages to IMultiThreadableTask#53952
SimaTian wants to merge 2 commits intodotnet:mainfrom
SimaTian:migrate-find-items-from-packages

Conversation

@SimaTian
Copy link
Copy Markdown
Member

Migrates \FindItemsFromPackages\ to support \IMultiThreadableTask.

Changes

  • Pattern A (pure in-memory): attribute + interface + stub TaskEnvironment. Task only reads \NuGetPackageId/\NuGetPackageVersion\ metadata via pure helper \ItemUtilities.GetPackageIdentity().
  • Uses \MetadataKeys.NuGetPackageId\ / \MetadataKeys.NuGetPackageVersion\ constants.
  • Adds 7 behavioral tests in a dedicated file using start-gate pattern.

Addresses review feedback from #53116

  • ✅ Start-gate pattern replaces Barrier + Parallel.For.
  • ✅ Dedicated per-task test file.
  • ✅ MetadataKeys constants.

Supersedes

Part of the split of stuck merge-group PR #53116. This is the 3-of-5 split for \FindItemsFromPackages.

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 FindItemsFromPackages to support MSBuild multithreaded execution by implementing IMultiThreadableTask and adding dedicated concurrency-focused unit tests.

Changes:

  • Update FindItemsFromPackages to implement IMultiThreadableTask and expose a TaskEnvironment property.
  • Add a new unit test suite validating interface implementation plus concurrent execution behavior via a start-gate pattern.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
src/Tasks/Microsoft.NET.Build.Tasks/FindItemsFromPackages.cs Implements IMultiThreadableTask and adds TaskEnvironment plumbing for multithreaded execution.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAFindItemsFromPackagesMultiThreading.cs Adds behavioral + concurrency tests to validate correctness under parallel execution.

Comment on lines +27 to +34

var multiThreadable = task as IMultiThreadableTask;
multiThreadable.Should().NotBeNull();

var property = typeof(FindItemsFromPackages).GetProperty(nameof(IMultiThreadableTask.TaskEnvironment));
property.Should().NotBeNull();
property.CanRead.Should().BeTrue();
property.CanWrite.Should().BeTrue();
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This test name says the TaskEnvironment property "can be set", but the test only checks via reflection that the property exists and is readable/writable. Consider actually setting TaskEnvironment on the task instance and asserting that the getter returns the same value (or that it can be accessed without throwing).

Suggested change
var multiThreadable = task as IMultiThreadableTask;
multiThreadable.Should().NotBeNull();
var property = typeof(FindItemsFromPackages).GetProperty(nameof(IMultiThreadableTask.TaskEnvironment));
property.Should().NotBeNull();
property.CanRead.Should().BeTrue();
property.CanWrite.Should().BeTrue();
var multiThreadable = task as IMultiThreadableTask;
multiThreadable.Should().NotBeNull();
var property = typeof(FindItemsFromPackages).GetProperty(nameof(IMultiThreadableTask.TaskEnvironment));
property.Should().NotBeNull();
property.CanRead.Should().BeTrue();
property.CanWrite.Should().BeTrue();
var taskEnvironment = Activator.CreateInstance(property.PropertyType);
taskEnvironment.Should().NotBeNull();
property.SetValue(task, taskEnvironment);
property.GetValue(task).Should().BeSameAs(taskEnvironment);

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +118
// Start-gate pattern to ensure maximum concurrency
var startGate = new ManualResetEventSlim(false);
var taskList = new List<Task>();

for (int i = 0; i < threadCount; i++)
{
int taskIndex = i;
taskList.Add(Task.Run(() =>
{
// Wait for start signal
startGate.Wait();

try
{
bool success = tasks[taskIndex].Execute();
success.Should().BeTrue($"Task {taskIndex} should succeed");
results.Add(tasks[taskIndex].ItemsFromPackages);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}));
}

// Release all threads simultaneously
startGate.Set();

// Wait for all tasks to complete
Task.WaitAll(taskList.ToArray());

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

These concurrency tests use startGate.Wait() and Task.WaitAll() without any timeout. If a task deadlocks or a thread never starts, this can hang the test run indefinitely. Consider adding timeouts (and failing the test with a clear message) and disposing the ManualResetEventSlim (e.g., via using) to avoid CI hangs/leaks.

Copilot uses AI. Check for mistakes.
Comment on lines +184 to +202
var startGate = new ManualResetEventSlim(false);
var taskList = new List<Task>();

for (int i = 0; i < threadCount; i++)
{
int taskIndex = i;
taskList.Add(Task.Run(() =>
{
startGate.Wait();

bool success = tasks[taskIndex].Execute();
success.Should().BeTrue();
results[taskIndex] = tasks[taskIndex].ItemsFromPackages;
}));
}

startGate.Set();
Task.WaitAll(taskList.ToArray());

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This test uses startGate.Wait() and Task.WaitAll() without a timeout. To avoid rare but costly CI hangs, consider adding timeouts (with a clear assertion message) and disposing startGate (ManualResetEventSlim implements IDisposable).

Copilot uses AI. Check for mistakes.
Comment on lines +242 to +261
var startGate = new ManualResetEventSlim(false);
var taskList = new List<Task>();
var results = new ConcurrentBag<ITaskItem[]>();

for (int i = 0; i < threadCount; i++)
{
int taskIndex = i;
taskList.Add(Task.Run(() =>
{
startGate.Wait();

bool success = tasks[taskIndex].Execute();
success.Should().BeTrue();
results.Add(tasks[taskIndex].ItemsFromPackages);
}));
}

startGate.Set();
Task.WaitAll(taskList.ToArray());

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This test uses startGate.Wait() and Task.WaitAll() without any timeout. Adding timeouts (and disposing the ManualResetEventSlim) would prevent indefinite hangs if something goes wrong during concurrent execution.

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +314
var startGate = new ManualResetEventSlim(false);
var taskList = new List<Task>();

for (int i = 0; i < threadCount; i++)
{
int taskIndex = i;
taskList.Add(Task.Run(() =>
{
startGate.Wait();

bool success = tasks[taskIndex].Execute();
success.Should().BeTrue();
results.Add(tasks[taskIndex].ItemsFromPackages);
}));
}

startGate.Set();
Task.WaitAll(taskList.ToArray());

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This test uses startGate.Wait() and Task.WaitAll() without a timeout. Consider adding timeouts (and disposing ManualResetEventSlim) to avoid indefinite hangs in CI if a task never completes.

Copilot uses AI. Check for mistakes.
Comment on lines +360 to +378
var startGate = new ManualResetEventSlim(false);
var taskList = new List<Task>();

for (int i = 0; i < threadCount; i++)
{
int taskIndex = i;
taskList.Add(Task.Run(() =>
{
startGate.Wait();

bool success = tasks[taskIndex].Execute();
success.Should().BeTrue();
results.Add(tasks[taskIndex].ItemsFromPackages);
}));
}

startGate.Set();
Task.WaitAll(taskList.ToArray());

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This test uses startGate.Wait() and Task.WaitAll() without any timeout. Adding timeouts (and disposing the ManualResetEventSlim) would help prevent the test suite from hanging indefinitely under failure conditions.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
get => _taskEnvironment ??= new TaskEnvironment(new ProcessTaskEnvironmentDriver(Directory.GetCurrentDirectory()));
set => _taskEnvironment = value;
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

On NETFRAMEWORK, the TaskEnvironment getter lazily initializes using Directory.GetCurrentDirectory(), which is process-global and may not match the project directory when tasks are executed concurrently for multiple projects. Since TaskEnvironment is intended to provide the per-project execution context, consider removing this CWD-based fallback and relying on MSBuild to set TaskEnvironment, or derive the directory from BuildEngine.ProjectFileOfTaskNode (when available) instead of Directory.GetCurrentDirectory().

Suggested change
get => _taskEnvironment ??= new TaskEnvironment(new ProcessTaskEnvironmentDriver(Directory.GetCurrentDirectory()));
set => _taskEnvironment = value;
}
get => _taskEnvironment ??= CreateTaskEnvironment();
set => _taskEnvironment = value;
}
private TaskEnvironment CreateTaskEnvironment()
{
string projectFile = BuildEngine?.ProjectFileOfTaskNode;
if (string.IsNullOrEmpty(projectFile))
{
throw new InvalidOperationException("TaskEnvironment was not set and the project file path is unavailable from the build engine.");
}
string projectDirectory = System.IO.Path.GetDirectoryName(projectFile);
if (string.IsNullOrEmpty(projectDirectory))
{
throw new InvalidOperationException("TaskEnvironment was not set and the project directory could not be determined from the build engine project file path.");
}
return new TaskEnvironment(new ProcessTaskEnvironmentDriver(projectDirectory));
}

Copilot uses AI. Check for mistakes.
SimaTian and others added 2 commits April 29, 2026 13:53
- Add IMultiThreadableTask interface implementation
- Add TaskEnvironment property stub (pure in-memory task)
- Create comprehensive multithreading test suite:
  * Interface implementation verification
  * Concurrent execution with start-gate pattern
  * Independent package filtering per thread
  * Empty input handling
  * Metadata preservation
  * Version matching correctness
- All 7 tests passed

This task is pure in-memory (no file system or environment access),
so only attribute + stub property are needed per Pattern A.

Co-authored-by: Copilot <[email protected]>
@AR-May
Copy link
Copy Markdown
Member

AR-May commented May 5, 2026

The task is already enlightened, the tests looks to me having very limited value.

@AR-May AR-May closed this May 5, 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.

3 participants