Skip to content

Conversation

@JanProvaznik
Copy link
Member

@JanProvaznik JanProvaznik commented Dec 10, 2025

fixes #12484
This PR migrates several file I/O tasks to use the new multithreadable task API by implementing the \IMultiThreadableTask\ interface and using \TaskEnvironment\ for thread-safe path resolution. The path absolutization is wrapped in try-catches to align error reporting with what exists in the tasks. (absolutization may throw on windows on net framework with invalid characters)

Changes

The following tasks now implement \IMultiThreadableTask\ and use \TaskEnvironment.GetAbsolutePath()\ instead of \Path.GetFullPath():

  • Copy.cs
  • Delete.cs
  • MakeDir.cs
  • RemoveDir.cs
  • Touch.cs

Why

\Path.GetFullPath()\ uses process-global state (current working directory) which is not thread-safe when multiple tasks run in parallel. The \TaskEnvironment.GetAbsolutePath()\ method provides thread-safe path resolution by using the task's execution context.

This enables these tasks to be safely executed in parallel when MSBuild's multithreading is enabled.

Copilot AI and others added 6 commits December 3, 2025 16:40
- Updated FileState.cs to accept TaskEnvironment parameter
- Migrated 7 File I/O tasks to IMultiThreadableTask:
  * Copy - added attribute, interface, TaskEnvironment property and updated path/environment access
  * Delete - migrated with proper path resolution
  * MakeDir - migrated with path deduplication
  * RemoveDir - migrated with directory operations
  * Touch - migrated with file timestamp operations
  * ReadLinesFromFile - migrated for file reading
  * WriteLinesToFile - migrated for file writing
- All tasks use MSBuildMultiThreadableTaskAttribute for routing
- All tasks implement IMultiThreadableTask for TaskEnvironment access
- Tasks use TaskEnvironment.GetAbsolutePath() for thread-safe path resolution
- Tasks use TaskEnvironment.GetEnvironmentVariable() for thread-safe environment access

Co-authored-by: JanProvaznik <[email protected]>
Copilot AI review requested due to automatic review settings December 10, 2025 16:21
@JanProvaznik JanProvaznik changed the title Remove null-conditional operators for TaskEnvironment in file I/O tasks Migrate file I/O tasks to multithreadable task API Dec 10, 2025
@JanProvaznik JanProvaznik requested a review from AR-May December 10, 2025 16:25
Copy link
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

This PR converts several file I/O tasks to support multi-threaded execution by implementing IMultiThreadableTask and using TaskEnvironment for thread-safe path resolution. The changes replace static path resolution methods with instance-based TaskEnvironment.GetAbsolutePath() calls to ensure proper path handling in concurrent scenarios.

Key Changes:

  • Added IMultiThreadableTask interface and [MSBuildMultiThreadableTask] attribute to 7 file I/O task classes
  • Introduced TaskEnvironment property to all modified tasks for thread-safe operations
  • Modified FileState constructor to require TaskEnvironment parameter and use it for path resolution

Reviewed changes

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

Show a summary per file
File Description
src/Tasks/Touch.cs Added multithreading support; uses TaskEnvironment for path resolution in TouchFile method
src/Tasks/RemoveDir.cs Added multithreading support; resolves paths using TaskEnvironment in Execute and RemoveDirectory methods
src/Tasks/MakeDir.cs Added multithreading support; uses TaskEnvironment for path resolution and deduplication
src/Tasks/FileState.cs Modified constructor to require TaskEnvironment parameter; uses it for absolute path resolution in Lazy initialization
src/Tasks/FileIO/WriteLinesToFile.cs Added multithreading support; uses TaskEnvironment for path resolution instead of FileUtilities.NormalizePath
src/Tasks/FileIO/ReadLinesFromFile.cs Added multithreading support; resolves file paths using TaskEnvironment before file operations
src/Tasks/Delete.cs Added multithreading support; uses TaskEnvironment for path resolution in delete operations
src/Tasks/Copy.cs Added multithreading support; uses TaskEnvironment for path resolution, environment variables, and FileState construction; changed PathsAreIdentical from static to instance method

@AR-May AR-May self-assigned this Dec 12, 2025
Copy link
Member

@AR-May AR-May left a comment

Choose a reason for hiding this comment

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

LGTM, but I would like one more review to ensure there is no behavior change in multi process mode.

@JanProvaznik
Copy link
Member Author

added defensiveness: all TaskEnvironment.GetAbsolutePaths happen in trycatches and the errors are logged in line with the specific task's logging

@JanProvaznik
Copy link
Member Author

JanProvaznik commented Jan 8, 2026

exp insertion for validation passed

@JanProvaznik JanProvaznik self-assigned this Jan 9, 2026
MSBuildEventSource.Log.CopyUpToDateStart(destItem.ItemSpec);
MSBuildEventSource.Log.CopyUpToDateStart(destAbsolutePath);
bool copyComplete = partitionIndex > 0 &&
String.Equals(
Copy link
Member

Choose a reason for hiding this comment

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

It looks like some perf optimization for repeated file copy requests. Can't we save some perf and use original file paths for it? Casting to AbsolutePath seems not needed.

/// </summary>
/// <param name="filePath">The resolved absolute path, if successful.</param>
/// <returns>True if the path was resolved successfully; false if an error occurred.</returns>
private bool TryGetAbsoluteFilePath(out AbsolutePath filePath)
Copy link
Member

Choose a reason for hiding this comment

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

It looks that we often want to catch exceptions when absolutizing the paths. Should we maybe consider making it the part of the API?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's a good high pri idea and also an answer to Yuliia's comment about repetition. I think it should be factored out of the PR not to bloat it more.
We didn't anticipate this need because we found out too late that .NET framework throws when you concatenate paths...

/// <param name="path">The path to resolve.</param>
/// <param name="absolutePath">The resolved absolute path, if successful.</param>
/// <returns>True if the path was resolved successfully; false if an error occurred.</returns>
private bool TryGetAbsolutePath(string path, out AbsolutePath absolutePath)
Copy link
Member

@YuliiaKovalova YuliiaKovalova Jan 9, 2026

Choose a reason for hiding this comment

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

you repeat it in other tasks, can we avoid it?

Copy link
Member Author

Choose a reason for hiding this comment

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

the methods look very similar but are subtly different (different error messages with variant number of params, adding lockcheck message, "fixing" the file path. I think we should provide a nicer way as part of the API as a high pri item I can get started on in which we then use the new API, I think it should be factored out of this PR.

Copy link
Member

Choose a reason for hiding this comment

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

I suggested to consider making it part of the TaskEnvironment API. Then when it returns false you can write whatever message needed. Since we find it useful in a lot of our tasks, it might be useful for customers as well.

Copy link
Member

Choose a reason for hiding this comment

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

I suggest making a helper function that does it for our repo tasks, and we can add the API later. As I mentioned you can check for the return value to be false to write different messages, so there is possibility to deduplicate this code somewhat.

if (destinationFileState.DirectoryExists)
{
Log.LogErrorWithCodeFromResources("Copy.DestinationIsDirectory", sourceFileState.Name, destinationFileState.Name);
Log.LogErrorWithCodeFromResources("Copy.DestinationIsDirectory", sourceFileState.Name.Original, destinationFileState.Name);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Log.LogErrorWithCodeFromResources("Copy.DestinationIsDirectory", sourceFileState.Name.Original, destinationFileState.Name);
Log.LogErrorWithCodeFromResources("Copy.DestinationIsDirectory", sourceFileState.Name.Original, destinationFileState.Name).Original;

/// Not normalized.
/// </summary>
internal string Name => _filename;
internal AbsolutePath Name => _filename;
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to review all usages of the Name property after this change. Depending on the scenario, the correct value is either Original or the absolutized path. There are already cases where the new absolutized value is being used incorrectly instead of the original path.

var task = new Copy { BuildEngine = new MockEngine(true), };
var task = new Copy
{
TaskEnvironment = TaskEnvironmentHelper.CreateForTest(),
Copy link
Member

Choose a reason for hiding this comment

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

nit: I see that we have to add TaskEnvironment everywhere now - for testing, maybe it makes sense to have a factory function like T Test.NewTask<T>(IBuildEngine engine) where T: Task where we can set the TaskEnvironment to the test-specific one by default? And then for specific tests where we want to test the 'live' logic or w/e you'd still set it manually.

Just thinking about maintainability + patterns that might be easier for the LLMs to follow, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Copy task to the new task API

4 participants