none
ESENT.DLL недопустимая операция RRS feed

  • Вопрос

  • Мое приложение использует базу данных, построенную движком ESE.

    При интенсивной работе с базой данных (очень интенсивная запись и чтение) в мультипоточном режиме, в ESENT.DLL происходит необработанное исключение 0xC0000005 (по адресу 0x4B1B7C01). Исключение происходит в потоке, который создается не мной (во-первых потому что все мои потоки обраблены _try _except, а во-вторых, потому что я веду лог всех созданных мной потоков и при обработке необработанного исключения сверяю, из какого потока произошло это исключение).

    Если запустить приложение из-под отладчика, то за минуту в окне Output наваливается около миллиона обработанных First-Chance exception, но все они обработаны. Видимо, из-за этого, приложение начинает работать в несколько раз медленнее и интенсивность записи / чтения в /из базы несколько падает. В таком режиме повторить проблему не удалось ни разу.

    Если уменьшить число потоков, работающих с базой данных, то проблема также не появляется.

    В чем может быть проблема и как от нее избавиться? Система: server 2003, версия esent.dll - 5.2.3790.4674 (но тоже самое происходит и с версией 5.2.3790.3959).

    20 августа 2012 г. 16:51

Ответы

  • Вопрос решен.
    Проблема оказалась внутри функции  JetSetCurrentIndex(session,eventstable,NULL), которая при определенных условиях выполняет недопустимую операцию при установке именованного первичного индекса с передачей NULL вместо имени индекса.

    Если кому интересно, могу выложить полный набор условий, при которых это происходит.

    Решил изменением условий, в которых вызывается функция.

    • Помечено в качестве ответа alex_alv 23 августа 2012 г. 14:56
    • Изменено alex_alv 24 августа 2012 г. 5:07
    • Снята пометка об ответе alex_alv 24 августа 2012 г. 5:08
    • Помечено в качестве ответа alex_alv 24 августа 2012 г. 5:08
    23 августа 2012 г. 14:29

Все ответы

  • Дополнительная информация по проблеме.

    Отписать дампы с проблемой никак не получается.

    Если установлен Visual Studio в качестве Just-In-Time дебагера, то после того, как проблема возникла, появляется окно с вопросом о том - запустить ли отладчик. Пока висит это окно, приложение продолжает работать и запись / чтение в базу продолжается (кроме нескольких читающих потоков, которые приостановлены). Как только я нажимаю Да (запустить отладчик), то программа исчезает без следа (просто закрывается окно и исчезает процесс), а отладчик говорит, что он не может ничего отладить.

    Если установить доктора ватсона, то после проблемы он закрывает программу (процесс исчезает без всяких сообщений). Появляется процесс dw20.exe, который на 100% загружает одно из ядер и работает в течение 10 минут. В это время все ресурсы, которые открывало приложение, остаются занятыми (файлы, сокеты и т. д.). Через 10 минут процесс dw20.exe также исчезает без следов, ресурсы освобождаются и я могу повторно запустить программу. Никаких дампов не создается. Никаких окно не высвечивается. Процесс просто исчезает и все.

    В полном тупике. Все указывает на то, что проблема в коде библиотеки ESENT.DLL. Может быть существует обновление, которое решает данную проблему?

    22 августа 2012 г. 6:21
  • Вопрос решен.
    Проблема оказалась внутри функции  JetSetCurrentIndex(session,eventstable,NULL), которая при определенных условиях выполняет недопустимую операцию при установке именованного первичного индекса с передачей NULL вместо имени индекса.

    Если кому интересно, могу выложить полный набор условий, при которых это происходит.

    Решил изменением условий, в которых вызывается функция.

    • Помечено в качестве ответа alex_alv 23 августа 2012 г. 14:56
    • Изменено alex_alv 24 августа 2012 г. 5:07
    • Снята пометка об ответе alex_alv 24 августа 2012 г. 5:08
    • Помечено в качестве ответа alex_alv 24 августа 2012 г. 5:08
    23 августа 2012 г. 14:29
  • Спасибо что выложили решение проблемы. Есил возможно, то условия возникновения были бы тоже очень полезны для тех кто может столкнуться с подобным.


    Для связи [mail]

    24 августа 2012 г. 11:25
  • Условия - очень длинные. И точно не понятно, какое конкретно условие привело к проблеме. Поэтому, опишу все, что может относиться к делу.

    Во-первых, я обманул, сказав, что проблема - не в моем потоке. Оказалось - в моем.

    Мое понимание, почему обрамление _try _except не перехватывало данную ситуацию. Как я понял, некоторые исключения обрабатываются внутри ESE, и эти обработчики завершают приложение с необработанным исключением в обход внешних обертывающих _try _except. Это сделано для того, чтобы не нарушить структуру базы, и это поведение может быть изменено с помощью параметра JET_paramExceptionAction (см. фнукцию JetSetSystemParameter). Я этого не делал, так как ситуация наблюдалась на рабочей базе данных, и ее разрушение мне было не очень нужно.

    24 августа 2012 г. 12:54
  • Почему мой обработчик необработанных исключений не сообразил, что поток - мой. Дело в том, что хоть у меня и был написан движок, который отслеживает все создаваемые мной потоки, он был отключен. Я просто забыл его включить.

    Мое понимание, почему доктор ватсон и отладчики не могли отписать дамп. В данной ситуации исключение происходило в нескольких потоках практически одновременно (на разных ядрах). Пока работал обработчик необработанных исключений, параллельно продолжали работать остальные мои потоки, которые также вызывали это же исключение. Вероятно, это приводило к проблеме со всеми дебагерами / отписывателями дампов. Для выхода из ситуации пришлось написать свои функции (типа CheckPoint), которые отслеживали прохождение некоторых контрольных точек во всех потоках, и при обрушении мой обработчик необработанных исключений записывал в креш-файл, в каком потоке произошло исключение, и какой последний чек-поинт был пройден этим потоком.

    24 августа 2012 г. 12:54
  • Далее опишу примерно структуру базы / приложения (что имеет отношение к ситуации).

    Имеется огромная таблица (около миллиарда записей), и в нее постоянно в отдельном потоке заносятся новые данные непрерывно (с разной скоростью, зависящей от времени суток, но в среднем - около сотни новых записей в секунду). В этой таблице есть особые записи, в которых содержатся набор неких показателей относительно предыдущей аналогичной записи (эти записи проиндексированы отдельным индексом).

    Есть функция, которая вычисляет сумму этих показателей от начала таблицы до выбранного (в параметрах) момента. Чтобы она работала быстро, заведен дополнительный столбец, в который она записывает свои результаты, и при следующем вызове - не переситывает все снова, а просто считывает из данного столбца.

    24 августа 2012 г. 12:54
  • Функция - рекурсивная. Она работает так:

    Ей в параметрах передается, до какой записи нужно все сложить. Она смотрит, не сохранено ли в этой записи уже ранее просчитанные результаты. Если да - просто считывает и возвращает. Если нет - находит предыдущую подобную запись с показателями, вызывает саму себя, чтобы просчитать сумму показателей для этой предыдущей записия, и к результату прибавляет показатели из переданной в параметрах записи. Результат - сохраняет в отдельный столбец и возвращает на выходе.


    • Изменено alex_alv 24 августа 2012 г. 13:08 опечатка
    24 августа 2012 г. 12:55
  • Функция вызывается в рамках транзакции. Внутри себя она открывает новую вложенную транзакцию. На время рекурсии она закрывает открытую собой транзакцию, делает рекурсию, после чего снова открывает транзакцию (сделано для того, чтобы не превысить лимит вложенных транзакций).

    При записи просчитанных результатов (для ускорения просчета в будущем) в базу, используется флаг JET_prepReplaceNoLock, так как запись не является особо важной (если будет ошибка, то и ничего страшного). При ошибке записи, открытая транзакция закрывается с помощью JetRollback. По моей ошибке откат транзакции происходил с флагом JET_bitRollbackAll, что закрывало не только транзакцию, открытую рекурсивной функцией, но и все транзакции, внутри которых это происходит. Это - моя ошибка (ошибка копи-пейста). Проблема устранилась именно после того, как заменил JET_bitRollbackAll на 0.

    24 августа 2012 г. 13:00
  • Теперь, в каком месте происходила проблема.

    Сразу после вызова этой функции, потоки вызывали JetSetCurrentIndex(session,eventstable,NULL)

    Если перед этим рекурсивная функция произвела неуспешную попытку записать результаты в базу и откатила все транзакции, то JetSetCurrentIndex в этом месте вызывалась уже вне транзакций. В принципе, это не должно приводить к исключению, но приводит.

     

    Итог. Проблема происходит, если несколько потоков одновременно пытаются записать в одно и тоже место в базе с флагом JET_prepReplaceNoLock. Запись у одного - успешная, у остальных завершается с ошибкой. Если после этого потоки с ошибками записи откатывают транзакции с флагом RollbackAll и сразу после этого вызывают функцию JetSetCurrentIndex(session,eventstable,NULL) для установки именованного первичного индекса, все эти потоки рушатся с недопустимой операцией. Возврата из JetSetCurrentIndex не происходит ни в одном из них.

     

    Надеюсь, описал максимально полно всю картину.

    24 августа 2012 г. 13:00