From 7d25ce19dbeb93093978607287b675839fa8b9fa Mon Sep 17 00:00:00 2001 From: Beast-ofcourse Date: Tue, 7 Jul 2026 11:55:00 +0530 Subject: [PATCH] fix(stdio): handle ObjectDisposedException from disposed NetworkStream during domain reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During domain reload, Stop() closes all active TcpClient connections. Any pending ReadAsync/WriteAsync on the disposed NetworkStream throws ObjectDisposedException. The existing top-level catch in HandleClientAsync already treats this as benign (ex is ObjectDisposedException), but the I/O methods should also handle it defensively so the exception type is consistent throughout the stack. Fix: - In ReadExactAsync: catch ObjectDisposedException and throw IOException with a clear 'Connection closed' message — the standard .NET pattern. - In WriteFrameAsync: wrap the WriteAsync calls in try/catch for ObjectDisposedException, same treatment. This ensures the disposed-stream scenario is handled uniformly as an IOException at every level, not just one catch block. Fixes #1232 --- .../Transport/Transports/StdioBridgeHost.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index c4fb2438d..4853256dc 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -735,6 +735,10 @@ private static async Task ReadExactAsync(NetworkStream stream, int count { throw new IOException("Read timed out"); } + catch (ObjectDisposedException) + { + throw new IOException("Connection closed before reading expected bytes"); + } } return buffer; @@ -758,13 +762,20 @@ private static async Task WriteFrameAsync(NetworkStream stream, byte[] payload, } byte[] header = new byte[8]; WriteUInt64BigEndian(header, (ulong)payload.LongLength); + try + { #if NETSTANDARD2_1 || NET6_0_OR_GREATER - await stream.WriteAsync(header.AsMemory(0, header.Length), cancel).ConfigureAwait(false); - await stream.WriteAsync(payload.AsMemory(0, payload.Length), cancel).ConfigureAwait(false); + await stream.WriteAsync(header.AsMemory(0, header.Length), cancel).ConfigureAwait(false); + await stream.WriteAsync(payload.AsMemory(0, payload.Length), cancel).ConfigureAwait(false); #else - await stream.WriteAsync(header, 0, header.Length, cancel).ConfigureAwait(false); - await stream.WriteAsync(payload, 0, payload.Length, cancel).ConfigureAwait(false); + await stream.WriteAsync(header, 0, header.Length, cancel).ConfigureAwait(false); + await stream.WriteAsync(payload, 0, payload.Length, cancel).ConfigureAwait(false); #endif + } + catch (ObjectDisposedException) + { + throw new IOException("Connection closed before writing frame"); + } } private static async Task ReadFrameAsUtf8Async(NetworkStream stream, int timeoutMs, CancellationToken cancel)