约束执行区域是C#/ .Net的一项功能,允许开发人员尝试提升“三巨头”。代码的关键区域之外的异常--OutOfMemory,StackOverflow和ThreadAbort。
CERs通过推迟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
,我们&#39}重新假设我的NativeMethods被标记为类似,以便VM在准备CER时可以正确地考虑它们。
所以尽管如此,你如何处理分配必须在CER内部发生的情况?分配违反规则,因为它很可能获得OutOfMemoryException。
在查询原生API(SSPI' s 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中唯一不好的分配是托管分配,因为它可能导致异步' OOM例外。所以现在的问题是如何确定性地处理CER内部失败的托管分配。
但这完全有可能。 CER可以嵌套try-catch-finally块。 CER中的所有调用,CER中所需的所有堆栈空间,即使是最终在CER内部记录嵌套try-finally的上下文,也可以在准备期间确定性地计算在我的任何代码实际执行之前,整个CER。
答案 0 :(得分:9)
只要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的主要内部使用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导致OOM异常。