Skip to content

Keep a replaced Azure DevOps session open until its last holder releases it [patch] - #315

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/298-lease-azure-devops-sessions
Sep 29, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/298-lease-azure-devops-sessions

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #298

What was wrong

CredentialedSessionCache.Get disposed the previous VssConnection as soon as the credentials changed. Requests that had already been handed that session were still using it, either waiting out the rate-limit delay or partway through the HTTP call. The Set Credentials flow changes the account and then the token in two separate popups, so re-entering a PAT did this twice while polling carried on. The ObjectDisposedException then escaped MakeAzureDevOpsRequestAsync and faulted the update loop.

The fix

Sessions are now leased with a reference count, which is the first option the issue proposed.

  • Get returns a CredentialedSessionCache<T>.Lease (IDisposable) instead of the bare session.
  • A session is retired rather than disposed in three cases: it is replaced by new credentials, it is Invalidated, or the cache itself is disposed. The last lease released on it disposes it, exactly once. Releasing the same lease twice does nothing.
  • The five Azure DevOps update paths hold their lease with using for the whole request.

Tests

  • ASessionHandedToACallerSurvivesARebuildBehindIt now also asserts that the held session is not disposed, which the issue asked for.
  • New ASessionReplacedWhileHeldIsDisposedOnlyWhenReleased: a deterministic version of the PAT re-entry path.
  • New AnInvalidatedSessionStaysOpenUntilItsLastHolderReleasesIt: two holders across an Invalidate.
  • DisposeDisposesTheCachedSessionOnce now checks that disposal waits for an outstanding lease.
  • The existing tests were moved onto leases.

I checked that the tests catch the bug by temporarily making retirement dispose immediately. Five tests failed, the four listed above plus EverySupersededSessionIsDisposedExactlyOnce. After restoring the fix, dotnet test passes 74 of 74.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XkEHuDnqdEMrwMy3iu9Kyr


Generated by Claude Code

…releases it [patch]

CredentialedSessionCache disposed the previous VssConnection as soon as the
credentials changed, while requests that had already been handed it were still
waiting out the rate-limit delay or mid-call. Set Credentials changes the
account and then the token in two popups, so re-entering a PAT did this twice
while polling continued, and the resulting ObjectDisposedException escaped
MakeAzureDevOpsRequestAsync and faulted the update loop.

Get now returns a lease. A session that is replaced, invalidated, or outlived
by the cache is retired rather than disposed, and the last lease released on it
disposes it. The Azure DevOps update paths hold their lease for the whole
request with `using`.

Fixes #298

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkEHuDnqdEMrwMy3iu9Kyr
Each update path now changes by one line: it takes the lease with `using` and
gets the session through an out parameter, so its existing null check and
client calls are untouched. The provider paths cannot be driven from a test,
because they build a real VssConnection, so keeping the change there to a
single line per path keeps the new code's coverage above the quality gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkEHuDnqdEMrwMy3iu9Kyr
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 9abe744 into main Sep 29, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the fix/298-lease-azure-devops-sessions branch September 29, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Changing Azure DevOps credentials disposes the VssConnection that in-flight requests are still using

1 participant