-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Anonymous pipe resource leak when DisposeLocalCopyOfClientHandle is not called #78562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…nheritable, its Dispose method should dispose the client no matter if it was exposed or not. This is going to allow users who did not call DisposeLocalCopyOfClientHandle to avoid resource leaks.
|
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsThis week I've spent few hours debugging a resource leak that was causing full Microbenchmarks run to fail on my Linux machine: dotnet/performance#2738. To my surprise, despite the fact that using AnonymousPipeServerStream inputFromBenchmark = new (PipeDirection.In, HandleInheritability.Inheritable);
using AnonymousPipeServerStream acknowledgments = new (PipeDirection.Out, HandleInheritability.Inheritable);This is how I've learned about the existence of I've taken a quick look to see if we could call it right after the client handle gets exposed, but it's impossible because the call to this method needs to happen after the creation of child process (so an opened handle can be inherited). My current best idea for now is to dispose the client handle (stored by the server) when the server instance gets disposed. Only in scenarios, where it was created with
|
|
Won't this be a breaking change if anonymous pipes are being used intraprocess? Let's say one thread is writing to the server stream, finishes writing, and disposes of the stream. Another thread is reading from the client stream. Today won't the client simply get EOF and their read will return 0, whereas now they could get an ObjectDisposedException? Or if they do get an exception today in this or related scenarios, won't it be a different type of exception? |
|
Oh, wait, you're still checking !_clientHandleExposed. Nevermind. |
|
Oh, no, you're not, if it was created as inheritable. My original question still stands :) |
It will, but only in case someone created anonymous pipe as inheritable (the intent of this is to perform outproc communication), but then uses it for inproc. I know the fix is not perfect, but IMO it's a lesser evil (I expect that way more people are going to avoid resource leak). |
|
@stephentoub Could we include this breaking change in .NET 8 Preview 1 and eventually revert it if it causes a lot of trouble (I really doubt that)? |
Outside of a benchmarking suite that's specifically testing creating tons of these, how impactful is that really? We're talking about one SafeHandle being left for finalization if someone doesn't follow the right pattern. Are these apps you're concerned about creating massive numbers of anonymous server/client pipe streams? |
In this case it's not about benchmarking the pipes, BDN uses them internally for the host-benchmark process communication. So we create two pipes per every benchmark.
The problem is that it's never left for finalization: runtime/src/libraries/System.IO.Pipes/src/System/IO/Pipes/AnonymousPipeServerStream.cs Lines 86 to 89 in b3de41e
So basically, if the user creates an inheritable handle, uses it in anyway, but does not call |
If GetClientHandleAsString is used. I was talking about a scenario where it's not used. Maybe a further mitigation is to split _clientHandleExposed. Have an additional _clientHandleExposedAsString, where GetClientHandleAsString sets both, and only do your proposed change if _clientHandleExposedAsString has been set. |
|
Is it possible to call |
…created with HandleInheritability.Inheritable and not exposed via SafePipeHandle property
Done, PTAL.
This would be also a breaking change and it would be non-deterministic: AnonymousPipeServerStream server = new (PipeDirection.Out, HandleInheritability.Inheritable);
SafePipeHandle clientPipeHandle = new (nint.Parse(server.GetClientHandleAsString()), ownsHandle: true);
server.Dispose();
server = null;
// Even if we re-register client handle owned by the server for finalization,
// it's not rooted by any object and going to be closed when the finalizer is executed.
// It might "work for a while" and then stop when the GC cleanups the memory.
Use(clientPipeHandle); |
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
|
Breaking change created: dotnet/docs#32713 |
This week I've spent few hours debugging a resource leak that was causing full Microbenchmarks run to fail on my Linux machine: dotnet/performance#2738.
To my surprise, despite the fact that
AnonymousPipeServerStreamwas wrapped with ausingblock, the resource leak was still happening.This is how I've learned about the existence of
DisposeLocalCopyOfClientHandlemethod (dotnet/BenchmarkDotNet#2200).I've taken a quick look to see if we could call it right after the client handle gets exposed, but it's impossible because the call to this method needs to happen after the creation of child process (so an opened handle can be inherited).
As of now, it's impossible. We could in theory call it after first successful read/write but tracking it would be error prone too.
My current best idea for now is to dispose the client handle (stored by the server) when the server instance gets disposed. Only in scenarios, where it was created with
HandleInheritability.Inheritable. I can't see any valid scenarios, where users would like to use client pipe handle owned by the server after the server got disposed.