diff --git a/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs b/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs index 3996b0d1..20ab1cf3 100644 --- a/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs +++ b/src/dotnetCampus.Ipc/Internals/IpcPipeServerMessageProvider.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Threading.Tasks; using dotnetCampus.Ipc.Context; using dotnetCampus.Ipc.Pipes; +using dotnetCampus.Ipc.Utils.Extensions; namespace dotnetCampus.Ipc.Internals { @@ -60,12 +62,18 @@ await Task.Factory.FromAsync(namedPipeServerStream.BeginWaitForConnection, namedPipeServerStream.EndWaitForConnection, null).ConfigureAwait(false); #endif } - catch (IOException e) + catch (IOException ex) { // "管道已结束。" // 当前服务关闭,此时异常符合预期 return; } + catch (ObjectDisposedException ex) + { + // 当等待客户端连上此服务端期间,被调用了 Dispose 方法后,会抛出此异常。 + // 日志在 Dispose 方法里记。 + return; + } //var streamMessageConverter = new StreamMessageConverter(namedPipeServerStream, // IpcConfiguration.MessageHeader, IpcConfiguration.SharedArrayPool); //streamMessageConverter.MessageReceived += OnClientConnectReceived; @@ -107,16 +115,25 @@ private async Task SendAckAsync(Ack receivedAck) public void Dispose() { - if (ServerStreamMessageReader is null) + try { - // 证明此时还没完全连接 - NamedPipeServerStream.Dispose(); + if (ServerStreamMessageReader is null) + { + // 证明此时还没完全连接 + NamedPipeServerStream.Dispose(); + } + else + { + // 证明已连接完成,此时不需要释放 NamedPipeServerStream 类 + // 不在这一层释放 NamedPipeServerStream 类 + ServerStreamMessageReader.Dispose(); + } } - else + finally { - // 证明已连接完成,此时不需要释放 NamedPipeServerStream 类 - // 不在这一层释放 NamedPipeServerStream 类 - ServerStreamMessageReader.Dispose(); + // 通过查看 Dispose 的堆栈来检查出异常时到底是谁在 Dispose。 + IpcContext.Logger.Warning(@$"IpcPipeServerMessageProvider.Dispose +{new StackTrace()}"); } } } diff --git a/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs index 90421e8f..0de65ded 100644 --- a/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs +++ b/src/dotnetCampus.Ipc/Internals/PeerReConnector.cs @@ -1,4 +1,7 @@ -using dotnetCampus.Ipc.Context; +using System.IO; +using System.Threading.Tasks; + +using dotnetCampus.Ipc.Context; using dotnetCampus.Ipc.Pipes; namespace dotnetCampus.Ipc.Internals @@ -27,7 +30,38 @@ private void PeerProxy_PeerConnectionBroken(object? sender, IPeerConnectionBroke private async void Reconnect() { var ipcClientService = _ipcProvider.CreateIpcClientService(_peerProxy.PeerName); - await ipcClientService.Start(); + for (int i = 0; i < 4; i++) + { + try + { + await ipcClientService.Start(); + break; + } + catch (FileNotFoundException ex) + { + // ## 此异常有两种 + // + // 1. 一种来自于 namedPipeClientStream.ConnectAsync(),刚调用时还能获取到管道句柄,但马上与之连接时便已断开。 + // 2. 另一种来自 RegisterToPeer(),前面已经连上了,但试图发消息时便已断开。 + // + // ## 然而,为什么一连上就断开了呢? + // + // 这是因为每个端有两条管道,各自作为服务端和客户端。 + // 当重连时,靠的是服务端管道读到末尾来判断的;但此时重连的却是客户端。 + // 有极少情况下,这两条的断开时间间隔足够长到本方法的客户端已开始重连。 + // 那么,本方法的客户端在一开始试图连接对方时连上了,但随即就完成了之前没完成的断开,于是出现 FileNotFoundException。 + // + // ## 那么,如何解决呢? + // + // 通过重连,我们可以缓解因对方正在断开导致的我们误连。通过重连多次,可以更大概率缓解以至于解决此异常。 + // + // ## 是否有后续问题? + // + // 有可能本方法已全部完成之后才断开吗?不可能,因为 RegisterToPeer() 会发消息的,如果是对方进程退出等原因导致的断连,那么消息根本就无法发送。 + // 因为本调用内会置一个 TaskCompleteSource,所以也会导致一起等待此任务的其他发送全部失败,而解决方法就是在其他发送处也重试。 + await Task.Delay(16); + } + } _peerProxy.Reconnect(ipcClientService); } } diff --git a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs index 8a556c55..767b7c0b 100644 --- a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.IO.Pipes; using System.Runtime.CompilerServices; using System.Security.Principal; @@ -9,6 +10,7 @@ using dotnetCampus.Ipc.Diagnostics; using dotnetCampus.Ipc.Internals; using dotnetCampus.Ipc.Messages; +using dotnetCampus.Ipc.Utils; using dotnetCampus.Ipc.Utils.Extensions; using dotnetCampus.Ipc.Utils.Logging; using dotnetCampus.Threading; @@ -83,12 +85,14 @@ public async Task Start(bool shouldRegisterToPeer = true) var namedPipeClientStream = new NamedPipeClientStream(".", PeerName, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.Impersonation); _namedPipeClientStreamTaskCompletionSource = new TaskCompletionSource(); + #if NETCOREAPP await namedPipeClientStream.ConnectAsync(); #else // 在 NET45 没有 ConnectAsync 方法 await Task.Run(namedPipeClientStream.Connect); #endif + if (!_namedPipeClientStreamTaskCompletionSource.Task.IsCompleted) { _namedPipeClientStreamTaskCompletionSource.SetResult(namedPipeClientStream);