Perf: Bound the ActiveDirectoryServicePrincipal token gate with a fixed semaphore pool - #2983
Open
Divyansh-jain2 wants to merge 4 commits into
Open
Perf: Bound the ActiveDirectoryServicePrincipal token gate with a fixed semaphore pool#2983Divyansh-jain2 wants to merge 4 commits into
Divyansh-jain2 wants to merge 4 commits into
Conversation
…ential semaphores
Make poolIndexForHashedSecret and SEM_POOL_SIZE package-private so the deterministic credential-to-slot mapping can be unit-tested. Adds SQLServerMSAL4JUtilsTest coverage: the index is always within [0, SEM_POOL_SIZE) (including negative hashCode inputs, guarding the sign-bit mask) and is deterministic for a given hashed secret.
Add a reference-only harness (docs/performance, not compiled by Maven) that drives K threads round-robined across N distinct service principals and reports the driver's TOKEN_ACQUISITION timings via PerformanceLogCallback. This is the multi-SP workload that exercises the cross-principal gate contention the semaphore pool relieves; the single-SP harness shows no difference because all callers hash to one slot.
Divyansh-jain2
marked this pull request as ready for review
July 12, 2026 06:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
On the
ActiveDirectoryServicePrincipaltoken-acquisition path, every call is gated by a single JVM-wideSemaphore(1). That gate protects the Entra ID (AAD) token endpoint from a stampede, but it also serialises token acquisitions for completely unrelated service principals — even though they share no cache state — so unrelated principals block one another under concurrency.This PR replaces the single global gate (on the SP flow only) with a fixed-size pool of
Nsemaphores (SEM_POOL_SIZE = 10). Each credential is mapped deterministically onto exactly one slot via itshashedSecret:Bounded: at most
Nsemaphores exist for the lifetime of the JVM — allocated once in a static initializer, never grown.Deterministic: the same credential always maps to the same slot, so concurrent callers for one principal still serialise on the same permit (preserving the anti-stampede guarantee per credential).
Correct under collision: two distinct principals may occasionally share a slot, which costs only a little extra serialisation — never an incorrect token, because token correctness comes entirely from the
hashedSecret-keyedTOKEN_CACHE_MAP, not from the gate.API changes / backwards compatibility: None. No public API, connection property, or default behavior change. Only the internal gate for the SP-secret flow changed; the user/password, certificate, integrated, and interactive flows keep the existing global
Semaphore(1).Who benefits: Applications opening many concurrent connections across multiple distinct service principals with
authentication=ActiveDirectoryServicePrincipal.How it works
The gate partition (
hashedSecret→ slot) matches the cache partition (hashedSecret→TOKEN_CACHE_MAPentry), andTOKEN_SEM_WAIT_DURATION_MSstill lets a follower proceed if the slot leader is slow, so a stuck leader never blocks its slot indefinitely.Why a bounded pool rather than one semaphore per credential: a per-credential map would grow without bound as principals/secrets rotate and is never evicted. The pool caps memory at
Nsemaphores while retaining almost all of the per-principal concurrency benefit.Testing
Unit tests in
SQLServerMSAL4JUtilsTest(offline, no server or Azure credentials):testPoolIndexIsAlwaysWithinBounds[0, SEM_POOL_SIZE)(an out-of-bounds index would throwArrayIndexOutOfBoundsExceptionon the token path)testPoolIndexHandlesNegativeHashCodehashCode() == Integer.MIN_VALUEstill maps to a non-negative slot — guards the& 0x7fffffffsign-bit masktestPoolIndexIsDeterministicAll existing tests continue to pass.
Performance test methodology
Measured using the driver's built-in
PerformanceLogCallback(theTOKEN_ACQUISITIONactivity from #2706) — no external timing. A reference harness is included atdocs/performance/MultiSpTokenAcquisitionTest.java(reference only — not compiled by Maven).This change only helps a multi-service-principal workload — with a single principal every caller hashes to the same pool slot, so it behaves identically to the global gate. The benchmark therefore round-robins K threads across N distinct principals.
SELECT 1→ closeauthentication=ActiveDirectoryServicePrincipalSEM_POOL_SIZE = 10connectRetryCount=0andINTERMITTENT_TLS_MAX_RETRY=0(both baseline and this branch built identically)TOKEN_ACQUISITIONviaPerformanceLogCallback(ns precision)main(globalSemaphore(1)), unmodified apart from the two retry knobs aboveToken acquisition avg (ms) — lower is better
Avg connection latency (ms) — lower is better
Token acquisition is ~45–48% cheaper across all durations because up to
N=10distinct principals acquire concurrently instead of serialising on one global permit. In the baseline, all 100 principals share one permit, so unrelated token acquisitions queue behind each other (~210–222 ms avg); with the pool they spread across 10 slots (~110–119 ms avg).Guidelines
Reviewed the contribution guidelines, code of conduct, best practices, coding guidelines, and review process.