-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathTaskRouter.cs
More file actions
101 lines (93 loc) · 4.58 KB
/
Copy pathTaskRouter.cs
File metadata and controls
101 lines (93 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Concurrent;
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// Determines where a task should be executed in multi-threaded mode.
/// In multi-threaded execution mode, tasks marked with MSBuildMultiThreadableTaskAttribute run
/// in-process within thread nodes, while all other tasks are routed to sidecar TaskHost processes
/// for isolation.
/// </summary>
/// <remarks>
/// This class should only be used when in multi-threaded mode. Traditional multi-proc builds
/// have different semantics and should not use this routing logic.
/// <para>
/// The attribute is the only routing signal. Implementing <see cref="Microsoft.Build.Framework.IMultiThreadableTask"/>
/// does not opt a task into in-process execution: <c>Microsoft.Build.Utilities.ToolTask</c> implements that
/// interface, so honoring it here would silently opt in every ToolTask-derived task in the ecosystem, none of which
/// have been reviewed for thread safety. The interface instead controls whether the engine injects a
/// TaskEnvironment into the task, which TaskExecutionHost handles separately.
/// </para>
/// </remarks>
internal static class TaskRouter
{
/// <summary>
/// Cache of task types to their multi-threadable attribute status.
/// This avoids repeated reflection calls for the same task types.
/// </summary>
private static readonly ConcurrentDictionary<Type, bool> s_multiThreadableTaskCache = new();
/// <summary>
/// Determines if a task needs to be routed to an out-of-process TaskHost sidecar
/// in multi-threaded mode based on its thread-safety characteristics.
/// </summary>
/// <param name="taskType">The type of the task to evaluate.</param>
/// <returns>
/// True if the task should be executed in an out-of-process TaskHost sidecar;
/// false if it can safely run in-process within a thread node.
/// </returns>
/// <remarks>
/// This method only considers the task's thread-safety indicators.
/// The caller is responsible for:
/// - Only calling this in multi-threaded mode
/// - Handling explicit out-of-proc requests (via TaskHostFactory or parameters)
/// - Handling the isAlreadyOutOfProc scenario
///
/// In multi-threaded mode:
/// - Tasks marked with MSBuildMultiThreadableTaskAttribute (non-inheritable) are considered
/// thread-safe and can run in-process (returns false)
/// - Tasks without the attribute must run in a sidecar TaskHost for isolation (returns true)
/// </remarks>
public static bool NeedsTaskHostInMultiThreadedMode(Type taskType)
{
ArgumentNullException.ThrowIfNull(taskType);
// Tasks without the thread-safety attribute need isolation in a TaskHost sidecar
return !HasMultiThreadableTaskAttribute(taskType);
}
/// <summary>
/// Checks if a task type is marked with MSBuildMultiThreadableTaskAttribute.
/// Detection is based on namespace and name only, ignoring the defining assembly,
/// which allows customers to define the attribute in their own assemblies.
/// Results are cached to avoid repeated reflection calls.
/// </summary>
/// <param name="taskType">The task type to check.</param>
/// <returns>True if the task has the attribute; false otherwise.</returns>
private static bool HasMultiThreadableTaskAttribute(Type taskType)
{
return s_multiThreadableTaskCache.GetOrAdd(
taskType,
static t =>
{
const string attributeFullName = "Microsoft.Build.Framework.MSBuildMultiThreadableTaskAttribute";
// Check for the attribute by full name, not by type identity
// This allows custom-defined attributes from different assemblies
foreach (object attr in t.GetCustomAttributes(inherit: false))
{
if (attr.GetType().FullName == attributeFullName)
{
return true;
}
}
return false;
});
}
/// <summary>
/// Clears the thread-safety cache. Used primarily for testing.
/// </summary>
internal static void ClearCache()
{
s_multiThreadableTaskCache.Clear();
}
}
}