Вопрос безопасности потоков является актуальной проблемой при разработке многопоточных приложений, особенно когда используются библиотеки и классы, предназначенные для работы в однопоточной среде. В данном случае, рассмотрим проблему, связанную с использованием класса TRTTIContext в Delphi.
Описание проблемы
Класс TRTTIContext предназначен для работы с метаданными типов в Delphi, что позволяет, например, сериализовать и десериализовать объекты. Однако, согласно предоставленным данным, при использовании метода FindType в многопоточной среде могут возникать проблемы, приводящие к возвращению nil. Это происходит не во всех версиях Delphi, и проблема отсутствует в версии XE, но присутствует начиная с XE6 и в последующих версиях, включая XE7 с обновлением 1.
Пример кода
В качестве примера представлен код, который демонстрирует проблему:
unit Unit1;
...
type
TTestThread = class(TThread)
private
FFailed: Boolean;
FRan: Boolean;
FId: Integer;
protected
procedure Execute; override;
public
property Failed: Boolean read FFailed;
property Ran: Boolean read FRan;
property Id: Integer read FId write FId;
end;
...
procedure TTestThread.Execute;
var
ctx : TRTTIContext;
begin
try
FFailed := not Assigned(ctx.FindType('Unit1.TForm1'));
FRan := True;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end;
...
При многопоточном выполнении этого кода, особенно с большим количеством потоков (например, 200), метод FindType может возвращать nil, что указывает на проблему. Использование TCriticalSection вокруг операций с TRTTIContext решает проблему.
Подтвержденный ответ
Проблема обнаружена внутри методов TRealPackage.FindType и MakeTypeLookupTable. Ошибка связана с неправильной реализацией паттерна "double checked locking". В коде, который должен реализовать этот паттерн, происходит присваивание общему ресурсу FNameToType после того, как проверка на его наличие уже выполнена, что приводит к гонке условий.
Альтернативный ответ
Проблема связана с неправильным использованием двойной проверки (double checked locking) в реализации класса TRealPackage. В коде, отвечающем за инициализацию внутреннего ресурса FNameToType, присутствует ошибка, которая приводит к тому, что другие потоки могут использовать этот ресурс до его полной инициализации.
Предложения по решению
Использовать глобальный контекст TRTTIContext для всех операций, связанных с RTTI.
В процессе инициализации программы выполнить операцию, которая вынудит вызвать TRealPackage.MakeTypeLookupTable. Это можно сделать, вызвав метод FindType на глобальном контексте, например, ctx.FindType('').
При невозможности изменить реализацию TRTTIContext, использовать синхронизацию с помощью TCriticalSection или аналогичных средств.
Заключение
Проблема безопасности потоков при использовании TRTTIContext в Delphi является известной и подтвержденной. Она была исправлена в версии Delphi 10.3 Rio. Для разработчиков, использующих более старые версии, рекомендуется применять вышеописанные меры предосторожности.
Проблема связана с некорректным поведением класса `TRTTIContext` в среде многопоточности в Delphi, что может приводить к возвращению `nil` при использовании метода `FindType` в многопоточных приложениях, начиная с версии XE6.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.