Perf harness: SetProcessAffinityMask called without ctypes argtypes, so Windows CPU pinning does not take effect
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 74/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- performance, tooling
Research direction
Start in eng/pipelines/perf/scripts/interleave_perf.py at apply_affinity and review the Windows SetProcessAffinityMask call and its exception handling. Run interleave_perf.py on 64-bit Windows with --cpus, then verify the spawned processes' affinity using Process Explorer or GetProcessAffinityMask; done means the requested CPUs are applied and genuine failures are reported accurately.
Written by the indexing model from the issue text.
Description
Describe the bug
apply_affinity in eng/pipelines/perf/scripts/interleave_perf.py calls SetProcessAffinityMask through ctypes.windll without declaring argtypes or restype:
https://github.com/dotnet/SqlClient/blob/main/eng/pipelines/perf/scripts/interleave_perf.py#L93-L95
handle = int(proc._handle) # noqa: SLF001 (Popen exposes the OS handle here)
if ctypes.windll.kernel32.SetProcessAffinityMask(handle, ctypes.c_size_t(mask)) == 0:
print(f"WARNING: SetProcessAffinityMask failed for pid {proc.pid}.", file=sys.stderr)
Without argtypes, ctypes marshals a plain Python int as a C int (32-bit). handle comes from proc._handle, which is a 64-bit HANDLE on 64-bit Windows, so it is truncated before the call. The result is that the process is not pinned.
Two things make this worse than a loud failure:
- The whole body is wrapped in
try/except Exceptionwith the docstring "Never raises — pinning is an optimisation, not a gate", so the run continues regardless. - The only signal is a
WARNINGline on stderr, which is easy to miss in pipeline logs. If a truncated handle happens to collide with another valid handle in the process, the call can succeed against the wrong target instead of failing.
Either way the interleaved A/B runs execute without the CPU isolation the harness is designed to provide.
Note this affects Windows only. The Linux path takes the earlier os.sched_setaffinity branch and is unaffected.
To reproduce
N/A — this is an engineering/infrastructure bug in the perf harness rather than a driver bug, so there is no C# repro.
To observe it, run interleave_perf.py on 64-bit Windows with a --cpus spec and check whether the spawned benchmark processes are actually pinned (for example via Process Explorer, or by calling GetProcessAffinityMask back and comparing). The mask will not match what was requested.
Expected behavior
The spawned benchmark processes are pinned to the requested CPUs on Windows, and a genuine failure to pin is reported accurately.
Suggested fix
Declare the signatures explicitly and pass a real HANDLE:
from ctypes import wintypes
kernel32 = ctypes.windll.kernel32
kernel32.SetProcessAffinityMask.argtypes = [wintypes.HANDLE, ctypes.c_size_t]
kernel32.SetProcessAffinityMask.restype = wintypes.BOOL
handle = wintypes.HANDLE(int(proc._handle))
if not kernel32.SetProcessAffinityMask(handle, ctypes.c_size_t(mask)):
err = ctypes.get_last_error()
...
Worth pairing with a GetProcessAffinityMask read-back so the script can report the mask that actually took effect rather than assuming the set succeeded. GetProcessAffinityMask needs its own argtypes for the same reason.
I have a working patch for this locally and can open a PR.
Further technical details
- Component: perf test harness (
eng/pipelines/perf/scripts/interleave_perf.py) - Microsoft.Data.SqlClient version: N/A (does not affect shipped product code)
- .NET target: N/A
- SQL Server version: N/A
- Operating system: 64-bit Windows only
Additional context
Found while working on the perf switch-experiment pipeline in #4543. It is pre-existing on main and unrelated to that PR's changes, so filing separately rather than folding it in.
The practical impact is on perf measurement quality: any Windows perf run that relies on pinning is not actually pinned, which adds scheduler noise and undermines the interleaved A/B comparison the harness exists to produce.
- Dominant language
- C#
- Stars
- 990
- Forks
- 340
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 62
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/SqlClient
-
External :link:
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
-
Area\Tests
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Code Health :pill: Good First Issue :sparkles:
Difficulty 1/5 Under an hour Newbie friendliness 76/100
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
All issues in dotnet/SqlClient
Similar issues
-
type/automation type/tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
ci-failure-cause test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
area:auth FE mvp P3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
klasolsson81/jobbliggaren#1788 ·