четверг, 19 ноября 2009 г.

Злые баги. Или почему неприятности приходят в самый неподходящий момент

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

Начну по-порядку. В последнее время наша команда снова вернулась к проекту, который мы закончили несколько лет назад. Тогда реализация всей серверной части была сделана на С++, ядро системы оборачивалось в "управляемую" (managed) оболочку и использовалась в управляемом коде. Клиент-серверное взаимодействие реализовывалось на .Net Remoting, а два типа клиентских приложения были реализованы на .Net с использованием технологии Windows Forms. Все это было реализовано под .Net 1.1, и, естественно, новую версию мы захотели перевести на .Net 3.5, благо никаких особых проблем это не предвещало, но давало ряд преимуществ, главным из которых было повторное использование кода и архитектурных решений (за несколько лет у нас накопилось достаточное количество компонентов, реализованных под .Net 3.5), да и вообще, очень  сложно переходить с LINQ 2 Objects обратно на .Net 1.1, где нет даже Nullable-типов и типизированных коллекций.

Взаимодействие управляемого и неуправляемого кода попило немало крови в исходной версии проекта (под .Net Framework 1.1 есть свои особенности, связанные с необходимостью вручную вызывать функции __crt_dll_initialize() и __crt_dll_terminate()) и оставалось наиболее рисковой составляющей перехода на новую версию .Net Framework. Дело, к тому же, осложнялось тем, что мы приняли решение отказаться от использования старых драйверов взаимодействия с СУБД PostgreSql (libpqxx), в пользу использования оболочки, которая бы позволила использовать ADO.NET провайдера доступа к БД (в данном случае, Npgsql) из native-кода (механизм использования управляемых библиотек из неуправляемого кода изложены в моей статье "Взаимодействие управляемого и неуправляемого кода", RSDN Magazine, 3-2008). Сам перевод на новую технологию доступа к данным не занял особо много времени, благодаря четкому выделению слоя доступа к данным, но само взаимодействие управляемого и неуправляемого вызывало наибольшее количество беспокойство и, как оказалось, не зря.

Итак, работа над проектом двигалась вперед, мы показали людям сырую альфу, чтобы скорректировать направление нашего движения и пообещали предоставить бету через две недели. За пару дней до контрольного срока я заметил, что серверные части системы (которые реализованы в виде служб Windows NT, но имеют также и тестовые консольные версии), начали "падать" с какой-то ошибкой при завершении работы, причем "падать" стали не в момент завершения, а уже после него, выдавая такое замечательное сообщение: «Исключение unknown software exception (0xc0020001) в приложении по адресу 0x7c81eb33».

Сложность диагностики этой ошибки была вызвана тем, что я не смог определить момент, когда эта проблема начала проявляться (примерно в это время я начал настраивать дополнительную конфигурацию сборки и просто не понял, что я зделал не так). Кроме того, полная перекомпиляция проектов занимала более получаса, а время здорово поджимало.

Судя по симптомам, ошибка происходила где-то при освобождении статических объектов. Первая мысль пала на синглтоны от Loki, в частности на использование стратегии управления временем жизни Phoenix (если кто не знаком с синглтонами из библиотеки Loki (автор Андрей Александреску), то идея заключается в использовании паттерна "стратегия" для задания таких параметров, как способ выделения памяти, управление временем жизни и управление параллелизмом). Стратегия управления временем жизни Phoenix как раз предназначена для решения известной проблемы С++, связанной с неопределенным порядком создания и уничтожения статических объектов в различных единицах трансляции; например, при обращении в деструкторе одного синглтона к другому синглтону поведение будет не определенным.

При поиске ошибок каждый разработчик придерживается ряда правил, основанных человеческом опыте и отлично озвученных Джоном Роббинсоном:

Шаг 1. Воспроизведите ошибку

Шаг 2. Опишите ошибку

Шаг 3. Всегда предполагайте, что ошибка ваша

Шаг 4. Разделяйте и властвуйте

Шаг 5. Думайте творчески

Шаг 6. Используйте инструменты

Шаг 7. Начните тяжелую отладку

Шаг 8. Убедитесь, что ошибка исправлена

Шаг 9. Извлеките урок и поделитесь им с другими

Первые 8 шагов в данном случае не особо интересны, поэтому я сразу же перейду к девятому.

Код ошибки 0xc0020001 определен следующим образом: BOOTUP_EXCEPTION_COMPLUS и может возникать при попытке достучаться к CLR до ее инициализации или после ее деинициализации. Как известно, в проекте C++/CLI любой cpp-файл (если не оговорено обратное) компилируется с ключем /clr, что позволяет создавать «смешанные» (mixed-mode) сборки, в которых сосуществует управляемый и неуправляемый код. Теперь, если объект некоторого класса будет сохранен в статической переменной, то время жизни этого объекта будет определяться моментом выгрузки этого модуля, что будет соответствовать моменту непосредственно перед завершением приложения. При этом совершенно неизвестно, будет ли «жива» CLR или нет, т.к. порядок выгрузки различных модулей неопределен (именно поэтому, в течение какого-то времени эта ошибка у меня не проявлялась).

Кроме этого, жизнь может осложняться следующим моментом. Предположим у вас есть заголовочный файл, в котором реализована (не только объявлена, но и реализована) некоторая функциональность (например, класс Foo). Тогда, при включении из cpp-файла (который компилируется с ключом /clr) этого заголовочного файла, класс Foo будет скомпилирован в mixed-mode, что не позволит объектам этого класса располагаться в статической памяти (точнее, располагаться-то позволит, а вот с освобождением этой памяти могут быть проблемы).

У этой проблемы есть несколько путей решения, каждый со своими преимуществами и недостатками.

Первое решение заключается в том, чтобы для каждого cpp-файла убрать ключ /clr, в результате, все классы будут компилироваться, как native-классы, и это позволит обращаться к их деструкторам уже после выгрузки CLR.

Второе решение легче показать на примере кода:

#pragma managed(push, off)

#include "Foo.h"

#pragma managed(pop)

Если заголовочный файл включается в cpp-файл, который, в свою очередь компилируется с ключом /clr, то класс (определенный в этом заголовочном файле) также будет также компилироваться в mixed-mode, что не позволит обращаться к его деструктору в момент завершения приложения. Обрамление заголовочного файла в #pragma managed приведет к компиляции класса, определенного в этом заголовочном файле в native-mode.

Третье решение связано с применением синглтонов от Loki. Как я уже писал выше, этот синглтон (как и многое другое в этой библиотеке) реализован с помощью стратегий, которые могут задавать некоторое поведение, одним из которых является управление временем жизни синглтона. Одной из существующих стратегий управления временем жизни является стратегия NoDestroy, при которой память, выделенная под синглтон, не будет освобождаться вовсе.

Самое интересное в этой истории то, что на самом деле это никакой не «злой баг», эта ситуация характеризуется как by design и описана в документе KB956195, смысл которого как раз и сводится к тому, что я описал выше.

Перечень ссылок по теме ошибки 0xc0020001:

1.     C++/CLI application throws 0xC0020001 on exit. http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=365110

2.     Static variable in native method causes exception c0020001 during process exit. http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=336844

3.     mixed-mode application (C++/CLI) throws unhandled exception '0xC0020001: The string binding is invalid.' on application exit. http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=218663

4.     KB 956195. C++/CLI application crashes with 0xc0020001. http://support.microsoft.com/kb/956195

5.     An intereseting issue in mixed-mode application. http://blogs.msdn.com/ravi_kumar/archive/2007/12/30/an-intereseting-issue-in-mixed-mode-application.aspx

P.S. Интересно, что на «синглтоне Мейерса» проблема воспроизводится не всегда, а вот применение синглтона от Loki (со стратегией управления временем жизни не NoDestory), стабильно приводит к выдаче сообщения об ошибке 0xc0020001 в момент закрытия приложения.

P.S.S. Здесь расположен тестовый проект, который стабильно воспроизводит описанное поведение. Проверено на нескольких компьютерах с Windows XP SP2, VS2008 SP1 и на одном Windows 7, VS2008 SP1.

четверг, 12 ноября 2009 г.

“Балдеющие от адреналина и зомбированные шаблонами” на русском языке

Издательство Символ-Плюс (http://www.symbol.ru) работает над переводом новой книги Тома Демарко, Тима Листера (и еще четырех авторов) Adrenaline Junkies and Template Zombies, которая вышла в начале этого года (см. на amazon.com здесь).

Книга, действительно, замечательная, более подробно мое мнение можно узнать по одному из предыдущих моих постов (http://sergeyteplyakov.blogspot.com/2009/09/adrenaline-junkies-and-template-zombies.html).

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

Содержание

Отзывы на книгу “Балдеющие от адреналина и зомбированные шаблонами”

О Гильдии

Введение

1. Балдеющие от адреналина

2. На старт!

3. Дохлая рыба

4. Собрания для оваций

5. Проектный нянь

6. Отраженная боль

7. Manana

8. Зрительный контакт

9. Управление по кольцу настроения

10. Правоверный

11. Сдай душу в аренду

12. Лемминговый цикл разработки систем

13. Без скамейки запасных

14. Очное общение

15. Я дал тебе резец – почему ты не Микеланджело?

16. Приборные панели

17. Бесконечные препирательства

18. Юные щенки и старые псы

19. Кинокритики

20. С кого спрос?

Интерлюдия: проектный жаргон

21. «Советский» стиль

22. Естественный авторитет

23. Слишком тихий офис

24. Аут

25. Молчание – знак согласия

26. Соломенное чучело

27. Притворная срочность

28. Время играет против вас

29. Льюис и Кларк

30. Карандашный огрызок

31. Ритм

32. Гадание по сверхурочным

33. Вечер покера

34. Ложный промежуточный контроль качества

35. Тестирование до тестирования

36. Правила виноделов

37. Сказано – записано

38. Проектные потаскушки

39. Атлант

40. Одежду носят не просто так

41. Командные смотрины

42. Трубка и акваланг

43. Эти чертовы интерфейсы

44. Пограничная зона

45. Улучшение новостей

46. Медленная правда

47. Практика эндшпиля

48. Музыканты

49. Журналисты

50. Свободное кресло .

51. Мой кузен Винни

52. Суп из функций

53. Качиство данных

54. Бен

55. Мисс Манеры

56. Нераздельное внимание

57. «Плаксы не играют в бейсбол!»

58. Хладнокровный Люк

59. Регулярная сдача в срок

60. Пища++

61. Сиротливые артефакты

62. Скрытая красота

63. Я не знаю

64. Дети из Лейк Вобегон

65. Взаимное обучение

66. Seelenverwandtschaft

67. Крестовая отвертка

68. Предсказание инноваций

69. Мэрилин Мюнстер

Интерлюдия: на полу монтажной

70. Ветераны броуновского движения

71. Четко и ясно

72. Предохранительный клапан

73. Вавилонская башня

74. Сюрприз!

75. Дверца холодильника

76. Солнце взойдет завтра

77. Завал

78. Время перемен

79. Бумажная фабрика

80. Офшорное безрассудство

81. Командный пункт

82. Чем пахнет?

83. Неразобранные полеты

84. Святость сырых идей

85. Утечка

86. Зомбированные шаблонами

Авторы иллюстраций