runtime: Windows Server 2022 - Setting affinity in .NET 6 application errors

Hi,

I have a Windows Server 2022 machine with 40 cores and 80 logical processors. Whenever I try and run my application on this machine and attempt to set it’s affinity in code or via Task Manager, I get errors.

The error I get in code is: image Win32Exception (87): The parameter is incorrect

When I try and set it via Task Manager (as an admin with UAC disabled) I get: image Unable to access or set process affinity

Also, when I try and get the affinity mask for the process in code, I get 0 back. After reading the docs: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setprocessaffinitymask https://docs.microsoft.com/en-us/windows/win32/procthread/processor-groups It is clear that on Windows 11 and Server 2022 that threads are not bound to a single processor group any more if there are more than 64 processors and can run across multiple processor groups.

My theory is that I am unable to set the affinity for my application because it is performing actions on threads across multiple processor groups. I have tried setting the affinity mask at startup in my application using:

Process proc = Process.GetCurrentProcess();
long affinityMask = (long)proc.ProcessorAffinity;
affinityMask &= 15; // First 4 processors
proc.ProcessorAffinity = (IntPtr)affinityMask;

But I just get the Win32Exception (87): The parameter is incorrect And when I try to get the affinity mask at start up it is set to 0 meaning it was unable to get it because there are threads in multiple groups.

So for me, is it possible to set a .NET 6 application to ONLY run in a single processor group on Windows Server 2022 before the application runs, so I can then set its affinity mask?

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (16 by maintainers)

Most upvoted comments

That is correct, once the process becomes multigroup-aware, the SetProcessAffinityMask Win32 function (that is what Process.ProcessorAffinity’s setter uses underneath) would always fail with ERROR_INVALID_PARAMETER (error 87). Even if you successfully set an affinity mask, some components may break away by replacing that mask or calling SetThreadGroupAffinity. In case you need to enforce a limit on CPU usage, a Win32 job object or a Docker container should be used.

The initialization of the Quic protocol handler is calling SetThreadGroupAffinity here:

KERNELBASE!SetThreadGroupAffinity [minkernel\kernelbase\thread.c @ 3716] 
msquic!CxPlatThreadCreate+0xa2 [D:\a\_work\1\s\src\msquic\src\inc\quic_platform_winuser.h @ 889] 
msquic!CxPlatWorkersInit+0xc7 [D:\a\_work\1\s\src\msquic\src\platform\platform_worker.c @ 134] 
msquic!CxPlatInitialize+0x9c [D:\a\_work\1\s\src\msquic\src\platform\platform_winuser.c @ 358] 
msquic!MsQuicLibraryInitialize+0x4b [D:\a\_work\1\s\src\msquic\src\core\library.c @ 251] 
msquic!MsQuicAddRef+0x3a [D:\a\_work\1\s\src\msquic\src\core\library.c @ 587] 
msquic!MsQuicOpenVersion+0x54 [D:\a\_work\1\s\src\msquic\src\core\library.c @ 1546] 
System_Net_Quic!ILStubClass.IL_STUB_PInvoke(UInt32, Microsoft.Quic.QUIC_API_TABLE**)+0x6b
System_Net_Quic!System.Net.Quic.Implementations.MsQuic.Internal.MsQuicApi..cctor+0x404 [/_/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @ 89] 
coreclr!CallDescrWorkerInternal+0x83
coreclr!DispatchCallDebuggerWrapper+0x1c [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 156] 
coreclr!DispatchCallSimple+0x60 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 221] 
coreclr!MethodTable::RunClassInitEx+0x164 [D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp @ 3475] 
coreclr!MethodTable::DoRunClassInitThrowing+0x6df [D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp @ 3658] 
coreclr!MethodTable::CheckRunClassInitThrowing+0x1b9 [D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp @ 3796] 
coreclr!DynamicHelperFixup+0x7dd [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 3020] 
coreclr!DynamicHelperWorker+0x168 [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 3329] 
coreclr!DelayLoad_Helper+0x7a
System_Net_Quic!System.Net.Quic.QuicListener.get_IsSupported+0xa [/_/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs @ 15] 
Microsoft_AspNetCore_Server_Kestrel_Transport_Quic!Microsoft.AspNetCore.Hosting.WebHostBuilderQuicExtensions.UseQuic+0x11 [/_/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderQuicExtensions.cs @ 23] 

So it appears that something in ASPNET is not multi-group aware

Nit: It is the exact opposite. This code is multi-group aware and that breaks affinity setting APIs. In other words, the existing Process affinity settings API do not work on multi-group aware processes.

That OS behavior change has been implemented in backward compatible manner. A process must explicitly declare itself multigroup-aware (by calling a relevant API) to break the existing APIs like SetProcessAffinityMask. Otherwise, the legacy affinity APIs should continue to work even if the process is currently running on multiple processor groups (but not explicitly multigroup-aware).

So for me, is it possible to set a .NET 6 application to ONLY run in a single processor group

One can use start /affinity ... for that purpose.