Skip to content

Make Runtime=NET task host handshake architecture-agnostic#13890

Open
ViktorHofer wants to merge 4 commits into
dotnet:mainfrom
ViktorHofer:vihofer/fix-arm64-net-task-host-handshake
Open

Make Runtime=NET task host handshake architecture-agnostic#13890
ViktorHofer wants to merge 4 commits into
dotnet:mainfrom
ViktorHofer:vihofer/fix-arm64-net-task-host-handshake

Conversation

@ViktorHofer

@ViktorHofer ViktorHofer commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Runtime="NET" task host launches fail with MSB4216 whenever the worker node and TaskHost node architectures don't line up the way the original implementation expected. Tracked as #13879 (Bug A).

Terminology: the worker node is the MSBuild process that launches the task host (the "parent"); the TaskHost node is the launched Runtime="NET" task host process (the "child"). This wording was adopted following review feedback.

Two coordinated handshake changes — one on each side — close every reasonable worker/TaskHost arch combination:

1. Worker node side — CommunicationsUtilities.GetHandshakeOptions

For a NET task host the launched TaskHost node's architecture is whatever the .NET SDK shipped, not the worker node's. Emitting the worker node's arch bit produces a wire-level mismatch with already-shipped SDK TaskHost nodes whose arch differs.

Suppress the X64/Arm64 bit when invoked by the worker node for a NET task host (detected by Runtime="net" in the explicit TaskHostParameters). The TaskHost-node path (TaskHostParameters.Empty) is unaffected, so already-deployed worker nodes that still emit an arch bit continue to match.

2. TaskHost node side — NodeEndpointOutOfProcBase.IsAllowedBitnessMismatch

The .NET task host side relaxation that lets a worker node without an arch bit on the wire connect to an SDK TaskHost node. Previously only tolerated expected = X64, so any arm64 SDK TaskHost node rejected the connection. Now also tolerates expected = Arm64. True cross-arch mismatches (worker X64 ↔ TaskHost Arm64) remain rejected.

Method is promoted to internal static so the test project can exercise the tolerance matrix directly. It was a stateless pure predicate already.

Effect across worker node / SDK combinations

current = behavior with this PR applied (worker-node change in VS MSBuild, TaskHost-node change in a future SDK). For each combo, "current behavior" notes whether the SDK side needs to update.

Worker node TaskHost node (SDK MSBuild.dll) Wire arch bit Old behavior Current behavior
x86 .NET Fx VS x64 SDK none → x64 ✅ (existing expectedIsX64 tolerance)
x86 .NET Fx VS arm64 SDK none → arm64 ❌ MSB4216 (Bug A) — the user's binlog ✅ once SDK ships with TaskHost-side expectedIsArm64 tolerance
amd64 .NET Fx VS x64 SDK x64 → x64 ✅ (matches exactly) ✅ (after worker fix: no bit on wire; already-shipped SDK tolerates none → x64)
amd64 .NET Fx VS arm64 SDK x64 → arm64 ❌ MSB4216 ✅ once VS picks up worker-side suppression; SDK must also have TaskHost-side Arm64 tolerance
arm64 .NET Fx VS x64 SDK arm64 → x64 ❌ MSB4216 ✅ once VS picks up worker-side suppression (works against already-shipped SDKs)
arm64 .NET Fx VS arm64 SDK arm64 → arm64 ✅ (matches exactly)
old worker node (pre-this-PR) new SDK TaskHost node x64/arm64 → matching unchanged unchanged — still works if arches match, still hits Bug A if they don't (worker has no fix)

Where each fix takes effect

Failing combo Fixed by Ships in
x86 .NET Fx VS → win-arm64 SDK TaskHost-side Arm64 tolerance a new .NET SDK build that picks up this PR
amd64 .NET Fx VS → win-arm64 SDK worker-side arch suppression + TaskHost-side Arm64 tolerance next VS insertion of MSBuild (worker side) plus a new SDK build (TaskHost side)
arm64 .NET Fx VS → win-x64 SDK worker-side arch suppression (existing expectedIsX64 tolerance already covers the TaskHost node) next VS insertion of MSBuild only — no SDK update needed

The reported binlog (Roslyn build on Windows-ARM, SDK 10.0.108, x86 VS 18 MSBuild worker node) is the first row — needs the SDK side to pick this up.

Tests

src/Build.UnitTests/BackEnd/NodeEndpointOutOfProcBase_Tests.cs (6 cases):

  • NoArchBitWorkerNode_X64TaskHost_IsTolerated
  • NoArchBitWorkerNode_Arm64TaskHost_IsTolerated
  • X64WorkerNode_X64TaskHost_NotConsideredMismatch
  • X64WorkerNode_Arm64TaskHost_NotTolerated
  • Arm64WorkerNode_X64TaskHost_NotTolerated
  • NoArchBitWorkerNode_NoArchBitTaskHost_NotTolerated (guards against a future simplification to return receivedIsX86;)

src/Build.UnitTests/BackEnd/CommunicationsUtilities_Tests.cs (5 cases):

  • GetHandshakeOptions_NetTaskHostWorkerNode_SuppressesArchBit × { x64, arm64, x86 }
  • GetHandshakeOptions_NonNetTaskHostWorkerNode_KeepsX64ArchBit
  • GetHandshakeOptions_NonNetTaskHostWorkerNode_KeepsArm64ArchBit
  • GetHandshakeOptions_NetTaskHostNode_KeepsArchBit

All 11 pass on net10.0.

References

IsAllowedBitnessMismatch is the .NET task host child-side relaxation that
lets a parent without an architecture bit on the wire (typically a .NET
Framework MSBuild) connect to an SDK child that runs on x64. On
Windows-ARM the SDK child runs as arm64 instead, but the function only
considered expectedIsX64, so any arm64 child rejected the connection ->
the launch times out and Runtime="NET" tasks fail with MSB4216.

Also tolerate expectedIsArm64. True cross-arch mismatches (parent X64 vs
child Arm64, or vice versa) remain rejected.

Make the method internal static so the test project can exercise the
tolerance matrix directly. The previous instance method did not touch any
instance state.

Partial fix for dotnet#13879 (Bug A).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 28, 2026 09:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a child-side handshake check that prevented .NET Framework MSBuild parents from launching a Runtime="NET" task host on Windows-ARM. IsAllowedBitnessMismatch previously only tolerated an x64-expecting child when the parent sent no architecture bit; it now tolerates arm64-expecting children as well, while still rejecting true cross-arch mismatches (X64↔Arm64). The method is also promoted to internal static to make it directly unit-testable.

Changes:

  • Extend IsAllowedBitnessMismatch to accept expectedIsArm64 in addition to expectedIsX64.
  • Convert the method to internal static and refresh its doc comment.
  • Add unit tests covering the no-arch-bit-parent tolerance matrix (x64/arm64 child) and rejection of true cross-arch mismatches.
Show a summary per file
File Description
src/Shared/NodeEndpointOutOfProcBase.cs Tolerate arm64-expecting child in bitness mismatch check; expose as internal static; update XML doc.
src/Build.UnitTests/BackEnd/NodeEndpointOutOfProcBase_Tests.cs New #if NET tests for the tolerance matrix and cross-arch rejection.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

@ViktorHofer ViktorHofer marked this pull request as draft May 28, 2026 10:04

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Summary — PR #13890

Well-scoped bug fix extending the .NET task host handshake tolerance to arm64 children. The logic change is correct and the approach is sound.

Findings

# Dimension Severity Summary
1 Test Coverage & Completeness MODERATE Missing test for "no-arch parent → no-arch child" (the path where receivedIsX86==true but method returns false)
2 Documentation Accuracy NIT Inherited mask comment has version/flags description inverted

Clean Dimensions (22/24)

Backwards Compatibility ✓ · ChangeWave ✓ · Performance ✓ · Error Messages ✓ · Logging ✓ · String Comparison ✓ · API Surface ✓ · Target Authoring ✓ · Design ✓ · Cross-Platform ✓ · Code Simplification ✓ · Concurrency ✓ · Naming ✓ · SDK Integration ✓ · Idiomatic C# ✓ · File I/O ✓ · Build Infrastructure ✓ · Scope & PR Discipline ✓ · Evaluation Model ✓ · Correctness ✓ · Dependency Mgmt ✓ · Security ✓

Notes

  • The private → internal static visibility change is appropriate for testability; the class is already internal and the method is a pure stateless predicate.
  • The behavioral change is strictly a relaxation (accepts arm64 where previously only x64 was accepted), so no ChangeWave is needed.
  • The fix correctly rejects true cross-arch mismatches (e.g., parent declares X64 but child expects Arm64).

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #13890 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13890 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Expert Code Review (on open) for issue #13890 · ● 5.5M

Comment thread src/Build.UnitTests/BackEnd/NodeEndpointOutOfProcBase_Tests.cs
Comment thread src/Shared/NodeEndpointOutOfProcBase.cs Outdated
For NET task host launches the child process's architecture is determined
by what the .NET SDK shipped (x64 today, arm64 in the future), not by the
parent's process architecture. Emitting the parent's arch bit creates a
wire-level mismatch with already-shipped SDK children whose arch differs
(e.g. arm64 VS launching an x64 SDK MSBuild.dll, or amd64 VS launching an
arm64 SDK MSBuild.dll). The existing child-side IsAllowedBitnessMismatch
tolerance only accepts "parent sent no arch bit", so this currently fails
with MSB4216.

In CommunicationsUtilities.GetHandshakeOptions, suppress the X64/Arm64 bit
when invoked by the parent for a NET task host (detected by Runtime="net"
in the explicit TaskHostParameters). The child path (TaskHostParameters
.Empty) is unaffected so already-deployed parents that still emit an arch
bit continue to match.

Combined with the child-side Arm64 tolerance in this PR, every parent/
child architecture combination now connects against every already-shipped
SDK that has the original (x64-only) tolerance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ViktorHofer ViktorHofer changed the title Tolerate Arm64 child in NET task host handshake bitness check Make Runtime=NET task host handshake architecture-agnostic May 28, 2026
@ViktorHofer ViktorHofer requested a review from Copilot May 28, 2026 11:45
- Add NoArchBitParent_NoArchBitChild_NotTolerated test (catches a regression that would simplify the return to just receivedIsX86).
- Fix inverted comment on the 0x00FFFFFF mask (lower 24 bits are flags, upper byte is version).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@ViktorHofer ViktorHofer marked this pull request as ready for review May 28, 2026 13:13
Comment thread src/Framework/BackEnd/CommunicationsUtilities.cs Outdated
Comment thread src/Framework/BackEnd/CommunicationsUtilities.cs Outdated
@ViktorHofer

Copy link
Copy Markdown
Member Author

@rainersigwald it would be great if you could also take a look :) Just want to be careful.

…handshake comments

- Remove the speculative 'arm64 in the future' note and condense the verbose
  NET task host arch-bit suppression comment to the underlying rule.
- Rename parent/child to worker node/TaskHost node in comments, the
  isNetTaskHostWorkerNode variable, and the handshake unit tests.
- Fix the stale inverted handshake mask comment at the IsAllowedBitnessMismatch
  call site.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ViktorHofer

Copy link
Copy Markdown
Member Author

@rainersigwald please approve if the changes look good to you

Comment on lines +594 to +595
/// True cross-arch mismatches (e.g. worker node sent X64 but TaskHost node expects
/// Arm64, or vice versa) remain rejected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But we're missing "worker node wanted x86" right, because it's indistinguishable from "didn't care"?

@JanProvaznik

Copy link
Copy Markdown
Member

@ViktorHofer mergeable? or do you plan to take action on last rainer's comment?

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.

MSB4216 on Windows ARM: NET task host handshake never tolerates expected=Arm64; #13741 implementation diverges from description

4 participants