Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The need to call DisposeLocalCopyOfClientHandle() after establishing a connection

Tags:

c#

windows

pipe

The step following the establishment of a connection with an anonymous pipe requires the server calling DisposeLocalCopyOfClientHandle. MSDN explains:

The DisposeLocalCopyOfClientHandle method should be called after the client handle has been passed to the client. If this method is not called, the AnonymousPipeServerStream object will not receive notice when the client disposes of its PipeStream object.

Trying to understand why wouldn't the server be noticed when client is closed, I went on to look at DisposeLocalCopyOfClientHandle on reference source:

// This method is an annoying one but it has to exist at least until we make passing handles between
// processes first class.  We need this because once the child handle is inherited, the OS considers
// the parent and child's handles to be different.  Therefore, if a child closes its handle, our 
// Read/Write methods won't throw because the OS will think that there is still a child handle around
// that can still Write/Read to/from the other end of the pipe.
//
// Ideally, we would want the Process class to close this handle after it has been inherited.  See
// the pipe spec future features section for more information.
// 
// Right now, this is the best signal to set the anonymous pipe as connected; if this is called, we
// know the client has been passed the handle and so the connection is live.
[System.Security.SecurityCritical]
public void DisposeLocalCopyOfClientHandle() {
    if (m_clientHandle != null && !m_clientHandle.IsClosed) {
       m_clientHandle.Dispose();
    }
}

This sentence confused me:

once the child handle is inherited, the OS considers the parent and child's handles to be different.

Aren't the parent's handle and the child's handle (i.e., the server's m_handle and the server's m_clientHandle, which is passed to the child) different in the first place? Does "different" here mean "references different objects" (this is the way I understand it), or does it have other meaning?

like image 422
OfirD Avatar asked Sep 25 '16 01:09

OfirD


1 Answers

The obscure detail that is hard to see in .NET is the bInheritHandles argument of CreateProcess(), a nasty little unixism that crept in the winapi. Determining the proper value of that argument is very difficult to reason through, you have to know a lot about the process you start and it scales really poorly, it is an all-or-nothing option. Raymond Chen has a blog post that talks about the ugly corner cases and what they did in Windows version 6.0 to solve the problem.

Not otherwise a solution that could be used in .NET. Primarily because it still supported older Windows versions up to .NET 4.5. And it would be pretty hard to use. Accordingly, the ProcessStartInfo class has no property that allows you to explicitly control the bInheritHandles argument value, Process.Start() always passes TRUE. Which is what the "until we make passing handles between processes first class" comment refers to.

A further detail is that the handle that the child process inherits is a separate handle that is distinct from the handle of the parent process. So a total of two CloseHandle calls are required to destroy the system object. Or to put it another way, both the parent and the child need to stop using the object. Which is what the "the OS considers the parent and child's handles to be different" comment refers to.

The underlying CreatePipe() winapi function that is used to create an anonymous pipe returns two handles, one to read and one to write. Depending on the pipe direction, one should be used by the parent (aka server) and one by the child process (aka client). These handles are inheritable handles so after you start the child process, a total of four CloseHandle calls are required to destroy the pipe object.

That's unpleasant. The .NET wrapper can do something about the server handle. It calls DuplicateHandle() to make a copy of the server-side handle, passing FALSE for the bInheritHandle argument. Then closes the original handle. Good, the child process will no longer inherit the server-side handle so now only three CloseHandle calls are required.

The same trick however cannot work for the pipe handle that the child process needs to use. After all, the intention is for it to inherit the handle so it can talk back to the server. Which is why you have to do it explicitly, after you know that the child process was started properly. After your DisposeLocalCopyOfClientHandle() method call now only two CloseHandle calls are required.

The CloseHandle call on the client side is easy enough, it does so by calling Close or Dispose on the AnonymousPipeClientStream. Or by keeling over by an unhandled exception that crashes the process, the OS then takes care of closing the handle. Now only one CloseHandle call is remaining.

One to go, it is harder on the server side. It only knows to close/dispose its AnonymousPipeServerStream when it gets the "notification" that the child process is no longer using it. Scary quotes around "notification", there is no event that tells you this. The proper way is for the child process to send an explicit "goodbye" message so the server knows to call Close. The not so proper but not uncommon way is that the child did not say goodbye nicely, then the server can only know it isn't around anymore from the exception it gets when it continues to use the pipe.

Which is the key, you only get the exception when the OS sees that the server tries to use the pipe and there are no remaining handles opened on the other side. Or in other words, if you forget to call DisposeLocalCopyOfClientHandle() then you don't get the exception. Not good.

like image 91
Hans Passant Avatar answered Sep 26 '22 08:09

Hans Passant