Skip to content

AE tests: create ConversionTests keys once per class to slow error 3807 identifier exhaustion - #4478

Open
paulmedynski wants to merge 8 commits into
mainfrom
dev/paul/kerberos-identifiers
Open

AE tests: create ConversionTests keys once per class to slow error 3807 identifier exhaustion#4478
paulmedynski wants to merge 8 commits into
mainfrom
dev/paul/kerberos-identifiers

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes the recurring Create failed because all available identifiers have been exhausted. (SQL Server error 3807) failures in the Always Encrypted (AE) manual tests, seen on the sqlclient-ci-kerberos pipeline.

Root cause

SQL Server assigns each Column Master Key (CMK) and Column Encryption Key (CEK) an identifier from a monotonic, per-database counter that DROP never reclaims. Diagnosis on the shared Kerberos server confirmed this: with 0 live keys, the CMK counter was already at 143,789, and CREATE COLUMN ENCRYPTION KEY failed with 3807 — while the same statement in a fresh database succeeded with id=1. So the exhausted resource is the counter, not accumulated/leaked keys, and no amount of key cleanup can fix it.

The dominant consumer of that counter is ConversionTests: it created a fresh CMK+CEK in its constructor, and xUnit constructs a test-class instance per test case. With ~79 data rows across 3 theories, each run issued dozens of CREATE COLUMN MASTER/ENCRYPTION KEY statements from this one class, steadily advancing the counter until it hit the ceiling on the long-lived server. (Every other AE class already creates its keys once, via an IClassFixture.)

Change

  • Add ConversionTestFixture (an IClassFixture) that creates the CMK+CEK once per test class and drops them on dispose.
  • Refactor ConversionTests to consume that fixture instead of creating keys in its per-test constructor. Per-test tables are still created/dropped per case (tables don't consume the constrained identifier).

This reduces this class's key-creation churn by roughly two orders of magnitude, dramatically slowing counter growth on any persistent AE database (both the Kerberos Northwind server and the enclave CI servers, which are on the same trajectory).

Notes / scope

  • This slows growth; it does not reset an already-exhausted counter. The only reset is recreating the database, which was done manually on sqldrv-sql22 to unblock the pipeline. AE databases require external environment setup that can't currently be provisioned per-run, so a fresh-DB-per-run approach isn't viable.
  • Earlier commits in this branch explored an orphan-sweep / leak-cleanup approach (AlwaysEncryptedCleanup, idempotent drops, resilient teardown, and a dropOrphanedAlwaysEncryptedKeys.sql script). Those targeted key leakage, which turned out not to be the cause of 3807, so they have been reverted. The net change of this PR is just the two files above.

Issues

Diagnosed from Kerberos pipeline runs 20260724.1 and 20260727.3.

Testing

Adds tools/testsql/dropOrphanedAlwaysEncryptedKeys.sql to drop leaked
Always Encrypted test keys (CMKs/CEKs) and their dependent tables. Leaked
keys accumulate on long-lived shared test servers (e.g. the Kerberos CI
instance) until the CEK identifier range is exhausted, causing
'Create failed because all available identifiers have been exhausted'
during AE test setup. Targets only AE_/AE- prefixed test objects and
defaults to preview mode.
Copilot AI review requested due to automatic review settings July 27, 2026 12:54
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Jul 27, 2026

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.

Pull request overview

Adds a standalone T-SQL maintenance script to clean up orphaned Always Encrypted test artifacts (AE-prefixed CMKs/CEKs and dependent tables) in long-lived shared CI databases, mitigating “identifier exhaustion” failures during AE test setup.

Changes:

  • Introduces dropOrphanedAlwaysEncryptedKeys.sql with a default “WhatIf” preview mode that prints DROP statements.
  • Implements dependency-ordered cleanup (tables → CEKs → CMKs) with per-object TRY/CATCH to continue on failures.
  • Provides a summary and reports any remaining AE-prefixed keys after execution.

Comment thread tools/testsql/dropOrphanedAlwaysEncryptedKeys.sql Outdated
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Jul 27, 2026
@paulmedynski paulmedynski added the Area\Tests Issues that are targeted to tests or test projects label Jul 27, 2026
@paulmedynski paulmedynski added this to the 7.1.0-preview3 milestone Jul 27, 2026
Phase 1 of fixing 'Create failed because all available identifiers have
been exhausted' in the AE manual tests:

- Add AlwaysEncryptedCleanup helper: DropSafely (best-effort per-object
  drop that never aborts) and DropOrphanedAeArtifacts (idempotent sweep
  of AE_/AE- prefixed tables, CEKs, CMKs).
- Make CMK/CEK/Table Drop idempotent (guarded existence checks) so a
  missing/already-dropped object cannot throw and abort teardown.
- Roll back partially-created objects when SQLSetupStrategy.SetupDatabase
  or the ConversionTests constructor fails, stopping the CMK leak that
  occurred every time a CEK create failed.
- Route all AE fixture teardown through DropSafely with per-connection
  isolation so remaining keys are always dropped regardless of outcome.
Phase 2: add AlwaysEncryptedCleanup.SweepOrphansOnce, a one-time
per-process sweep of orphaned AE_/AE- prefixed tables, CEKs, and CMKs
left by earlier runs that were cancelled or hard-killed before teardown.
Invoked before key creation in SQLSetupStrategy.SetupDatabase and the
ConversionTests constructor. The ManualTests assembly runs serially
(XUnitAssemblyAttributes), so the sweep cannot race with keys created by
the current run.

Phase 3: add server-independent AlwaysEncryptedCleanupTests verifying
DropSafely attempts every object even when one drop throws (the leak
this change prevents) and tolerates an empty sequence.
Copilot AI review requested due to automatic review settings July 27, 2026 13:22
@paulmedynski paulmedynski changed the title Add Always Encrypted orphaned-key cleanup script for exhausted identifiers Fix Always Encrypted "exhausted identifiers" by making key cleanup leak-proof Jul 27, 2026

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread tools/testsql/dropOrphanedAlwaysEncryptedKeys.sql Outdated
Drop AlwaysEncryptedCleanupTests; we don't need tests that test the test
cleanup helpers. The cleanup is validated by real AE manual-test runs on
the pipeline.
Copilot AI review requested due to automatic review settings July 27, 2026 13:29

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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AlwaysEncryptedCleanup.cs:97

  • In the orphan-sweep T-SQL, the cursor for tables selects a schema-qualified, QUOTENAME'd identifier (e.g. [schema].[table]). Storing that in @name sysname (nvarchar(128)) can truncate longer schema-qualified names, leading to invalid DROP statements and leaving orphans behind. Use a wider NVARCHAR for @name (or separate @tableName/@keyName variables).
SET NOCOUNT ON;
DECLARE @name sysname, @sql nvarchar(max);

DropOrphanedAeArtifacts now counts and returns how many tables, CEKs, and
CMKs it removed, and SweepOrphansOnce writes a one-line summary per server
so a pipeline run shows the identifier recovery happening (or confirms
there were no orphans).
Copilot AI review requested due to automatic review settings July 27, 2026 13:47

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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

tools/testsql/dropOrphanedAlwaysEncryptedKeys.sql:55

  • @label is declared as nvarchar(400) but is assigned values from @dropTables.tableName (nvarchar(517)). If schema/table names are long, this can truncate the identifier and generate invalid DROP statements. Use nvarchar(517) for @label to match the max length of schema-qualified QUOTENAME output.
DECLARE @stmt   nvarchar(max);
DECLARE @label  nvarchar(400);
DECLARE @errMsg nvarchar(4000);

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AlwaysEncryptedCleanup.cs:128

  • The T-SQL sweep uses DECLARE @name sysname, but the table cursor stores a schema-qualified, QUOTENAME'd name which can be up to 517 characters. Using sysname (128) risks truncation and incomplete cleanup for long names.
DECLARE @name sysname, @sql nvarchar(max);

Comment thread src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs Outdated
Root cause of 'Create failed because all available identifiers have been
exhausted' on the long-lived Kerberos test DB: SQL Server assigns each
CMK/CEK an identifier from a monotonic per-database counter that DROP
never reclaims. ConversionTests created a fresh CMK+CEK in its
constructor, and xUnit constructs a test class instance per test case, so
each run issued dozens of CREATE COLUMN MASTER/ENCRYPTION KEY statements,
steadily advancing the counter until it hit error 3807.

Move the CMK/CEK creation into a new ConversionTestFixture class fixture
so the keys are created once per class instead of once per test case
(~two orders of magnitude fewer key creations per run). Per-test tables
are still created/dropped per case.

This also reverts the earlier orphan-sweep/leak-cleanup approach
(AlwaysEncryptedCleanup, idempotent drops, resilient teardown, and the
dropOrphanedAlwaysEncryptedKeys.sql script), which targeted key leakage
rather than the identifier-counter growth that actually causes 3807.
Copilot AI review requested due to automatic review settings July 27, 2026 15:50
@paulmedynski paulmedynski changed the title Fix Always Encrypted "exhausted identifiers" by making key cleanup leak-proof AE tests: create ConversionTests keys once per class to slow error 3807 identifier exhaustion Jul 27, 2026

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.

Pull request overview

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

- ConversionTests: remove using directives orphaned by the fixture
  refactor (System.Security.Cryptography.X509Certificates and
  Microsoft.Data.SqlClient.Tests.Common.Fixtures) to avoid unused-using
  diagnostics (CS8019). (PR #4478 discussion r3658797866)
- ConversionTestFixture.Dispose: normalize ConnectTimeout to a 30s
  minimum like the constructor, so timeout-prone AE teardown is less
  likely to be skipped and leave the shared CMK/CEK behind.
  (PR #4478 discussion r3658797921)
Copilot AI review requested due to automatic review settings July 27, 2026 16:11

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Wrap ConversionTestFixture.Dispose DB cleanup in try/finally and treat
CMK/CEK drops as best-effort per connection, so base.Dispose (which
removes the test certificate from the store) always runs even if a drop
or connection open throws. (PR #4478 discussion r3658967825)
Copilot AI review requested due to automatic review settings July 27, 2026 16:31

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.90%. Comparing base (a684c99) to head (0e83eee).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4478      +/-   ##
==========================================
- Coverage   64.64%   62.90%   -1.75%     
==========================================
  Files         288      283       -5     
  Lines       44009    66928   +22919     
==========================================
+ Hits        28451    42101   +13650     
- Misses      15558    24827    +9269     
Flag Coverage Δ
CI-SqlClient ?
PR-SqlClient-Project 62.90% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@paulmedynski
paulmedynski marked this pull request as ready for review July 28, 2026 10:36
@paulmedynski
paulmedynski requested a review from a team as a code owner July 28, 2026 10:36
@paulmedynski paulmedynski moved this from In progress to In review in SqlClient Board Jul 28, 2026
@paulmedynski
paulmedynski enabled auto-merge (squash) July 28, 2026 15:34

@cheenamalhotra cheenamalhotra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Must consider backporting to release branches as well.

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

Labels

Area\Tests Issues that are targeted to tests or test projects

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants