Leaky Abstractions, System.Diagnostics.Process, and 1 bottle of Mountain Dew
This is a story about leaky abstractions. It’s not a happy story, and I don’t talk about cool things like WPF, AJAX, JSON, POX, or how much I detest the RIAA.
Instead, this is the story of a developer and the Process.Start() method. It’s not exciting, and I don’t mention Silverlight anywhere other than this sentence.
Still, you should listen.
Leaky Abstractions are the technological equivalent of a spring poking through your couch. Until the day that you sat on that rusty, pointy springy-spring, you knew that your couch was comfy. To tell the truth, you really didn’t know, care, or care to know why. Joel Spolsky has his own diatribe on this subject, and it’s worth reading. Of course, he talks about software, and not couch springs, but I’m sure you will still get the point.
Some time ago, my team noticed that a core part of our application would fail to respond, and when we restarted it, it would fail to reclaim its remoting TCP/IP port. Specifically, it listened on port 30123, and whenever we restarted the application, we would receive a System.Net.Sockets.SocketException claiming that the port was already in use. From the command line, a netstat –a verified this information. The netstat output also revealed that the “dead” process had the port still locked. We were mystified, as netstat was reporting a Process ID (PID) that was no longer in the task list.
After much head scratching, we engaged Microsoft Product Support Services. Unfortunately, Microsoft PSS did about as much head scratching as we did. As this problem was only intermittent, our troubleshooting was particularly difficult. Eventually, we were able to repro the issue in our development environments, and Microsoft escalated the issue to their internal debugging teams. We submitted several crashdumps, and discovered some really, really interesting news:
Under certain circumstances, when you launch a process using the Microsoft System.Diagnostics.Process.Start() method, all handles from the parent process are inherited by the child process.
This may not be particularly interesting, but consider the following potential chain of events:
1. Process A starts.
2. Process A opens a TCP port.
3. Process A starts Process B using System.Diagnostics.Process.Start
4. Process A terminates unexpectedly, and does not properly close its TCP port.
5. Process B maintains a copy of the handle to the TCP port.
6. As there is an existing open handle, the Operating System does not close the port when cleaning up dead resources for process A.
It took several user generated crash dumps, but Microsoft PSS finally confirmed that this was the issue.
How does this tie into the concept of Leaky Abstractions? Well, if we examine the Process.Start() method using Reflector, we note the following line of code:
flag = NativeMethods.CreateProcess( null,
cmdLine,
null,
null,
true,
creationFlags,
zero,
workingDirectory,
lpStartupInfo,
lpProcessInformation);
If you note the fifth parameter is hardcoded to true, and if you are familiar with the fact that the fifth parameter to CreateProcess is boolean bInheritHandles, I think you’ll understand our handle leak.
I believe that I would be hard-pressed to find a single person (besides the author of Process.Start ) who actually understands that the .NET CreateProcess method defaults the bInheritsHandles bool to true. Nowhere in the Process class documentation is this mentioned, and even our interactions with Microsoft Support failed to pin this issue down until we resorted to crash dump analysis.
What’s the moral of the story? I’m not really sure. I know that I wouldn’t urge anyone to avoid the .NET Process class, but it would be nice if there were mechanisms for finding out this information short of WinDbg and Reflector.
Only 1 bottle of Mountain Dew was harmed during the creation of this article.