Skip to content

Commit 40aa45c

Browse files
Fix ObjectDisposedException when disposing session after client.StopAsync()
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 #306
1 parent 73c1431 commit 40aa45c

File tree

2 files changed

+28
-2
lines changed

2 files changed

+28
-2
lines changed

dotnet/src/Session.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public partial class CopilotSession : IAsyncDisposable
5252
private readonly SemaphoreSlim _userInputHandlerLock = new(1, 1);
5353
private SessionHooks? _hooks;
5454
private readonly SemaphoreSlim _hooksLock = new(1, 1);
55+
private int _isDisposed;
5556

5657
/// <summary>
5758
/// Gets the unique identifier for this session.
@@ -553,8 +554,24 @@ await InvokeRpcAsync<object>(
553554
/// </example>
554555
public async ValueTask DisposeAsync()
555556
{
556-
await InvokeRpcAsync<object>(
557-
"session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], CancellationToken.None);
557+
if (Interlocked.Exchange(ref _isDisposed, 1) == 1)
558+
{
559+
return;
560+
}
561+
562+
try
563+
{
564+
await InvokeRpcAsync<object>(
565+
"session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], CancellationToken.None);
566+
}
567+
catch (ObjectDisposedException)
568+
{
569+
// Connection was already disposed (e.g., client.StopAsync() was called first)
570+
}
571+
catch (IOException)
572+
{
573+
// Connection is broken or closed
574+
}
558575

559576
_eventHandlers.Clear();
560577
_toolHandlers.Clear();

dotnet/test/ClientTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,13 @@ public void Should_Throw_When_UseLoggedInUser_Used_With_CliUrl()
243243
});
244244
});
245245
}
246+
247+
[Fact]
248+
public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client()
249+
{
250+
await using var client = new CopilotClient(new CopilotClientOptions { CliPath = _cliPath });
251+
await using var session = await client.CreateSessionAsync();
252+
253+
await client.StopAsync();
254+
}
246255
}

0 commit comments

Comments
 (0)