@@ -23,6 +23,8 @@ public static partial class MCPForUnityBridge
23
23
private static bool isRunning = false ;
24
24
private static readonly object lockObj = new ( ) ;
25
25
private static readonly object startStopLock = new ( ) ;
26
+ private static readonly object clientsLock = new ( ) ;
27
+ private static readonly System . Collections . Generic . HashSet < TcpClient > activeClients = new ( ) ;
26
28
private static CancellationTokenSource cts ;
27
29
private static Task listenerTask ;
28
30
private static int processingCommands = 0 ;
@@ -196,9 +198,15 @@ private static void EnsureStartedOnEditorIdle()
196
198
}
197
199
198
200
isStarting = true ;
199
- // Attempt start; if it succeeds, remove the hook to avoid overhead
200
- Start ( ) ;
201
- isStarting = false ;
201
+ try
202
+ {
203
+ // Attempt start; if it succeeds, remove the hook to avoid overhead
204
+ Start ( ) ;
205
+ }
206
+ finally
207
+ {
208
+ isStarting = false ;
209
+ }
202
210
if ( isRunning )
203
211
{
204
212
EditorApplication . update -= EnsureStartedOnEditorIdle ;
@@ -378,6 +386,18 @@ public static void Stop()
378
386
}
379
387
}
380
388
389
+ // Proactively close all active client sockets to unblock any pending reads
390
+ TcpClient [ ] toClose ;
391
+ lock ( clientsLock )
392
+ {
393
+ toClose = activeClients . ToArray ( ) ;
394
+ activeClients . Clear ( ) ;
395
+ }
396
+ foreach ( var c in toClose )
397
+ {
398
+ try { c . Close ( ) ; } catch { }
399
+ }
400
+
381
401
// Give the background loop a short window to exit without blocking the editor
382
402
if ( toWait != null )
383
403
{
@@ -440,6 +460,9 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
440
460
using ( client )
441
461
using ( NetworkStream stream = client . GetStream ( ) )
442
462
{
463
+ lock ( clientsLock ) { activeClients . Add ( client ) ; }
464
+ try
465
+ {
443
466
// Framed I/O only; legacy mode removed
444
467
try
445
468
{
@@ -479,7 +502,7 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
479
502
try
480
503
{
481
504
// Strict framed mode only: enforced framed I/O for this connection
482
- string commandText = await ReadFrameAsUtf8Async ( stream , FrameIOTimeoutMs ) ;
505
+ string commandText = await ReadFrameAsUtf8Async ( stream , FrameIOTimeoutMs , token ) . ConfigureAwait ( false ) ;
483
506
484
507
try
485
508
{
@@ -491,7 +514,7 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
491
514
}
492
515
catch { }
493
516
string commandId = Guid . NewGuid ( ) . ToString ( ) ;
494
- TaskCompletionSource < string > tcs = new ( ) ;
517
+ var tcs = new TaskCompletionSource < string > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
495
518
496
519
// Special handling for ping command to avoid JSON parsing
497
520
if ( commandText . Trim ( ) == "ping" )
@@ -510,7 +533,7 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
510
533
commandQueue [ commandId ] = ( commandText , tcs ) ;
511
534
}
512
535
513
- string response = await tcs . Task ;
536
+ string response = await tcs . Task . ConfigureAwait ( false ) ;
514
537
byte [ ] responseBytes = System . Text . Encoding . UTF8 . GetBytes ( response ) ;
515
538
await WriteFrameAsync ( stream , responseBytes ) ;
516
539
}
@@ -533,6 +556,11 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
533
556
break ;
534
557
}
535
558
}
559
+ }
560
+ finally
561
+ {
562
+ lock ( clientsLock ) { activeClients . Remove ( client ) ; }
563
+ }
536
564
}
537
565
}
538
566
@@ -611,9 +639,9 @@ private static async System.Threading.Tasks.Task WriteFrameAsync(NetworkStream s
611
639
#endif
612
640
}
613
641
614
- private static async System . Threading . Tasks . Task < string > ReadFrameAsUtf8Async ( NetworkStream stream , int timeoutMs )
642
+ private static async System . Threading . Tasks . Task < string > ReadFrameAsUtf8Async ( NetworkStream stream , int timeoutMs , CancellationToken cancel )
615
643
{
616
- byte [ ] header = await ReadExactAsync ( stream , 8 , timeoutMs ) ;
644
+ byte [ ] header = await ReadExactAsync ( stream , 8 , timeoutMs , cancel ) . ConfigureAwait ( false ) ;
617
645
ulong payloadLen = ReadUInt64BigEndian ( header ) ;
618
646
if ( payloadLen > MaxFrameBytes )
619
647
{
@@ -626,7 +654,7 @@ private static async System.Threading.Tasks.Task<string> ReadFrameAsUtf8Async(Ne
626
654
throw new System . IO . IOException ( "Frame too large for buffer" ) ;
627
655
}
628
656
int count = ( int ) payloadLen ;
629
- byte [ ] payload = await ReadExactAsync ( stream , count , timeoutMs ) ;
657
+ byte [ ] payload = await ReadExactAsync ( stream , count , timeoutMs , cancel ) . ConfigureAwait ( false ) ;
630
658
return System . Text . Encoding . UTF8 . GetString ( payload ) ;
631
659
}
632
660
0 commit comments