From b9e02739750fec10ba6d874ce890cd8240eb8be1 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 7 Jan 2022 14:33:04 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=90=9E=E6=8E=89=20ObjectDisposedExceptio?= =?UTF-8?q?n=20=E5=9B=A0=E4=B8=BA=E7=9B=AE=E5=89=8D=E5=9C=A8=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E4=B8=8A=E6=8E=A8=E6=B5=8B=E6=B2=A1=E6=9C=89=E7=8E=B0?= =?UTF-8?q?=E8=B1=A1=EF=BC=8C=E4=BD=86=E5=88=B0=E5=BA=95=E8=B0=81=20Dispos?= =?UTF-8?q?e=20=E5=AE=83=E7=9A=84=E5=AD=98=E7=96=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Internals/IpcPipeServerMessageProvider.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) 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()}"); } } } From 9b16101126d0078c4439facc09607b92219e7ffd Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 7 Jan 2022 15:23:22 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=87=8D=E8=BF=9E?= =?UTF-8?q?=E6=97=B6=E5=87=BA=E7=8E=B0=E7=9A=84=20FileNotFoundException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pipes/IpcClientService.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs index 8a556c55..5c3b691f 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; @@ -83,12 +84,31 @@ public async Task Start(bool shouldRegisterToPeer = true) var namedPipeClientStream = new NamedPipeClientStream(".", PeerName, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.Impersonation); _namedPipeClientStreamTaskCompletionSource = new TaskCompletionSource(); + + // 带有重试的连接。 + for (int i = 0; i < 4; i++) + { + try + { #if NETCOREAPP - await namedPipeClientStream.ConnectAsync(); + await namedPipeClientStream.ConnectAsync(); #else - // 在 NET45 没有 ConnectAsync 方法 - await Task.Run(namedPipeClientStream.Connect); + // 在 NET45 没有 ConnectAsync 方法 + await Task.Run(namedPipeClientStream.Connect); #endif + break; + } + catch (FileNotFoundException ex) + { + // 因为每个端有两条管道,各自作为服务端和客户端。 + // 当重连时,靠的是服务端管道读到末尾来判断的;但此时重连的却是客户端。 + // 有极少情况下,这两条的断开时间间隔足够长到本方法的客户端已开始重连。 + // 那么,本方法的客户端在一开始试图连接对方时连上了,但随即就完成了之前没完成的断开,于是出现 FileNotFoundException。 + // 这时,我们通过重新连接一次可以再次等对方重新启动完再连。 + await Task.Delay(16); + } + } + if (!_namedPipeClientStreamTaskCompletionSource.Task.IsCompleted) { _namedPipeClientStreamTaskCompletionSource.SetResult(namedPipeClientStream); From 132d26e0bbafe59bd8606b9f416ad845f5a94747 Mon Sep 17 00:00:00 2001 From: walterlv Date: Fri, 7 Jan 2022 16:55:03 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=87=8D=E8=BF=9E?= =?UTF-8?q?=E6=97=B6=E5=87=BA=E7=8E=B0=E7=9A=84=E5=8F=A6=E4=B8=80=20FileNo?= =?UTF-8?q?tFoundException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Internals/PeerReConnector.cs | 38 ++++++++++++++++++- .../Pipes/IpcClientService.cs | 24 ++---------- 2 files changed, 40 insertions(+), 22 deletions(-) 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 5c3b691f..767b7c0b 100644 --- a/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs +++ b/src/dotnetCampus.Ipc/Pipes/IpcClientService.cs @@ -10,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; @@ -85,29 +86,12 @@ public async Task Start(bool shouldRegisterToPeer = true) PipeOptions.None, TokenImpersonationLevel.Impersonation); _namedPipeClientStreamTaskCompletionSource = new TaskCompletionSource(); - // 带有重试的连接。 - for (int i = 0; i < 4; i++) - { - try - { #if NETCOREAPP - await namedPipeClientStream.ConnectAsync(); + await namedPipeClientStream.ConnectAsync(); #else - // 在 NET45 没有 ConnectAsync 方法 - await Task.Run(namedPipeClientStream.Connect); + // 在 NET45 没有 ConnectAsync 方法 + await Task.Run(namedPipeClientStream.Connect); #endif - break; - } - catch (FileNotFoundException ex) - { - // 因为每个端有两条管道,各自作为服务端和客户端。 - // 当重连时,靠的是服务端管道读到末尾来判断的;但此时重连的却是客户端。 - // 有极少情况下,这两条的断开时间间隔足够长到本方法的客户端已开始重连。 - // 那么,本方法的客户端在一开始试图连接对方时连上了,但随即就完成了之前没完成的断开,于是出现 FileNotFoundException。 - // 这时,我们通过重新连接一次可以再次等对方重新启动完再连。 - await Task.Delay(16); - } - } if (!_namedPipeClientStreamTaskCompletionSource.Task.IsCompleted) {