diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.cs index cdb54c60e37e9a..d9c1221662b389 100644 --- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.cs +++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using Microsoft.Win32.SafeHandles; namespace System.IO.Pipes @@ -12,7 +11,8 @@ namespace System.IO.Pipes public sealed partial class AnonymousPipeServerStream : PipeStream { private SafePipeHandle _clientHandle = null!; - private bool _clientHandleExposed; + private bool _clientHandleExposed, _clientHandleExposedAsString; + private readonly HandleInheritability _inheritability; public AnonymousPipeServerStream() : this(PipeDirection.Out, HandleInheritability.None, 0) @@ -73,6 +73,7 @@ public AnonymousPipeServerStream(PipeDirection direction, HandleInheritability i } Create(direction, inheritability, bufferSize); + _inheritability = inheritability; } ~AnonymousPipeServerStream() @@ -84,7 +85,7 @@ public AnonymousPipeServerStream(PipeDirection direction, HandleInheritability i // processes. For now, people do it via command line arguments. public string GetClientHandleAsString() { - _clientHandleExposed = true; + _clientHandleExposedAsString =_clientHandleExposed = true; GC.SuppressFinalize(_clientHandle); return _clientHandle.DangerousGetHandle().ToString(); } @@ -121,10 +122,11 @@ protected override void Dispose(bool disposing) { try { - // We should dispose of the client handle if it was not exposed. - if (!_clientHandleExposed && _clientHandle != null && !_clientHandle.IsClosed) + // We should dispose of the client handle when it was not exposed at all OR + // it was exposed as a string (handle finalization has been suppressed) and created inheritable (out-of-proc communication). + if (!_clientHandleExposed || (_clientHandleExposedAsString && _inheritability == HandleInheritability.Inheritable)) { - _clientHandle.Dispose(); + DisposeLocalCopyOfClientHandle(); } } finally diff --git a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CrossProcess.cs b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CrossProcess.cs index 9a68529796319b..85dd1fac34bfbf 100644 --- a/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CrossProcess.cs +++ b/src/libraries/System.IO.Pipes/tests/AnonymousPipeTests/AnonymousPipeTest.CrossProcess.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Threading; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -48,16 +47,23 @@ void ChildFunc(string inHandle, string outHandle) } } - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - public void ServerClosesPipe_ClientReceivesEof() + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(true)] + [InlineData(false)] + public void ServerClosesPipe_ClientReceivesEof(bool callDisposeLocalCopyOfClientHandle) { using (var pipe = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) using (var remote = RemoteExecutor.Invoke(new Action(ChildFunc), pipe.GetClientHandleAsString())) { - pipe.DisposeLocalCopyOfClientHandle(); + if (callDisposeLocalCopyOfClientHandle) + { + pipe.DisposeLocalCopyOfClientHandle(); + } + pipe.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); pipe.Dispose(); + Assert.True(pipe.ClientSafePipeHandle.IsClosed); Assert.True(remote.Process.WaitForExit(30_000)); }