Skip to content

Fix ObjectDisposedException when disposing session after client.StopAsync()#371

Open
parthtrivedi2492 wants to merge 1 commit intogithub:mainfrom
parthtrivedi2492:fix/dotnet-session-dispose-after-stop
Open

Fix ObjectDisposedException when disposing session after client.StopAsync()#371
parthtrivedi2492 wants to merge 1 commit intogithub:mainfrom
parthtrivedi2492:fix/dotnet-session-dispose-after-stop

Conversation

@parthtrivedi2492
Copy link

Summary

Fixes #306

When await client.StopAsync() is called before the await using var session goes out of scope, session.DisposeAsync() is called twice:

  1. First by StopAsync() (while connection is alive) ✓
  2. Second by await using cleanup (connection is now disposed) ✗ → ObjectDisposedException

Repro (from issue)

using GitHub.Copilot.SDK;

await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync();

await client.StopAsync();
// When method exits, 'await using var session' tries to dispose → CRASH

Solution

Make CopilotSession.DisposeAsync() idempotent and tolerant to already-disposed connections:

  1. Added _isDisposed flag - Thread-safe using Interlocked.Exchange
  2. Made DisposeAsync idempotent - Returns immediately if already disposed (required by IAsyncDisposable contract)
  3. Handle connection failures gracefully - Catches ObjectDisposedException and IOException since dispose methods should never throw

This follows the same pattern already used in CopilotClient.DisposeAsync().

Changes

  • dotnet/src/Session.cs: Add idempotency and exception handling to DisposeAsync()
  • dotnet/test/ClientTests.cs: Add regression test

Testing

  • All 13 ClientTests pass including the new regression test
  • Build passes with no errors

…sync()

Make CopilotSession.DisposeAsync() idempotent and tolerant to already-disposed
connections. This fixes the crash when 'await using var session' cleanup runs
after client.StopAsync() has already disposed the connection.

Changes:
- Add _isDisposed flag with Interlocked.Exchange for thread-safe idempotency
- Catch ObjectDisposedException and IOException during dispose (connection gone)
- Add regression test to ClientTests

Fixes github#306
@parthtrivedi2492 parthtrivedi2492 requested a review from a team as a code owner February 4, 2026 15:25
Copilot AI review requested due to automatic review settings February 4, 2026 15:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a double disposal bug where disposing a CopilotSession after calling client.StopAsync() would throw an ObjectDisposedException. The issue occurred because StopAsync() disposes all sessions, and then the await using cleanup would attempt to dispose the session again on an already-closed connection.

Changes:

  • Added thread-safe idempotency to CopilotSession.DisposeAsync() using an _isDisposed flag with Interlocked.Exchange
  • Added exception handling to gracefully handle disposal when the connection is already disposed
  • Added a regression test to prevent this issue from recurring

Reviewed changes

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

File Description
dotnet/src/Session.cs Implements idempotent disposal with thread-safe flag and exception handling for ObjectDisposedException and IOException
dotnet/test/ClientTests.cs Adds regression test that reproduces the issue scenario: creating a session, calling StopAsync(), then allowing implicit disposal

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.

Disposing of a CopilotSession after stopping the client results in exception

1 participant