Skip to content

Multithreaded task routing#12617

Merged
JanProvaznik merged 14 commits intodotnet:mainfrom
JanProvaznik:mt-routing
Oct 21, 2025
Merged

Multithreaded task routing#12617
JanProvaznik merged 14 commits intodotnet:mainfrom
JanProvaznik:mt-routing

Conversation

@JanProvaznik
Copy link
Member

@JanProvaznik JanProvaznik commented Oct 8, 2025

Task Routing for Multi-Threaded MSBuild

Summary

Implements automatic task routing for multi-threaded MSBuild. Thread-safe tasks (via IMultiThreadableTask interface or [MSBuildMultiThreadableTask] attribute) run in-process; others route to isolated TaskHost processes. Traditional builds unchanged.

Routing Logic

Multi-threaded mode?
├─ No → Run in-process (existing behavior)
└─ Yes:
    ├─ Task has [MSBuildMultiThreadableTask] attribute? → In-process
    └─ No attribute → Out-of-process (TaskHost)

Simplified Semantics: The [MSBuildMultiThreadableTask] attribute is the sole determinant for multi-threaded task routing. The attribute is non-inheritable (Inherited = false), meaning each task class must explicitly declare its thread-safety capability.

Tasks may also implement IMultiThreadableTask to gain access to TaskEnvironment APIs, but the interface alone does not affect routing - only the attribute matters.

Explicit TaskHostFactory specification overrides all routing decisions.

Thread-Safety Declaration

Attribute-Based (Required)

[MSBuildMultiThreadableTask]
public class ThreadSafeTask : Task
{
    public override bool Execute()
    {
        // Handle thread-safety manually without API assistance
        string path = Path.Combine(ProjectDirectory, "file.txt");
        return true;
    }
}
  • ✅ Task runs in-process in MSBuild 10.0+ multi-threaded mode
  • ✅ Also works in older MSBuild versions (attribute ignored)
  • ❌ No TaskEnvironment API assistance
  • ⚠️ Must be thread-safe without framework assistance
  • ⚠️ Attribute is non-inheritable - derived classes must declare it explicitly

Interface-Based (Optional Enhancement)

[MSBuildMultiThreadableTask]
public class ModernTask : Task, IMultiThreadableTask  
{
    public TaskEnvironment TaskEnvironment { get; set; }

    public override bool Execute()
    {
        string path = TaskEnvironment.GetAbsolutePath("file.txt");
        return true;
    }
}

Requires MSBuild 10.0+. The attribute controls routing; the interface provides TaskEnvironment API access.

Nonenlightened Tasks (No Changes Required)

public class NonEnlightenedTask : Task
{
    public override bool Execute()
    {
        Directory.SetCurrentDirectory("somewhere");
        return true;
    }
}

Routed to TaskHost in multi-threaded mode. No changes required.

Implementation

Core Components

TaskRouter.cs - Simplified routing logic with caching

  • NeedsTaskHostInMultiThreadedMode() - Returns true if task lacks MSBuildMultiThreadableTaskAttribute
  • HasMultiThreadableTaskAttribute() - Checks for attribute using name-based matching (cached per type)
  • Name-based attribute matching for cross-assembly compatibility
  • Uses inherit: false since attribute is non-inheritable

AssemblyTaskFactory.cs - Integration point

  • Invokes routing decision after architecture/runtime checks
  • Creates TaskHostTask (OOP) or direct instance (in-process)
  • XML documentation explains three-level routing hierarchy

Attribute Detection

private static bool HasMultiThreadableTaskAttribute(Type taskType)
{
    return s_multiThreadableTaskCache.GetOrAdd(
        taskType,
        static t =>
        {
            const string attributeFullName = 
                "Microsoft.Build.Framework.MSBuildMultiThreadableTaskAttribute";
            
            // Use inherit: false since attribute is non-inheritable
            foreach (object attr in t.GetCustomAttributes(inherit: false))
            {
                if (attr.GetType().FullName == attributeFullName)
                    return true;
            }
            return false;
        });
}

Name-based matching allows task assemblies to define their own attribute copy without depending on MSBuild internals. Results are cached for performance.

Files Changed

New

  • TaskRouter.cs - Simplified routing logic with caching

Modified

  • MSBuildMultiThreadableTaskAttribute.cs - Made non-inheritable (Inherited = false)
  • AssemblyTaskFactory.cs - Integrated routing decision, added XML documentation
  • TaskHostTask.cs - Fixed custom event parameter ordering
  • TaskExecutionHost.cs - Pass scheduledNodeId through
  • TaskRouter_IntegrationTests.cs - Updated integration tests for simplified routing logic

Test Utilities

  • TaskRouterTestHelper - Semantic log assertions to reduce test fragility

Testing

Integration Tests (7 scenarios)

  • Attribute-based tasks run in-process
  • Interface-only tasks (without attribute) route to TaskHost
  • Nonenlightened tasks route to TaskHost
  • Mixed task builds
  • Explicit TaskHostFactory overrides routing
  • Single-threaded mode unchanged
  • Multi-threaded mode routing active

Compatibility

No breaking changes. Nonenlightened tasks automatically isolated in TaskHost. Single-threaded mode unchanged.

AR-May added a commit that referenced this pull request Oct 14, 2025
Related to #11828

Changes:
This PR introduces the core APIs that enable thread-safe task execution
in MSBuild's multithreaded execution model. These APIs provide safe
alternatives to global process state operations, allowing
`IMultithreadable` tasks to run concurrently within a single MSBuild
process.

Node:
Adding multithreaded APIs in order to unblock #12617
Extracted APIs from #12608

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Chet Husk <chusk3@gmail.com>
@JanProvaznik JanProvaznik marked this pull request as ready for review October 15, 2025 14:06
Copilot AI review requested due to automatic review settings October 15, 2025 14:06
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 implements automatic task routing for multi-threaded MSBuild builds. Tasks that declare thread-safety (via IMultiThreadableTask interface or [MSBuildMultiThreadableTask] attribute) run in-process, while legacy tasks without these indicators are isolated in TaskHost processes for safety. Single-threaded builds remain unchanged.

Key Changes

  • Routing logic centralizes task safety evaluation with reflection-based caching
  • Integration point in AssemblyTaskFactory applies routing only in multi-threaded mode
  • Comprehensive test coverage validates routing decisions across all scenarios

Reviewed Changes

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

File Description
Microsoft.Build.csproj Adds TaskRoutingDecision.cs to project compilation
AssemblyTaskFactory.cs Integrates routing decision after architecture checks in CreateTaskInstance
TaskRoutingDecision.cs Implements routing logic with interface/attribute detection and type-based caching
TaskRouting_IntegrationTests.cs Adds 7 integration tests covering all routing scenarios and a custom attribute definition for testing

@JanProvaznik JanProvaznik changed the title [WIP] Multithreaded task routing Multithreaded task routing Oct 15, 2025
@JanProvaznik JanProvaznik requested a review from AR-May October 16, 2025 13:37
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.

Looks good to me! I would like though either @rainersigwald or @baronfel approve of our task routing strategy here.

@JanProvaznik JanProvaznik requested a review from AR-May October 20, 2025 15:33
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.

Looks good to me!

@JanProvaznik JanProvaznik merged commit 243fb76 into dotnet:main Oct 21, 2025
10 checks passed
@yuehuang010
Copy link
Contributor

I have feedback to consider in the future.

  1. The analyzer should also detect usage of Path.GetFullPath() as that will query for CWD every call.
  2. IMO, MSBuild should finally add SetEnvironment task that doesn't actually set Env, but replay the values during Exec task.
  3. Did you mention Task batching? Would those be parallelized or it is out of scope.
  4. What is the difference between "MultiThreadable" vs "Threadsafe"?

@JanProvaznik
Copy link
Member Author

I have feedback to consider in the future.

  1. The analyzer should also detect usage of Path.GetFullPath() as that will query for CWD every call.
  2. IMO, MSBuild should finally add SetEnvironment task that doesn't actually set Env, but replay the values during Exec task.
  3. Did you mention Task batching? Would those be parallelized or it is out of scope.
  4. What is the difference between "MultiThreadable" vs "Threadsafe"?
  1. agree
  2. @baronfel ?
  3. out of scope for now, though there were some discussions about it
  4. the naming was evolving, we settled on MultiThreadable, we should clean up "Threadsafe" where we use it inappropriately

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.

5 participants