Skip to content

Add default fallback TaskEnvironment to all IMultiThreadableTask implementations#13415

Merged
JanProvaznik merged 7 commits intodotnet:mainfrom
JanProvaznik:dev/janprovaznik/fallback-multiprocess-task-env
Mar 19, 2026
Merged

Add default fallback TaskEnvironment to all IMultiThreadableTask implementations#13415
JanProvaznik merged 7 commits intodotnet:mainfrom
JanProvaznik:dev/janprovaznik/fallback-multiprocess-task-env

Conversation

@JanProvaznik
Copy link
Copy Markdown
Member

@JanProvaznik JanProvaznik commented Mar 19, 2026

fixes #13374

Summary

All 19 built-in tasks implementing \IMultiThreadableTask\ now initialize their \TaskEnvironment\ property with a \MultiProcessTaskEnvironmentDriver-backed default. This prevents \NullReferenceException\ when tasks are:

  • Explicitly instantiated (e.g.
    ew Copy()) outside the engine
  • Run in the out-of-proc task host via \TaskHostFactory\

The engine's in-proc path overwrites this default in \TaskExecutionHost.InitializeForBatch(), so it is purely a safety net.

Changes

  • 19 task files: Changed \TaskEnvironment { get; set; }\ → \TaskEnvironment { get; set; } = new TaskEnvironment(MultiProcessTaskEnvironmentDriver.Instance);\
  • New test file: \TaskHost_MultiThreadableTask_Tests.cs\ with 2 tests validating both explicit instantiation and task host scenarios via inheritance from \MakeDir\

Testing

  • Both new tests pass (RED→GREEN TDD)
  • 7/7 \TaskRouter_IntegrationTests\ pass (no regressions)
  • 84/84 \TaskExecutionHost_Tests\ pass (no regressions)

…ementations

All 19 built-in tasks implementing IMultiThreadableTask now initialize their
TaskEnvironment property with a MultiProcessTaskEnvironmentDriver-backed default.
This prevents NullReferenceException when tasks are:
- Explicitly instantiated (e.g. new Copy()) outside the engine
- Run in the out-of-proc task host via TaskHostFactory

The engine's in-proc path overwrites this default in
TaskExecutionHost.InitializeForBatch(), so it is purely a safety net.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik marked this pull request as ready for review March 19, 2026 10:25
Copilot AI review requested due to automatic review settings March 19, 2026 10:25
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

This PR adds a defensive default TaskEnvironment initialization to all built-in tasks that implement IMultiThreadableTask, preventing NullReferenceException when such tasks are instantiated outside the engine or executed in the out-of-proc task host.

Changes:

  • Initialize TaskEnvironment to a MultiProcessTaskEnvironmentDriver-backed default across 19 built-in IMultiThreadableTask implementations.
  • Add unit tests validating TaskEnvironment is non-null/usable for explicit instantiation and TaskHostFactory execution.

Reviewed changes

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

Show a summary per file
File Description
src/Tasks/ZipDirectory.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/XslTransformation.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/XmlPoke.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/XmlPeek.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/WriteCodeFragment.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/Unzip.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/Touch.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/RemoveDir.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/Move.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/MakeDir.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/ListOperators/FindUnderPath.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/FileIO/WriteLinesToFile.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/FileIO/VerifyFileHash.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/FileIO/ReadLinesFromFile.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/FileIO/GetFileHash.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/DownloadFile.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/Delete.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/Copy.cs Initialize TaskEnvironment with a default fallback instance.
src/Tasks/AssignTargetPath.cs Initialize TaskEnvironment with a default fallback instance.
src/Build.UnitTests/BackEnd/TaskHost_MultiThreadableTask_Tests.cs Add tests validating TaskEnvironment fallback works for explicit instantiation and TaskHostFactory execution.

You can also share your feedback on Copilot code review. Take the survey.

… tasks

Update multithreaded-task-migration skill, thread-safe-tasks spec,
multithreaded-msbuild spec, taskhost-threading spec, and tasks
instructions to reflect that all 19 built-in IMultiThreadableTask
implementations now have a default TaskEnvironment backed by
MultiProcessTaskEnvironmentDriver.Instance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JanProvaznik JanProvaznik requested a review from a team as a code owner March 19, 2026 11:13
JanProvaznik and others added 2 commits March 19, 2026 14:54
Co-authored-by: AR-May <67507805+AR-May@users.noreply.github.com>
Co-authored-by: AR-May <67507805+AR-May@users.noreply.github.com>
@JanProvaznik JanProvaznik enabled auto-merge (squash) March 19, 2026 14:27
@JanProvaznik JanProvaznik merged commit 82c3923 into dotnet:main Mar 19, 2026
10 checks passed
@ViktorHofer
Copy link
Copy Markdown
Member

Doesn't that imply that every task that implements the interface should now do this? Means customer tasks would need to do this as well. Why can't we provide this by default when instantiating the task?

@JanProvaznik
Copy link
Copy Markdown
Member Author

JanProvaznik commented Mar 19, 2026

Yes it implies that.
For NET10 it could work by having a default value on the interface, however we also target net472 where that's not possible.

We can't provide it by default because the problematic case is where customers instantiate it themselves.
(and since they can inherit our task with the new interface, that new requirement would be a breaking change)

@ViktorHofer
Copy link
Copy Markdown
Member

ViktorHofer commented Mar 19, 2026

Yes it implies that.

Sad panda face :( Did we ever require such a thing in the past from task authors?

Is the fundamental problem here that the property on the already shipped interface has a setter and so customers can mutate it?

@JanProvaznik
Copy link
Copy Markdown
Member Author

JanProvaznik commented Mar 19, 2026

Did we ever require such a thing in the past from task authors?

we don't have other capability extending interfaces for Tasks so this is new. It modifies our ask from task authors "if you want your task to be multithreadable you should not only implement the interface but also set the default to multiproc"

Is the fundamental problem here that the property on the already shipped interface has a setter and so customers can mutate it?

No, the problem is that the internals of our tasks can't work without the environment. It's our recommended migration path is to replace cwd-based calls by our APIs, and those can't work without the environment.
It blows up when a 2nd order task author decides to directly instantiate an IMultithreadable task in their task that is naive about TaskEnvironments so it can't pass though it's own (and would be a breaking chage to require it). Tthis happened in VMR for Exec

@ViktorHofer
Copy link
Copy Markdown
Member

@rainersigwald & @baronfel I would love to hear your opinion on this. I'm not happy with this API shape honestly but sounds like there isn't a better option.

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.

tasks implementing IMultiThreadableTask should work in taskhost

4 participants