Skip to content

Enabled windows ProcessorAffinity support#629

Open
RakeshwarK wants to merge 16 commits intomicrosoft:mainfrom
RakeshwarK:cpuAffinity
Open

Enabled windows ProcessorAffinity support#629
RakeshwarK wants to merge 16 commits intomicrosoft:mainfrom
RakeshwarK:cpuAffinity

Conversation

@RakeshwarK
Copy link
Contributor

@RakeshwarK RakeshwarK commented Jan 29, 2026

This PR enables CPU affinity support for Virtual Client process on windows, by setting affinity via Process.ProcessorAffinity API.

Why Different Approaches for Windows vs. Linux?
-On Linux, we use numactl to wrap the process execution command.
-Windows doesn't have a built-in numactl equivalent, the Process.ProcessorAffinity property is the standard .NET approach which leverages the existing process management infrastructure.
process.Start();
process.ProcessorAffinity = affinityMask;

Limitations for Windows:

  1. Affinity is applied immediately after Start(), creating a small timing window (~<1ms) where the process may run on unintended cores. The impact can be negligible for most workloads.
  • Alternative approach: Windows Job Objects could theoretically eliminate this by creating the process in CREATE_SUSPENDED state and assigning it to a Job Object with affinity constraints and resuming the process. But we must bypass .NET's Process class and manually manage the process lifecycle.
  1. Process.ProcessorAffinity is an affinity mask interface that only targets a single processor group (the process’s primary group), and a processor group has a maximum of 64 logical processors.
  • Alternative approach: To pin a workload on a >64 core machine, we must control threads, not just the process, and we must control which group(s) the workload uses and explicitly place threads across groups. This requires group‑aware APIs built on the GROUP_AFFINITY structure.
    Set thread group + CPU mask > Create thread in a group > Query group topology > Multi‑group thread affinity

@RakeshwarK RakeshwarK changed the title Enable defining specific logical processors against which to run a given process fo Windows Enabled windows ProcessorAffinity support Jan 29, 2026
@RakeshwarK RakeshwarK marked this pull request as ready for review January 29, 2026 20:35
@nchapagain001 nchapagain001 self-assigned this Feb 13, 2026
Copy link
Contributor

@nchapagain001 nchapagain001 left a comment

Choose a reason for hiding this comment

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

Let's make changes directly in CreateProcessWithAffinity() to allow windows process affinity. Users shouldn't have to make additional call for the same thing.

Copy link

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 enables CPU affinity support for Virtual Client processes on Windows by using the Process.ProcessorAffinity API. The implementation follows a different approach than Linux: Windows applies affinity immediately after starting the process (creating a small ~1ms timing window), while Linux uses numactl to wrap the process execution command (zero timing window). The Windows implementation is limited to 64 cores due to processor group constraints.

Changes:

  • Added WindowsProcessAffinityConfiguration class to handle Windows-specific CPU affinity using bitmask-based ProcessorAffinity API
  • Created ApplyAffinity extension method for IProcessProxy to apply Windows affinity after process start
  • Added comprehensive unit and functional tests for Windows affinity support
  • Provided example executor (ExampleWorkloadWithAffinityExecutor) and profile demonstrating CPU affinity usage
  • Updated documentation to explain CPU affinity support and platform differences

Reviewed changes

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

Show a summary per file
File Description
src/VirtualClient/VirtualClient.Common/ProcessAffinity/WindowsProcessAffinityConfiguration.cs Core implementation of Windows CPU affinity using ProcessorAffinity bitmask, includes reflection-based approach to access underlying process
src/VirtualClient/VirtualClient.Common/ProcessAffinity/ProcessAffinityConfiguration.cs Updated factory method to support Windows platform in addition to Linux
src/VirtualClient/VirtualClient.Core/ProcessExtensions.cs Added ApplyAffinity extension method for applying Windows affinity to running processes
src/VirtualClient/VirtualClient.TestFramework/InMemoryProcess.cs Added OnApplyAffinity delegate to support testing affinity application
src/VirtualClient/VirtualClient.Common.UnitTests/ProcessAffinity/WindowsProcessAffinityConfigurationTests.cs Unit tests for Windows affinity configuration including bitmask calculation and validation
src/VirtualClient/VirtualClient.Core.UnitTests/ProcessExtensionsAffinityTests.cs Unit tests for process extension methods, verifying Windows throws on pre-start affinity methods
src/VirtualClient/VirtualClient.Actions/Examples/ExampleWorkloadWithAffinityExecutor.cs Reference implementation showing how to use CPU affinity in workload executors (contains critical bug in Linux command construction)
src/VirtualClient/VirtualClient.Actions.FunctionalTests/ExampleWorkloadWithAffinityProfileTests.cs Functional tests for the example affinity workload on both Linux and Windows platforms
src/VirtualClient/VirtualClient.Main/profiles/PERF-CPU-EXAMPLE-AFFINITY.json Example profile demonstrating CPU affinity configuration
website/docs/developing/0030-workload-onboarding.md Documentation for CPU affinity feature including core specification formats and platform differences
website/docs/developing/0010-develop-guide.md Updated developer guide to reference CPU affinity support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +246 to +247
this.WorkloadExecutablePath,
commandArguments);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The usage of GetCommandWithAffinity is incorrect for this scenario. The method is designed to wrap bash commands (e.g., GetCommandWithAffinity("bash -c", "command args") produces bash -c "numactl -C 0-3 command args"). However, here it's being called with the executable path directly, which would produce an invalid command structure like /path/to/exe "numactl -C 0-3 args". The correct approach would be to pass empty string as the first parameter and combine the executable and arguments in the second parameter, or use the pattern shown in the tests: GetCommandWithAffinity("", $"{this.WorkloadExecutablePath} {commandArguments}").

Suggested change
this.WorkloadExecutablePath,
commandArguments);
string.Empty,
$"{this.WorkloadExecutablePath} {commandArguments}");

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +87
// Use reflection to access the protected UnderlyingProcess property
System.Reflection.PropertyInfo propertyInfo = typeof(ProcessProxy).GetProperty(
"UnderlyingProcess",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

if (propertyInfo == null)
{
throw new NotSupportedException("Unable to access the underlying process for affinity configuration.");
}

System.Diagnostics.Process underlyingProcess = propertyInfo.GetValue(processProxy) as System.Diagnostics.Process;
if (underlyingProcess != null)
{
underlyingProcess.ProcessorAffinity = this.AffinityMask;
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The use of reflection to access the protected UnderlyingProcess property creates a fragile coupling that could break silently if the ProcessProxy class structure changes. Consider adding a public method to ProcessProxy (e.g., SetProcessorAffinity or ApplyAffinityMask) that internally accesses UnderlyingProcess and sets ProcessorAffinity. This would create an explicit API contract, improve maintainability, and make the intention clearer. The current approach bypasses encapsulation boundaries which may have been intentional.

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +356
**CPU Affinity Support**
Virtual Client supports binding workload processes to specific CPU cores to enable core isolation testing. For detailed guidance on implementing
CPU affinity in workload executors, see the [Workload Onboarding Process](./0030-workload-onboarding.md#cpu-core-affinity-optional) documentation.
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The indentation and formatting of the "CPU Affinity Support" subsection is inconsistent with the rest of the document. Other sections use bullet points (asterisks) for subsections, but this one doesn't. Consider reformatting to match the existing pattern, for example by indenting it further or adding it as a bullet point under "Process Management" to maintain consistency with the document structure.

Copilot uses AI. Check for mistakes.
};

// Simulate process completion when WaitForExitAsync is called
Task originalWait = process.WaitForExitAsync(CancellationToken.None);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The variable 'originalWait' is assigned but never used. This line creates a Task that is never awaited or stored meaningfully. Either remove this line if it's not needed, or clarify its purpose if it's meant to serve some test setup function.

Suggested change
Task originalWait = process.WaitForExitAsync(CancellationToken.None);

Copilot uses AI. Check for mistakes.
@RakeshwarK
Copy link
Contributor Author

Let's make changes directly in CreateProcessWithAffinity() to allow windows process affinity. Users shouldn't have to make additional call for the same thing.

We cannot do that because for windows we can only apply the affinity once the process is started, In the executor there is explicit calls for StartAndWait after process creation so we need to call Start then apply affinity and call WaitAsync.

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.

2 participants

Comments