I'm trying to investigate a really nasty software crash which is possibly related to a managed heap corruption (since it happens during a garbage collection). Using WinDbg with the (SOS) !gchandles command I get something like
0:000> !gchandles GC Handle Statistics: Strong Handles: 259 Pinned Handles: 137 Async Pinned Handles: 1 Ref Count Handles: 79 Weak Long Handles: 197 Weak Short Handles: 650 Other Handles: 0 Statistics:
And I'm just curious, what is the difference between a "normal" pinned handle and an "async pinned" one? And can I find which one of my handles is the "async" one? I couldn't find any information on the net about it and since it seems that the application always crashes when this counter is exactly one it might be relevant to the crash. But then again it might just be some internal stuff used during the garbage collection..
Async pinned handles are strongly correlated with overlapped I/O in Windows. Which supports asynchronous reading and writing with ReadFile and WriteFile, using the OVERLAPPED argument. The device driver stores the passed buffer pointer and directly reads/writes from/to the buffer, entirely asynchronously from the program's operation. The managed wrapper methods are BeginRead and BeginWrite.
If the buffer is allocated in the GC heap then it needs to be pinned until the driver finishes using the buffer. Having the GC move the buffer while the driver is working on the I/O transfers is disastrous, writes would produce junk and reads would corrupt the GC heap, pinning is required to prevent the buffer from being moved while the driver is using it.
Pinned objects are pretty unpleasant, they give the garbage collector a hard time to work around the rock in the road when it compacts the heap. A necessary evil here, the only possible way to get ahead is to leave the buffer pinned for as short amount of time as possible.
Async pinned handles are marked specially to allow the CLR to automatically unpin the buffer on I/O completion. As quickly as possible, when the I/O completion port signals completion and thus not having to wait for the client code to execute the callback and unpin the buffer. Which could take a while when there are lots of threadpool threads in flight. It is a micro-optimization that tends to turn into a macro one when you have, say, a web server that handles tens of thousands of client requests.
It is only ever used for objects of type System.Threading.OverlappedData, an internal class in mscorlib.dll that the CLR has special knowledge of and is the managed facsimile for the native OVERLAPPED structure that the Windows api functions use.
Long story short, all you really know is that there's an overlapped I/O pending if you see the handle count at 1 when it crashes. Having any native code that does overlapped I/O with gc allocated buffers that are not pinned is otherwise indeed a good way to destroy the heap. You have rather a lot of pinned handles btw.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With