Skip to content

Use InternalsVisibleTo instead of reflection in LocalAppContextSwitchesHelper - #4759

Closed
paulmedynski wants to merge 1 commit into
dev/paul/perf-source-baselinefrom
dev/paul/local-app-switch-helper-ivt
Closed

paulmedynski wants to merge 1 commit into
dev/paul/perf-source-baselinefrom
dev/paul/local-app-switch-helper-ivt

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Replaces reflection with InternalsVisibleTo in LocalAppContextSwitchesHelper.

Stack: this is PR 2 of 2, stacked on #4763. This PR targets dev/paul/perf-source-baseline, so the diff shown here is just the IVT commit. Merge #4763 first; this PR will retarget to main automatically.

Why stacked

The dependency runs one way. This PR stops TestCommon honouring MdsPackageVersion, because a test project supports the driver at the same repo commit and an older released driver cannot grant InternalsVisibleTo to this commit's test assemblies. But PerformanceTests still set that property, so landing this alone produces:

PerformanceTests -> Microsoft.Data.SqlClient.TestCommon -> Microsoft.Data.SqlClient (>= 8.0.0-preview1-dev)
PerformanceTests -> Microsoft.Data.SqlClient (>= 7.0.2)                          error NU1605

#4763 removes the setting side, so with it in first this lands cleanly.

The change

The helper reached LocalAppContextSwitches' cached switch fields by reflection, so switch names were stringly-typed and a rename in the driver would only fail at run time.

SwitchValue and the s_* cache fields are now internal, with an IVT grant to Microsoft.Data.SqlClient.TestCommon. The helper reads and writes them directly, so a rename is now a build break. All get/set logic lives in the helper; the driver keeps only the field declarations (net -112 lines there).

Two build consequences, both verified:

  • TestCommon must bind to the implementation assembly. In Package mode NuGet hands the compiler the ref/ assembly (public API only, ~74 KB vs ~1.9 MB), where internals and the IVT attribute don't exist. ExcludeAssets="compile" plus an explicit Reference fixes that, mirroring what UnitTests already does.
  • UnitTests now references Abstractions directly. That exclusion also suppresses compile assets across the package's dependency subtree, which is where Microsoft.Data.SqlClient.Extensions.Abstractions came from. UnitTests had been free-riding on TestCommon's unrestricted package reference to resolve the SqlAuthenticationProvider type-forwards.

Validation

Scenario Result
UnitTests net8.0 — Project mode 1293 passed, 0 failed
UnitTests net8.0 — Package mode 1293 passed, 0 failed
TestCommon — Project / Package / Package + stale pin 0 errors (pin ignored)
FunctionalTests, ManualTests, PerformanceTests 0 errors

Package mode is the meaningful run: exercised against a locally packed driver, it confirms the internal field access resolves at run time through the packaged assembly (no FieldAccessException). The packed DLL was confirmed to carry InternalsVisibleTo + Microsoft.Data.SqlClient.TestCommon.

Reviewer notes

  • runtimes/win/... HintPath. Verified safe today — the win and unix payloads are byte-identical and the driver has zero OS-conditional compilation (no #if _WINDOWS/_UNIX, no .windows.cs/.unix.cs). Carries the same @TODO as UnitTests.csproj.
  • Signed IVT is dormant. TestSigningKeyPath is set by no pipeline, and the only pipeline setting SigningKeyPath runs no tests, so tests always match the unsigned grant. TestCommon already had the test-signing block, and its PublicKey=-qualified grant uses the same blob as UnitTests (same key), so it is covered symmetrically if that path is ever wired up. I could not verify the blob against the real key — it is an ADO secure file.
  • Signed + Project mode now cannot compile TestCommon, since that config intentionally grants no IVT. UnitTests already had this constraint; TestCommon joins it.

Checklist

  • Tests added or updated
  • Public API changes documented — none; internal only
  • Verified against customer repro — n/a
  • Ensure no breaking changes introduced — no public surface change

Copilot AI balanced review requested due to automatic review settings September 28, 2026 16:56

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 review overview

🟡 Changes recommended

The default tagged baseline is incompatible with interleaved execution, and tag acquisition unnecessarily depends on the fallback remote.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Replaces reflective AppContext-switch access with compile-time internal access and moves performance baselines from released packages to tagged source builds.

Changes:

  • Adds InternalsVisibleTo access for TestCommon and direct cached-switch access.
  • Adjusts package-mode references for implementation assemblies and dependencies.
  • Reworks performance baselines to build release tags from source.
File Description
LocalAppContextSwitchesTest.cs Updates reflection-related wording.
Microsoft.Data.SqlClient.UnitTests.csproj Adds the Abstractions package reference.
Microsoft.Data.SqlClient.PerformanceTests.csproj Removes package-baseline versioning.
Microsoft.Data.SqlClient.TestCommon.csproj References the packaged implementation assembly.
LocalAppContextSwitchesHelper.cs Replaces reflection with direct internal access.
LocalAppContextSwitches.cs Exposes switch cache internals.
Microsoft.Data.SqlClient.csproj Adds TestCommon IVT grants.
sqlclient-perf-pipeline.yml Resolves baseline versions to source tags.
sqlclient-perf-experiment.yml Updates baseline-selector documentation.
run-perf-tests.sh Removes NuGet-package baseline support.
run-perf-tests.ps1 Mirrors the Bash baseline changes.
eng/​pipelines/​perf/​README.md Documents source-built baselines.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eng/pipelines/perf/sqlclient-perf-pipeline.yml
- ${{ elseif ne(parameters.baselineVersion, '') }}:
- name: PerfArgsBaseline
value: '--baseline-version ${{ parameters.baselineVersion }}'
value: '--baseline-source-ref v${{ parameters.baselineVersion }} --baseline-repo-url ${{ parameters.baselineRepoUrl }}'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 17bc009.

Correct — both helpers fetched --no-tags with a branch-only refspec, so refs/heads/v7.1.0 never resolved and the origin path could not succeed for any tagged baseline, sending every run to the public fallback clone even where origin was reachable.

Both now try refs/heads/<ref> first and fall back to refs/tags/<ref>, mapping either into the same private refs/remotes/perfbaseline/<ref> namespace so the existing isolation from a same-named local branch is preserved. The public clone stays as a last resort.

paulmedynski added a commit that referenced this pull request Sep 28, 2026
Two issues found by Copilot review on #4759, both in the move to
source-built perf baselines.

Baseline acquisition built a branch-only refspec, so a 'v<version>' tag
never resolved from the checkout's origin and every tagged run silently
fell through to cloning the public fallback URL - breaking runs where
the ADO origin is reachable but GitHub egress is not. Both helpers now
try refs/heads first and then refs/tags, mapping either into the same
private perfbaseline namespace.

The default baseline of 7.0.2 also predated the perf harness protocol.
Interleaved execution drives the benchmark app one unit at a time via
PERF_LIST_BENCHMARKS/PERF_BENCHMARK, which arrived in v7.1.0-preview3;
every v7.0.x Program ignores both variables and runs the whole suite, so
each requested unit would have run everything and the baseline/candidate
pairing would have been meaningless. Raise the default to 7.1.0 and have
both scripts check the materialised baseline tree for the protocol,
failing fast instead of emitting an invalid comparison or timing out.

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

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 review overview

🟡 Changes recommended

Source-tag baselines can break interleaved runs when benchmark registries diverge, and introductory documentation remains inconsistent.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Low severity Update README to describe the source-based baseline

eng/​pipelines/​perf/​README.md:16

The updated script description now says the baseline is source-based, but this README still describes the overall comparison and the main pipeline row as using a released NuGet package. Update those introductory references so the documented architecture is consistent.

Comment on lines +205 to +208
value: '-BaselineSourceRef v${{ parameters.baselineVersion }} -BaselineRepoUrl ${{ parameters.baselineRepoUrl }}'
- ${{ elseif ne(parameters.baselineVersion, '') }}:
- name: PerfArgsBaseline
value: '--baseline-version ${{ parameters.baselineVersion }}'
value: '--baseline-source-ref v${{ parameters.baselineVersion }} --baseline-repo-url ${{ parameters.baselineRepoUrl }}'
@paulmedynski paulmedynski added this to the 8.0.0-preview1 milestone Sep 28, 2026
@paulmedynski paulmedynski added Area\Tests Issues that are targeted to tests or test projects Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. labels Sep 28, 2026
…esHelper

LocalAppContextSwitchesHelper reached the driver's cached switch fields by
reflection, which meant the switch names were stringly-typed and a rename in
LocalAppContextSwitches would only fail at run time.

Make the SwitchValue enum and the s_* cache fields internal and grant
InternalsVisibleTo to Microsoft.Data.SqlClient.TestCommon, so the helper reads
and writes them directly at compile time. All get/set logic now lives in the
helper; the driver keeps only the field declarations.

TestCommon must bind to the implementation assembly for this to work, because
NuGet hands the compiler the ref assembly (public API only) in Package mode.
ExcludeAssets="compile" plus an explicit Reference does that, mirroring what
UnitTests already does. That exclusion also suppresses compile assets across the
package's dependency subtree, so UnitTests now references
Microsoft.Data.SqlClient.Extensions.Abstractions directly - it had been relying
on TestCommon's unrestricted package reference to resolve the
SqlAuthenticationProvider type-forwards.

TestCommon also no longer honours MdsPackageVersion. Test projects support the
driver at the same repo commit, and an older released driver cannot grant
InternalsVisibleTo to this commit's test assemblies. A consumer that pins an
older driver must use the matching revision of these test projects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 28, 2026 18:37
@paulmedynski
paulmedynski force-pushed the dev/paul/local-app-switch-helper-ivt branch from 17bc009 to f8ceeb8 Compare September 28, 2026 18:37
@paulmedynski
paulmedynski changed the base branch from main to dev/paul/perf-source-baseline September 28, 2026 18:38
@paulmedynski
paulmedynski added this pull request to stack #4764 September 28, 2026 18:39

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 review overview

🟢 Approval recommended

The reviewed changes consistently implement compile-time switch access without altering the public API.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)

@paulmedynski

Copy link
Copy Markdown
Contributor Author

Superseded by #4765, which targets main directly. This PR could not be retargeted or reopened after its base branch (#4763) was abandoned and deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. Area\Tests Issues that are targeted to tests or test projects

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants