如何处理受约束执行区域中的分配?

约束执行区域是C#/ .Net的一个特性,它允许开发人员尝试从关键的代码区域中提升“三大”exception–OutOfMemory,StackOverflow和ThreadAbort。

CER通过推迟ThreadAborts,准备调用图中​​的所有方法来实现这一点(因此不会发生JIT,这可能导致分配),并确保有足够的堆栈空间来适应随后的调用堆栈。

典型的不间断区域可能如下所示:

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 ); } } } 

以上几乎都很好,因为没有规则在CER内部被破坏 – 所有.Net分配都在CER之外, Marshal.ReadInt32()有一个兼容的ReliabilityContract ,我们假设我的NativeMethods被标记为类似于在准备CER时,VM可以正确地考虑它们。

因此,除了所有这些之外,您如何处理分配必须在CER内部发生的情况? 分配违反了规则,因为很有可能获得OutOfMemoryException。

在查询本机API(SSPI的QuerySecurityPackageInfo )时,我遇到了这个问题,这会迫使我违反这些规则。 本机API确实执行自己的(本机)分配,但如果失败,我只得到一个空的结果,所以没有什么大不了的。 但是,在它分配的结构中,它存储了一些未知大小的C字符串。

当它返回指向它所分配的结构的指针时,我必须复制整个事物,并分配空间来存储这些c字符串作为.Net字符串对象。 毕竟,我应该告诉它释放分配。

但是,由于我在CER中执行.Net分配,我违反规则并可能泄漏一个句柄。

处理这个问题的正确方法是什么?

对于它的价值,这是我天真的方法:

 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 ) { // This performs allocations as it makes room for the strings contained in the result. Marshal.PtrToStructure( rawInfoPtr, info ); NativeMethods.FreeContextBuffer( rawInfoPtr ); } } return info; } 

编辑

我应该提一下,在这种情况下我的“成功”是我从不泄漏手柄; 如果我执行失败的分配,并释放句柄,然后向我的调用者返回一个错误,指示分配失败,那就没问题。 只是不能泄漏手柄。

编辑以回应Frank Hileman

当我们执行互操作调用时,我们无法控制所需的内存分配。

取决于你的意思 – 可能被分配用于执行调用调用的内存,还是由调用的调用创建的内存?

我们完全控制分配用于执行调用的内存 – 这是由JIT创建的内存,用于编译所涉及的方法,以及堆栈执行调用所需的内存。 JIT编译存储器在CER准备期间分配; 如果失败,整个CER永远不会被执行。 CER准备工作还计算CER执行的静态调用图中需要多少堆栈空间,如果没有足够的堆栈,则中止CER准备。

巧合的是,这涉及到任何try-catch-finally框架的堆栈空间准备,甚至是嵌套的try-catch-finally框架,这些框架恰好定义并参与CER。 在CER中嵌套try-catch-finally是完全合理的,因为JIT可以计算记录try-catch-finally上下文所需的堆栈内存量,如果需要太多则中止CER准备工作。

调用本身可以在.net堆之外进行一些内存分配; 我很惊讶在CER内部允许本地通话。

如果您的意思是由调用的调用执行本机内存分配,那么这也不是CER的问题。 本机内存分配成功或返回状态代码。 OOM不是由本机内存分配生成的。 如果本机分配失败,可能是我正在调用的本机API通过返回状态代码或空指针来处理它。 这个电话仍然是确定性的。 唯一的副作用是,由于内存压力增加,可能导致后续托管分配失败。 但是 ,如果我们要么从不执行分配,要么可以确定地处理失败的托管分配,那么它仍然不是问题。

因此,CER中唯一不好的分配是托管分配,因为它可能导致“异步”OOMexception。 所以现在的问题是如何确定性地处理CER内部失败的托管分配。

但这完全有可能。 CER可以嵌套try-catch-finally块。 CER中所有的调用,以及CER所需的所有堆栈空间,即使是最终在CER中记录嵌套try-finally的上下文,也可以在整个CER的准备过程中确定性地计算,在我的任何代码实际执行之前。

只要CER准备好处理失败的分配,就可以在CER内部执行管理分配。

首先,这是破碎的代码:

 SecPkgInfo info; SecurityStatus status = SecurityStatus.InternalError; SecurityStatus freeStatus; IntPtr rawInfoPtr; rawInfoPtr = new IntPtr(); info = new SecPkgInfo(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr ); if ( rawInfoPtr != IntPtr.Zero ) { if ( status == SecurityStatus.OK ) { // *** BWOOOP **** BWOOOP *** // This performs allocations as it makes room for the strings contained // in the SecPkgInfo class. That means that we're performing managed // allocation inside of a CER. This CER is broken and may cause a leak because // it never calls FreeContextBuffer if an OOM is caused by the Marshal. Marshal.PtrToStructure( rawInfoPtr, info ); } freeStatus = NativeMethods.FreeContextBuffer( rawInfoPtr ); } } 

由于try-catch-finally可以嵌套,并且在CER准备期间预先计算嵌套try-catch-finally所需的任何额外堆栈空间,我们可以在CER的main中使用try-finally,最后确保我们的FreeContextBuffer永远不会泄露:

 SecPkgInfo info; SecurityStatus status = SecurityStatus.InternalError; SecurityStatus freeStatus; IntPtr rawInfoPtr; rawInfoPtr = new IntPtr(); info = new SecPkgInfo(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr ); if ( rawInfoPtr != IntPtr.Zero ) { try { if ( status == SecurityStatus.OK ) { // This may fail but the finally will make sure we always free the native pointer. Marshal.PtrToStructure( rawInfoPtr, info ); } } finally { freeStatus = NativeMethods.FreeContextBuffer( rawInfoPtr ); } } } 

我还整理了一个演示程序,可在http://www.antiduh.com/tests/LeakTest.zip上找到 。 它有一个跟踪分配的小型自定义本机DLL,以及一个调用该DLL的托管应用程序。 它显示了使用嵌套try-finally的CER如何能够确定性地释放非托管资源,即使部分CER导致OOMexception。