Стандартная библиотека .net не работает в ядре .net, но не в фреймворке

Мы должны использовать API от стороннего поставщика (perforce). До сегодняшнего дня мы могли ссылаться на этот API и использовать его в приложении .net framework. Но сейчас мы занимаемся рефакторингом нашего продукта и, конечно же, решили использовать новую среду .net, а именно .net core 2.2. Поскольку Perforce не опубликовал эту библиотеку для ядра .net, мы решили перенести эту библиотеку на стандарт .net.

Итак, в двух словах мы скачали исходный код, портировали и добавили в качестве эталона в основной проект .net.

Все идет нормально. Странно то, что после некоторого использования этой библиотеки мы получаем ExecutionEngineException из этой библиотеки, которая запускает Environment.Failfast и завершает работу приложения.

Еще одна важная информация заключается в том, что библиотека использует другую родную библиотеку (p4bridge.dll).

Исключение такое:

FailFast:
A callback was made on a garbage collected delegate of type 'p4netapi!Perforce.P4.P4CallBacks+LogMessageDelegate::Invoke'.

   at Perforce.P4.P4Bridge.RunCommandW(IntPtr, System.String, UInt32, Boolean, IntPtr[], Int32)
   at Perforce.P4.P4Bridge.RunCommandW(IntPtr, System.String, UInt32, Boolean, IntPtr[], Int32)
   at Perforce.P4.P4Server.RunCommand(System.String, UInt32, Boolean, System.String[], Int32)
   at Perforce.P4.P4Command.RunInt(Perforce.P4.StringList)
   at Perforce.P4.P4CommandResult..ctor(Perforce.P4.P4Command, Perforce.P4.StringList)
   at Perforce.P4.P4Command.Run(Perforce.P4.StringList)
   at Perforce.P4.Client.runFileListCmd(System.String, Perforce.P4.Options, System.String, Perforce.P4.FileSpec[])
   at Perforce.P4.Client.SyncFiles(System.Collections.Generic.IList`1<Perforce.P4.FileSpec>, Perforce.P4.Options)

Я уже знаю о сообщении, связанном с garbage collected delegate. Кажется, где-то указатель на делегата передается в неуправляемую библиотеку, а потом его собирает сборщик мусора.

Мы смотрим на исходный код этого API. И мы увидели несколько возможных мест, которые могут быть причиной этой ошибки. Но, это всего лишь мысль.

При расследовании сбоя мы создали еще одно приложение .net framework, которое ссылается на эту перенесенную библиотеку, и тогда мы не обнаружили ошибок в .net framework.

Мои вопросы:

  1. Есть ли разница между .net framework и .net core с точки зрения механизма сборки мусора?
  2. Как это возможно, что .net framework и .net core по-разному реагируют на одну и ту же библиотеку?

person Farhad Jabiyev    schedule 12.12.2019    source источник
comment
Есть ли разница между .net framework и .net core с точки зрения механизма сборщика мусора? - абсолютно! GC продолжал развиваться; однако я подозреваю, что если он работал ранее, то наиболее вероятным сценарием является то, что он работал случайно с неопределенным поведением; Можете ли вы предоставить более подробную информацию о том, как этот обратный вызов был использован совместно с другой библиотекой?   -  person Marc Gravell    schedule 12.12.2019
comment
Как это возможно, что .net framework и .net core по-разному реагируют на одну и ту же библиотеку? - потому что JIT, GC и BCL претерпели значительные изменения; большая часть кода не пострадает, но некоторые сценарии: абсолютно! черт возьми, даже что-то такое простое, как спросить, какая кодировка текста «по умолчанию»? (где "по умолчанию" - абсолютно неправильное слово) принципиально отличается...   -  person Marc Gravell    schedule 12.12.2019
comment
@MarcGravell Спасибо за информацию. На самом деле, мы нашли одно место, где они передают указатель на неуправляемую библиотеку, и в какой-то момент они повторно инициализируют эту переменную внутри .net API. На самом деле, изучив исходный код, мы обнаружили, что это странно работает в .net framework.   -  person Farhad Jabiyev    schedule 12.12.2019
comment
на самом деле, если вы передаете указатели на неуправляемый код, невероятно легко создать код, который просто сломан; все простые параметры четко определены только на время одного вызова P/Invoke или на время fixed блока; для большинства долгоживущих сценариев вам нужно иметь дело с ручными пинами через GCHandle и т. д.; такой код P/Invoke очень может оказаться неправильным. Итак: если это неправильно, мы снова находимся на неопределенной территории, где неправильный код может работать при небольших значениях работы; Меня не удивит, что такой код будет хрупким из-за переключателей фреймворка.   -  person Marc Gravell    schedule 12.12.2019


Ответы (1)


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

Сначала хочу резюмировать ответ на 2-й вопрос:

Как это возможно, что .net framework и .net core по-разному реагируют на одну и ту же библиотеку?

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

Начнем с «определения» .Net Standard. .NET Standard — это не что иное, как спецификация (думайте об этом как об интерфейсе), она всего лишь объявляет, какие типы и API-интерфейсы предоставляются конкретной платформой в зависимости от ее версии.

Если вы посмотрите, то увидите, что ваша стандартная библиотека ссылается на .NET Standard SDK, который содержит netstandard.dll, содержащий API, или, лучше сказать, контракт, мы можем использовать из проекта библиотеки. Если вы посмотрите на свою стандартную библиотеку с Ildasm.exe, вы увидите, что она использует .netstandard.dll.

введите здесь описание изображения

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

Я всегда был поклонником диаграмм:

введите здесь описание изображения

Как видно на диаграмме, и базовые, и фреймворковые приложения ссылаются на свои собственные netstandard.dll. И эти библиотеки построены на основе концепции type forwarding. Для основных приложений BCL типов предоставляются System.Runtime.dll, а для фреймворков BCLтипы предоставляются mscorlib.dll. Итак, две разные реализации.

Например, вот IL-код библиотеки .netstandard, которая будет использоваться основными приложениями:

введите здесь описание изображения

Подробнее о переадресации типов из msdn.

Источник сведений о .NET Standard.


А теперь немного о моем первом вопросе:

Есть ли разница между .net framework и .net core с точки зрения механизма сборки мусора?

На самом деле неудивительно, что Microsoft продолжает развивать механизм GC.


А теперь как я решил проблему:

Хорошо, что библиотека, которую я перенес в стандарт, была с открытым исходным кодом. Я исследовал проблему, и в двух словах они передают указатель делегата на неуправляемую библиотеку. И эта неуправляемая библиотека сохраняет указатель и пытается использовать его в течение некоторого времени жизни, которое больше, чем время жизни этого указателя. На самом деле, интересный момент заключается в том, что GC фреймворка каким-то образом не собирает этот делегат, и поэтому на стороне фреймворка нет исключений. Но в случае ядра GC собирает этот делегат и создает причину сбоя. Я изменил способ установки этого поля, и теперь он работает. Я объявил это поле статическим и инициализировал его внутри статического конструктора, поэтому время жизни этого делегата равно времени жизни приложения.

person Farhad Jabiyev    schedule 14.12.2019
comment
Я очень рад, что вы докопались до сути! - person Marc Gravell; 15.12.2019
comment
Поскольку GC не является детерминированным, подобные ошибки довольно часто остаются незамеченными в течение длительного времени. - person David Browne - Microsoft; 15.12.2019
comment
Большое спасибо за этот пост и ответ. Вы явно потратили на это много времени. Один запрос... Как текущий сопровождающий P4API.NET, я очень заинтересован в том, чтобы точно знать, какие изменения вы должны были внести, чтобы решить эту проблему, чтобы я мог включить их в следующий выпуск. Не могли бы вы сделать изменения исходного кода доступными? Большое спасибо! - person Norman Morse; 26.05.2021