Esent和Ravendb中的.Net Finalizer Order/Semantics

1 c# clr garbage-collection esent ravendb

帮帮我理解 我读过这个

"终结者的执行时间和顺序无法预测或预先确定"

正确?

但是看看RavenDB源代码TransactionStorage.cs,我看到了这一点

~TransactionalStorage()
{
try
{
 Trace.WriteLine(
  "Disposing esent resources from finalizer! You should call TransactionalStorage.Dispose() instead!");
 Api.JetTerm2(instance, TermGrbit.Abrupt);
}
catch (Exception exception)
{
  try
  {
   Trace.WriteLine("Failed to dispose esent instance from finalizer because: " + exception);
   }
   catch
   {
   }
 }
}
Run Code Online (Sandbox Code Playgroud)

API类(属于Managed Esent)可能会使用SafeHandle对本机资源进行处理?

因此,如果我理解正确,可以在TransactionStorage之前完成本机句柄SafeHandle,这可能会产生不良影响,也许为什么Ayende在此处添加了catch all子句?

实际上潜入Esent代码,它不使用SafeHandles.

根据CLR通过C#这是危险的吗?

    internal static class SomeType {  
   [DllImport("Kernel32", CharSet=CharSet.Unicode, EntryPoint="CreateEvent")]  

 // This prototype is not robust  
   private static extern IntPtr CreateEventBad( 
      IntPtr pSecurityAttributes, Boolean manualReset, Boolean initialState, String name);  


 // This prototype is robust  
  [DllImport("Kernel32", CharSet=CharSet.Unicode, EntryPoint="CreateEvent")]  
  private static extern SafeWaitHandle CreateEventGood( 
     IntPtr pSecurityAttributes, Boolean manualReset, Boolean initialState, String name)

  public static void SomeMethod() {  
     IntPtr         handle = CreateEventBad(IntPtr.Zero, false, false, null);  
     SafeWaitHandle swh    = CreateEventGood(IntPtr.Zero, false, false, null);  
  }  
}
Run Code Online (Sandbox Code Playgroud)

Managed Esent(NativeMEthods.cs)看起来像这样(使用Ints vs IntPtrs?):

  [DllImport(EsentDll, CharSet = EsentCharSet, ExactSpelling = true)]
    public static extern int JetCreateDatabase(IntPtr sesid, string szFilename, string szConnect, out uint dbid, uint grbit);
Run Code Online (Sandbox Code Playgroud)

Managed Esent正在处理finalization/dispoal正确的方法,其次是RavenDB处理终结器的纠正方式还是补偿Managed Esent?

Lau*_*all 7

使用带有ESENT资源的SafeHandles是非常复杂和危险的,所以我决定不这样做.有两个主要问题:

  1. 与Win32句柄不同,ESENT句柄是相互关联的,因此关闭一个句柄将隐式关闭其他句柄.
  2. 关闭已经关闭的ESENT句柄是不安全的.

首先,有几个案例需要考虑:

  • JetRollback将关闭在事务内部打开的所有表,但JetCommit不会.
  • JetEndSession将关闭会话打开的所有表和数据库.
  • JetTerm可以关闭实例打开的所有会话,表和数据库.

现在JET_SESID或JET_TABLEID实际上是一个指向内部结构的指针(我们尝试通过一个句柄表进行间接,但发现它太慢了,特别是当被多个线程使用时).这意味着一旦资源关闭,就可以重用内存.再次释放资源可能会释放另一个线程的资源; 就像双重释放指针一样.

这使得这个代码在最终确定方面具有惊人的挑战性:

void Foo(JET_SESID sesid, JET_DBID dbid)
{
    JET_TABLEID tableid;

    Api.JetBeginTransaction(sesid);
    Api.JetOpenTable(sesid, dbid, "table", null, 0, OpenTableGrbit.None, out tableid);
    // do something...
    if (somethingFailed)
    {
        Api.JetRollback(sesid, RollbackTransactionGrbit.None);
    }
    else
    {
        Api.JetCommitTransaction(sesid, CommitTransactionGrbit.None);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果JET_TABLEID包装在SafeHandle中,我们必须知道JetRollback()调用(甚至不将tableid作为参数)关闭了表,因此终结器无法关闭表.另一方面,如果我们采用提交路径,那么终结器应该关闭表.

如果JET_SESID也是一个SafeHandle,那么我们必须跟踪终结器的执行顺序.如果JET_SESID已经完成,那么我们就无法关闭JET_TABLEID.

跟踪实例,会话,表和事务之间的关系,然后在终结器中做正确的事情将是非常困难的,并且最好使用比ManagedEsent提供的更复杂的对象模型.

但是,我们可以使用带有JET_INSTANCE的SafeHandle,因为没有可以隐式关闭实例的API.Instance()包装器就是这样做的.为什么不让JET_INSTANCE成为SafeHandle?在某些情况下,应用程序想要退出而不终止ESENT - 终止可能很慢,如果您只是退出程序,使用持久事务您实际上不会丢失任何信息 - 数据库恢复将自动在JetInit上运行.

至于终结器订单,我相信关键终结器(例如SafeHandles)总是在所有正常终结器运行后运行.