Limited execution areas is the C # /. Net function, which allows the developer to try to get the "big three" exceptions from critical areas of the code - OutOfMemory, StackOverflow and ThreadAbort.
CER achieve this by deferring ThreadAborts, preparing all the methods in the call graph (so that JIT should not happen, which could cause distribution), and by providing enough stack space is available to match the next call stack.
A typical area without interruption might look like this:
public static void GetNativeFlag() { IntPtr nativeResource = new IntPtr(); int flag; // Remember, only the finally block is constrained; try is normal. RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { NativeMethods.GetPackageFlags( ref nativeResource ); if ( nativeResource != IntPtr.Zero ) { flag = Marshal.ReadInt32( nativeResource ); NativeMethods.FreeBuffer( nativeResource ); } } }
Above all, it’s good and good because none of the rules are broken inside CER - all .Net distributions are outside CER, Marshal.ReadInt32() has a compatible ReliabilityContract , and we assume that my NativeMethods are similarly labeled so that the virtual machine can correctly consider them when preparing CER.
So, with all this in mind, how do you deal with situations where the distribution should occur inside the CER? Distribution breaks the rules, because it is very easy to get an OutOfMemoryException.
I ran into this problem when requesting my own API (SSPI QuerySecurityPackageInfo ), which causes me to break these rules. The native API performs its own distribution, but if it fails, I just get an empty result, so there isn’t much there. However, in the structure that it allocates, it stores several C-lines of unknown size.
When he returns me a pointer to the structure allocated to him, I need to copy all this and allocate a place to store these c-lines as .NET string objects. After all this, I have to say to free the selection.
However, since I am executing .Net distributions in CER, I break the rules and possibly a handle leak.
What is the right way to handle this?
For what it's worth, this is my naive approach:
internal static SecPkgInfo GetPackageCapabilities_Bad( string packageName ) { SecPkgInfo info; IntPtr rawInfoPtr; rawInfoPtr = new IntPtr(); info = new SecPkgInfo(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr ); if ( rawInfoPtr != IntPtr.Zero ) {
Edit
I must mention that the “success” for me in this case is that I never lose a pen; this is normal if I perform a selection that fails and release the handle and then return an error to my caller, which indicates a distribution failure. I just can’t leak out the pens.
Edit to respond to Frank Hilleman
We do not have much control over the memory allocations that are required when making interop calls.
Depending on what you mean, the memory that can be allocated to make a call to the call, or the memory created by the called call?
We have perfect control over the memory allocated to make the call — the memory created by JIT to compile the methods involved, and the memory needed by the stack to make the call. JIT compilation memory is allocated during CER preparation; if this fails, the entire CER is never executed. CER preparation also calculates how much stack space is required in the static call schedule performed by CER, and aborts CER preparation if there is not enough stack.
By the way, this has to do with preparing the stack space for any try-catch-finally frames, even nested try-catch-finally frames that define and participate in CER. The nested try-catch attempt inside CER is quite reasonable, because JIT can calculate the amount of stack memory needed to write the try-catch-finally context and interrupt CER preparation anyway if too much is required.
The call itself may perform some memory allocations outside the .net heap; I am surprised that internal calls are allowed inside CER in general.
If you had in mind your own memory allocations performed by the called call, then this is also not a problem for CERs. Native memory allocations are either successful or return a status code. OOMs are not generated using native memory allocations. If the native distribution fails, presumably the native API that I call processes it by returning a status code or a null pointer. The call is still deterministic. The only side effect is that it can lead to a failure of subsequent managed allocations due to increased memory pressure. However, if we either never execute distributions, or we can deterministically handle failed managed distributions, then this is still not a problem.
Thus, the only kind of distribution that is bad in CER is managed distribution, as this can cause an “asynchronous” OOM exception. So now the question is how do I deterministically handle the failed managed distribution inside CER ..
But it is quite possible. CER can have nested try-catch-finally blocks. All calls to the CER and all the stack space required by the CER, even to write the context of the nested try-finally inside the CER, can finally be deterministically calculated during the preparation of the entire CER before any of my codes actually execute.