Fix broken private reflection in ArrayPool stress test - #132675
Conversation
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Fixes a stress-gated System.Buffers ArrayPool test that was using broken private reflection by resolving GetMemoryPressure on the correct internal type (System.Buffers.Utilities) and adding assertions to fail fast if reflection breaks again.
Changes:
- Switch reflection lookup from
ArrayPool<byte>.Shared.GetType()totypeof(ArrayPool<byte>).Assembly.GetType("System.Buffers.Utilities"). - Add assertions that the internal type and method resolve, avoiding a later
NullReferenceException. - Remove the now-unnecessary
#pragma warning disable IL2075around the broken reflection.
|
This PR got auto labelled Going by area-owners.md, Could someone relabel it so it routes to the right area owners? @jeffhandley, I apologize for the direct ping, I am flagging it since the current label points this at the CoreCLR VM team instead. |
|
@dotnet-policy-service agree |
|
Tagging subscribers to this area: @dotnet/area-system-buffers |
|
Applied both inline suggestions by @teo-tsirpanis on
What I pushed instead keeps the I went for a single field because splitting the @jkotas this deviates from what you approved, and adds a new unconditional test, but I think it is the better trade-off. The alternatives all detect the drift indirectly, and this one fails on the thing it is actually checking. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs:110
- s_pressureMethod is nullable and is dereferenced inside the RemoteExecutor delegate. If someone runs only ThreadLocalIsCollectedUnderHighPressure (or if resolution differs in the remote process), this can throw NullReferenceException before producing a useful assertion failure. Capture a non-null local MethodInfo via Assert.NotNull and use it for ReturnType/Invoke to make the failure mode explicit and avoid nullable deref.
object highPressure = Enum.Parse(s_pressureMethod.ReturnType, "High");
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs:88
- PressureMethod is nullable, but ThreadLocalIsCollectedUnderHighPressure dereferences it without any local assertion. The new non-stress Fact helps in full-suite runs, but test execution order (and filtered runs) is not guaranteed, so this can still regress back to a NullReferenceException without a clear diagnostic. Consider making the helper resolution self-validating (assert type + method) and returning a non-null MethodInfo, then have the Fact simply touch it.
private MethodInfo? PressureMethod =>
Type.GetType("System.Buffers.Utilities, System.Private.CoreLib")
?.GetMethod("GetMemoryPressure", BindingFlags.Static | BindingFlags.NonPublic, Type.EmptyTypes);
// ThreadLocalIsCollectedUnderHighPressure only runs under DOTNET_TEST_STRESS=1, so without
// this, the private API it reflects on could change without anyone noticing.
[Fact]
public void MemoryPressureHelperIsAvailable()
{
Assert.NotNull(PressureMethod);
}
Fixes #128431
ThreadLocalIsCollectedUnderHighPressurelooks up the internalGetMemoryPressurehelper by reflection so it can spin until the pool reports high memory pressure. The lookup targets the wrong type:ArrayPool<byte>.Sharedis aSharedArrayPool<byte>, butGetMemoryPressureis a static on the separate internalSystem.Buffers.Utilitiesclass.GetMethodtherefore returnsnull, and the test throwsNullReferenceExceptionon the firstInvoke, before it applies any memory pressure at all.Why this went unnoticed
The test is a
ConditionalFactgated onIsStressModeEnabledAndRemoteExecutorSupported, which requiresDOTNET_TEST_STRESS=1. Normal CI never sets that, so the test is always skipped and the failure has been invisible since .NET 6.The fix
Look the method up on
System.Buffers.Utilities, and assert that both the type and the method resolve. The assertions are as much the point of the change as the corrected lookup, because a silentnullis exactly what let this rot undetected for five years, so a future move of the helper now fails with a clear message instead of a bareNullReferenceException.The
#pragma warning disable IL2075is no longer needed. I verified that a clean/t:RebuildwithEnableTrimAnalyzer=trueandEnableAotAnalyzer=trueproduces no new trim warnings.I left the
(int)cast on the boxed enum as is. Unboxing an enum to its underlying type is valid, so it was never part of the bug. It would arguably read better to resolve the nestedUtilities.MemoryPressuretype and compare againstEnum.Parse(pressureType, "High")instead of the literal2. That is a couple of extra lines and I am happy to follow up if you would prefer it. In the meantime, I left a small comment explaining what that magic2is.Verification
Run locally on linux-x64 with
DOTNET_TEST_STRESS=1.Before:
After:
The loop only exits once
GetMemoryPressure()returnsHigh, so the test completing confirms that the reflection now resolves and that the pool genuinely drops the buffer under pressure. Reproduced twice for good measure, at 6.66s and 6.61s.Without
DOTNET_TEST_STRESSthe suite is 91 passed / 1 skipped both before and after, so nothing else is affected.Note for reviewers
The test remains stress gated, so CI will continue to skip it and the local run above is the only evidence that it passes.
DOTNET_TEST_STRESSis not set anywhere in the repo's pipelines, so this is true of every stress gated test. Reproducing locally is a single command if you would like to confirm it independently: