.Net Finalizer Order / Семантика в Esent и Ravendb - PullRequest
1 голос
/ 21 мая 2010

Помоги мне понять. Я прочитал это

«Время и порядок исполнения финализаторов не могут быть предсказаны или предопределены»

Правильно?

Однако, глядя на исходный код 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
   {
   }
 }
}

Класс API (который принадлежит Managed Esent), который предположительно обрабатывает собственные ресурсы, предположительно, с помощью SafeHandle?

Так что, если я правильно понимаю, собственные дескрипторы SafeHandle могут быть завершены до TransactionStorage, что может привести к нежелательным эффектам, возможно, почему 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);  
  }  
}

Управляемое Esent (NativeMEthods.cs) выглядит следующим образом (используя Ints против IntPtrs?):

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

Правильно ли обрабатывается завершение / удаление Managed Esent, и, во-вторых, RavenDB обрабатывает финализатор правильным способом или компенсирует Managed Esent?

Ответы [ 3 ]

7 голосов
/ 21 мая 2010

Использование SafeHandles с ресурсами ESENT очень сложно и опасно, поэтому я решил не делать этого. Есть две основные проблемы:

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

Если JET_TABLEID заключен в SafeHandle, мы должны знать, что вызов JetRollback () (который даже не принимает tableid в качестве параметра) закрыл таблицу, поэтому финализатор не может закрыть таблицу. Еще одна рука, если мы возьмем путь фиксации, финализатор должен закрыть таблицу.

Если JET_SESID также является SafeHandle, тогда мы должны отслеживать порядок выполнения финализаторов. Если JET_SESID уже завершен, мы не можем закрыть JET_TABLEID.

Отслеживание отношений между экземплярами, сеансом, таблицами и транзакциями, а затем правильное выполнение действий в финализаторе было бы невероятно трудным, и лучше всего было бы сделать это с использованием более сложной объектной модели, чем обеспечивает ManagedEsent.

Однако мы можем использовать SafeHandle с JET_INSTANCE, потому что нет API, которые могли бы неявно закрывать экземпляр. Оболочка Instance () делает это. Почему бы JET_INSTANCE не быть SafeHandle? Существуют случаи, когда приложения хотят завершить работу, не прерывая ESENT вообще - завершение может быть медленным, а с длительными транзакциями вы фактически не потеряете никакой информации, если просто выйдете из программы - восстановление базы данных будет выполнено автоматически в JetInit.

Что касается порядка финализаторов, я считаю, что критические финализаторы (например, SafeHandles) всегда запускаются после запуска всех обычных финализаторов.

2 голосов
/ 21 мая 2010

Здесь много чего не так. Мои комментарии:

  1. Финализаторы должны предполагать, что финализаторы уже запущены для каждого другого класса. Это может включать, скажем, классы, записывающие Trace.WriteLine операторов в файл журнала.
  2. Предложение catch может или не может работать для защиты от уже завершенных классов. Обычно финализаторы не выдают, даже если им не удается освободить свои неуправляемые ресурсы (поскольку это обычно приводит к сбою всей программы).
  3. Управляемый ESENT, безусловно, должен использовать SafeHandle с, по причинам, описанным здесь (а именно, предотвращение утечек при возникновении асинхронных исключений и защита от повторного использования ручки). Я очень удивлен, что Лорион не использует здесь лучшие практики.
  4. Идентификаторы сеанса ESENT IntPtr с, но их идентификаторы БД uint с . Однако оба они должны быть заключены в класс SafeHandle.

Короче говоря, похоже, что оба проекта не обрабатывают финализацию или обрабатывают правильно. Это трудная проблема, как отмечается в моей первой статье CodeProject .

Перед выпуском управляемого ESENT я начал работать над своими собственными оболочками для ESENT API, намереваясь выпустить его как OSS. После нескольких дискуссий с Лорионом я решил не делать этого, поскольку Microsoft тоже собиралась это сделать. Код, который у меня есть, не является полным набором функций, но он действительно использует SafeHandle s, если вам интересно.

Также обратите внимание, что ESENT не имеет обратной совместимости при записи (это приведет к обновлению, поэтому БД с XP, открытая на Win7, никогда не сможет снова считываться XP). Это хороший выбор, если все данные являются локальными для машины, но их нельзя использовать, если вам нужно скопировать базу данных на другие машины.

1 голос
/ 23 мая 2010

Я просто хотел бы кое-что прояснить относительно SafeHandles.SafeHandles - это не только финализуемые объекты, они имеют более строгую гарантию финализации, называемую критическим финализацией. Критический финализатор гарантированно запускается после того, как все обычные финализаторы уже запущены.По этой причине вы гарантируете, что SafeHandle - это последняя вещь, которая будет завершена.

Критическое завершение - одна из функций, которая делает SafeHandle Safe:)

Эта ссылка содержит more информация.Надеюсь, это поможет.

...