Борьба С Загадочными Сбоями Msbuild На Xamltaskfactory

Наша команда разрабатывает ядро кросс-платформенного приложения, которое должно быть построено на Windows под управлением Visual Studio 2015, Linux с gcc 4.9+, MacOS, iOS, Android и Windows Phone 8.1+.

Для автоматической проверки кода на Jenkins сборки настраиваются под все необходимые конфигурации.

Задача сборок — отловить код, который не строится на одной или нескольких платформах или не проходит модульные тесты, и не допустить его попадания к командам конечных приложений до тех пор, пока не будут сделаны соответствующие исправления.

Этот CI-процесс позволяет разработчику локально использовать удобную для него операционную систему и среду разработки, будь то Visual Studio, XCode, QtCreator или вообще vim+ninja, не опасаясь, что его изменения не сработают или провалят тесты в другая среда.

В идеальном мире красная сборка Jenkins (это то, что мы используем в качестве сервера сборки) указывает на проблему в коде.

Увидев красную лампочку на мониторе, висящем в углу комнаты, «дежурный по сборке» должен пойти и исправить найденную проблему.

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

Такие ложные срабатывания отнимают у команды лишнее время, притупляют внимание и в целом снижают доверие к CI внутри команды.

Я хочу рассказать историю борьбы с одной из таких проблем.

Проблема была специфична для MSBuild и проявлялась примерно в таком сообщении в журнале:

  
  
   

20:03:56 "D:\jenkins\workspace\task\ws\.

\SomeTarget.vcxproj" (default target) (429) -> 20:03:56 (_QtMetaObjectCompilerH target) -> 20:03:56 D:\jenkins\workspace\task\ws\.

\SomeQtBasedTarget.targets(52,5): error MSB4175: The task factory "XamlTaskFactory" could not be loaded from the assembly "Microsoft.Build.Tasks.Core".

Could not find file 'D:\jenkins\workspace\task\ws\TEMP\fv5nnzin.dll'.

[D:\jenkins\workspace\jenkins\workspace\task\ws\.

\SomeTarget.vcxpro]

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

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

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

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

Итак, что именно привело к ошибке? Для создания проектов мы используем цыпочка у которого есть 2 способа вызова внешней команды во время сборки - это действия И правила .

Действия реализуются через CustomBuild внутри файлов vcxproj. Пример из документации:

<ItemGroup> <CustomBuild Include="faq.txt"> <Message>Copying readme.</Message> <Command>copy %(Identity) $(OutDir)%(Identity)</Command> <Outputs>$(OutDir)%(Identity)</Outputs> </CustomBuild> </ItemGroup>

И всё у них хорошо, они не взрываются.

Правила используют другой механизм.

Комментарий в коде гласит:

Правила MSBuild реализуются с использованием трех файлов: файла XML, файла .

targets и файла .

props. Видеть blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx .

Как это работает? Для каждого такого правила MSbuild в %TEMP% генерирует исходный код на C# (файл .

cs), из которого пытается скомпилировать dll и сразу использовать ее, а если это не сработает, выдает исключение .

В комментарии говорится:

Это происходит, если произошел сбой при компиляции сборки.

Мы просто проходим, потому что разберемся с провалом ниже.

Действительно, в системном журнале за пару секунд до момента ошибки (согласно журналу сервера сборки) можно найти что-то вроде следующей записи об ошибке компилятора C#:

Faulting application name: csc.exe, version: 4.6.1055.0, time stamp: 0x563c1a09 Faulting module name: KERNELBASE.dll, version: 6.3.9600.18233, time stamp: 0x56bb4ebb Exception code: 0xc0000142 Fault offset: 0x00000000000ecdd0 Faulting process id: 0x1af4 Faulting application start time: 0x01d1d13dbec0f5bd Faulting application path: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe Faulting module path: KERNELBASE.dll Report Id: fc6cf36d-3d30-11e6-8260-0cc47ab21249 Faulting package full name: Faulting package-relative application ID:

Поиск подобных ошибок в Google показал, что дело в размере.

Куча рабочего стола для неинтерактивных сеансов.

Действительно, все было похоже: код ошибки совпадал, а подчиненный агент Jenkins работал как служба Windows. Приняв эту гипотезу в разработку, я начал экспериментировать со значением раздела SharedSection в записи реестра HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows. Попутно мне случайно удалось сделать сборку склонной к сбою почти со 100% вероятностью, что несколько облегчило итерации отладки.

Прочитав еще немного, я добрался до флажка «Разрешить взаимодействие с рабочим столом» в свойствах службы Jenkins, а затем до опции NoInteractiveServices в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows. Но все эти попытки не принесли плодов.

Иногда сборки проходили, но закономерности уловить не удавалось.

Продолжая возиться с особенностями запуска процессов из сервиса Jenkins, я наткнулся следующий текст на StackOverflow. Автор рассказывает об особенностях поведения MSBuild по умолчанию при указании опции /M для параллельной сборки нескольких проектов.

Дело в том, что MSBuild создает необходимое количество копий самого себя — узлов, ожидающих выполнения задач.

В процессе сборки задачи распределяются по этим узлам и выполняются параллельно.

После завершения сборки узла Нет гаснут и продолжают ждать поступления новых задач.

Это произошло с нами на Дженкинсе; после завершения сборки процессы MSBuild продолжали зависать в памяти.

Я начал экспериментировать.

Воспроизведя крах сборки несколько раз подряд, я убил все висевшие в памяти процессы MSBuild, и, о чудо, следующая сборка прошла успешно! Затем я вооружился инструкциями из StackOverflow и добавил в наш скрипт сборки настройку переменной MSBUILDDISABLENODEREUSE и передачу параметра /nr:false вызову MSBuild. После этого все процессы MSBuild стали умирать в конце сборки, а не оставаться висеть в памяти.

Решение оказалось рабочим.

Прошло почти 2 недели, а проблема так и не повторилась.

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

Теги: #msbuild #Visual Studio #C++ #jenkins #C++ #компиляторы #разработка для Windows

Вместе с данным постом часто просматривают: