среда, 12 ноября 2008 г.

Mono

Думаю уже нет людей не слышавших о проекте Mono. Замечательный проект, сама идея. Ведь это путь как раз к реальной кроссплатформенности .NET, а не только win серии, жаль правда, что за реализацию взялась не сама Microsoft. Я конечно не говорю о том, что OpenSource бесплатные проекты это совсем плохо, но качество зачастую не высшее, не то которым обладает проприетарное ПО. За 2 летний опыт работы с использованием C# .NET я не припомню ни одного бага в самой системе. Далее о ситуации с Mono...

Недавно возникла необходимость в переносе одной моей библиотеки на платформу Linux. Библеотека была на C#, соответственно первая мысль - "Mono". Да тут еще такое событие релиз 2ой версии Mono. Поддержка C# 3.0, что совсем замечательно. Бинарей не было, качаем, ставим из репозитория.
Компилим... и... да! с первого раза, без проблем, скомпилилось ) Ну собственно по ходу исследования успешности переезда на Mono выяснились следующие вещи...


  1. Библиотека активно использует SerialPort, он самый первый и начал сообщать о проблемах. От девайса ничего не приходило, он не отвечал... В чем проблема... Удалось выяснить тут. Оказывается в Mono реализации этого класса подписка на событие прихода данных с порта не работает... Искуственно эта проблема решилась конечно, но неприятно...

  2. 2 потока в одном домене приложения...Один валится с эксепшеном...Что произойдет ?.. Нет все приложение не свалится... Второй поток в Mono прекрасно продолжает работать ) Это я запостил в мэил рассылку Mono...Ответ пришел ОЧЕНЬ быстро...Что классно и за что огромная им благодарность. Это был баг, который правится патчем к исходникам...

  3. Оказывается в документе ссылка на который есть выше описаны не все проблемы с SerialPort в Mono. Он также игнорирует WriteTimeout...Проблема тоже вполне решаемая...но это уже 3 проблема по переносу...


Итого: все же удалось заставить библиотеку работать полноценно под обеими платформами, что несомненный плюс в сторону Mono. Проблем с этим замечательным проектом еще море, но... согласитесь перенос с одной платформы на друю и всего лишь 3 вещи на которых произошла остановка, это уже классно...Очень благодарен этим ребятам.

пятница, 29 февраля 2008 г.

NHibernate unexpected updates

Часто начиная работать над одной проблемой, приходишь к совершенно неожиданным местам кода, которые казалось бы никак не связаны целью нынешнего «расследования». На примере одного такого своего расследования я расскажу о том как в некоторых случаях ведет себя NHibernate.
Проблема заключалась в deadlock’ах при одновременном обращении пользователей к некоторым сущностям. Читая код, я обнаружил, что посредством NHibernate у нас идет самое простое обращение к базе, получение объекта и вывод некоторых его полей. Все это по логике вещей должно вылиться в один SELECT запрос к базе, который не предвещает никаких блокировок. Что собственно и подтверждала замечательная программа Ayende.NHibernateQueryAnalyzer, которая позволяет видеть как NHibernate преобразует встроенный язык запросов hql в требуемый диалект TSQL.
Но посмотрим, что происходит на практике, ведь есть возможность настроить NHibernate, так чтобы он показывал все запросы к базе которые он выполняет. Для этого необходимо в конфигурационный файл добавить следующую строку:

<property name="show_sql">true</property>

Теперь можно смело проводить эксперименты. Попробуем промоделировать самую простейшую ситуацию, которая у нас может получиться. Проект на ASP.NET и на каждый Request-Response у нас создается одна сессия и одна транзакция. Т.о. простейший случай выглядит приблизительно так:

string hqlQuery = "from EventMessage entity where entity.ID = :entityID";
ISession session = NHibernateSessionManager
.Instance
.GetSessionFor("hibernate.cfg.xml");
using (session.BeginTransaction())
{
EventMessage eventMessage = session.CreateQuery(hqlQuery)
.SetParameter("entityID", "123")
.UniqueResult<EventMessage>();
session.Transaction.Commit();
}


В результате, по запуску этого кода, при правильном mapping’е и других факторах мы должны получить один или несколько SELECT запросов к базе. Вот тут моё скромное investigation и начало давайть интересные результаты. На экране я увидел не один запрос, а сразу несколько запросов получения, как к примеру данном случае сущностей EventMessage, следом за ними, что самое интересное следовали UPDATE запросы на все полученные сущности EventMessage. Вот он deadlock, транзакции просто перекрывали друг друга с таким количеством UPDATE запросов.
Встает 2 вопроса:
  1. Откуда столько SELECT запросов на эту сущность, если требуется только один экзэмпляр.
  2. Почему происходит UPDATE полученных сущностей.
Довольно не сложно ответить на первый вопрос, просто просмотрев mapping файлы на наличие lazy="false", что успешно и было проведено.
Второй вопрос гораздо интереснее, с чего NHibernate решил делать UPDATE ?
В замечательной книге “NHibernate in Action” можно найти следующие слова:
«We have taken advantage of a NHibernate feature called transparent persistence: This feature saves us
the effort of explicitly asking NHibernate to update the database when we modify the state of an object
inside a transaction.»

Т.е. такое поведение называется “transparent persistence” и все вроде как было бы нормально, если бы мы действительно изменяли объект. NHibernate на самом деле для определения состояния объекта хранит его копию сразу после получения и сравнивает её с тем экзэмпляром, который попал к нам в руки и, если находит несовпадения, то делает UPDATE. Конечно, можно заставить NHibernate думать, что объект не менялся:

NHibernate.Impl.SessionImpl impSession =(NHibernate.Impl.SessionImpl)session;
NHibernate.Impl.EntityEntry eEntry = impSession.GetEntry(eventMessage);
eEntry.Status = NHibernate.Impl.Status.Gone;

Но такой хак, конечно не будет выходом. Нужно посмотреть, что именно «изменяется».
Сделать можно это несколькими способами: зарегестрировать на сессию свою реализацию интерфейся IIntercepter или на основе того же кода который приведен выше сравнить два массива

eEntry.Persister.GetPropertyValues(eventMessage)

и

eEntry.LoadedState

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

В моем случае выяснилась следующая интересная ситуация. Объект содержал несколько полей типа DateTime, в БД у некоторых строк колонки с этими датами содержали NULL.
NHibernate получая получая из БД значения NULL для значимого типа передавал в сеттер значение default(DateTime). В результате в эти свойства содержали значение 01.01.0001, которое конечно же отличалось от того NULL, который сохранил для себя NHibernate. И все это в итоге приводило к «unexpected update». Способ борьбы очень прост, раз уж эти даты могут быть NULL, то и тип у них должен быть Nullable…т.е. DateTime?

Вот так пара простых промашек с мэппингом привели к deadlock’ам. Будьте внимательны. Удачи.