Search
Write a publication
Pull to refresh

Release Version: отладка только начинается…

Reading time3 min
Views3.1K

О чем речь?


Ваша программа наконец заработала, заказчик на тестовом полигоне уверенно тыкает кнопки, все работает штатно… но не спешите радоваться: вас могут подстерегать проблемы после выхода release версии. ПО начинает сыпаться с непонятными багами, заказчик уже готов порвать вас на британский флаг…

Думаете это проблема только Си-шников? Вы ошибаетесь!

Практический пример


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

Казалось бы, бремя выпуска релиза Вас уже мало касается, все же .NET это чуточку улучшенный C++ в котором существуют определенные проблемы. Но все не так безоблачно, как может казаться на первых порах.

Итак, пример:
  1.     static void Main(string[] args)
  2.     {
  3.       System.Threading.Timer t = new System.Threading.Timer(state =>
  4.       {
  5.         Console.WriteLine("Timer!");
  6.  
  7.         GC.Collect();
  8.       }, null, 0, 1000);
  9.  
  10.       Console.ReadKey();
  11.     }
* This source code was highlighted with Source Code Highlighter.


Простейший код, который по задумке автора должен писать заветное «Timer!» в консоль раз в секунду. Отладка в собранном в debug конфигурации проекте ничего подозрительного не выявляет, и подготавливается релиз… в котором программа не работает.

Разбор полётов


Да, да, этот «баг» является непосредственным следствием оптимизации (ну и, конечно, небольшой халатности программиста). Халатность заключена в том, что указанный System.Threading.Timer на самом деле наследует IDisposable, и, по хорошему, мы должны бы вызывать для него Dispose() метод. Ну пусть мы этого не делаем (на самом деле это к делу и не относится).

Изучаем листинг IL кода, сгенерированного в релиз и дебаг версиях и видим вот что.
дебаг:
.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 5
     .locals init (
     [0] class [mscorlib]System.Threading.Timer t)

     L_0000: nop
     L_0001: ldsfld class
    [mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<>9__CachedAnonymousMethodDelegate1
....



и релиз:
.method private hidebysig static void Main(string[] args) cil managed
{
     .entrypoint
     .maxstack 8
     L_0000: ldsfld class [mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<>9__CachedAnonymousMethodDelegate1
....



Я выделил различие.

Для тех кто еще не догадался

В дебаг версии компилятор оставляет нигде не используемую (кроме объявления) ссылку Timer в стеке, и GC видя, что ссылка жива (до тех пор пока не закончился метод) ничего не делает. Таймер работает.

В релиз же версии ссылка нигде не существует и первый же (не гарантируется) метод GC.Collect() уничтожит таймер.

Послесловие


Я искренне надеюсь что вам никогда не придется попадаться на явные (или нет) баги оптимизаторов, как в приведенном выше примере, в реальной жизни. Поиск подобных багов (особенно на стороне заказчика) очень сложен и порекомендовать я могу только сборку дебаг версии всех компонент и постепенной компиляции частей в релизе с целью поиска «бракованной» сборки и дальнейший ее анализ. Также может помочь распространение дебаг информации (.pdb файл обычно).

Удачной пятницы, ведите логи 8-)
Tags:
Hubs:
Total votes 29: ↑17 and ↓12+5
Comments12

Articles