В текущем проекте thread sanitizer периодически выдает предупреждение о data race на фрагменте, который относится к SObjectizer-у.
Самое плохое то, что:
- я не понимаю в чем именно thread sanitizer видит проблему. Соответственно, неизвестно, является ли срабатывание TSan-а ложно позитивным или же есть реальная ошибка, которую следует исправить;
- мне не удается повторить такую же ситуацию в тестах для самого SO-5. Т.е. в рамках проекта TSan диагностику выдает, а в мелких тестах, которые пытаются повторить тот же сценарий -- нет. Ни в какую. Что сильно затрудняет разбирательства и поиск обходных путей.
Что здесь происходит:
Агент на нити T7 отсылает сообщение GiveMeTask агенту-координатору, который работает на нити T3.
Агент-координатор получает сообщение и обрабатывает его. После чего начинается разрушение объекта execution_demand_t, в котором лежит умный указатель на экземпляр сообщения.
В это же время на нити T7 завершается процедура отсылки сообщения.
И, как я понимаю, фокус в том, что эти два действия происходят на двух нитях очень близко друг к другу по времени.
Экземпляр сообщения GiveMeTask создается на нити T7. Указатель на этот экземпляр хранится на нити T7 внутри объекта intrusive_ptr_t.
На нити T3 внутри execution_demand_t так же есть свой объект intrusive_ptr_t который хранит указатель на этот же экземпляр GiveMeTask.
Т.е. на двух нитях есть два разных intrusive_ptr_t, которые хранят в себе указатель на один и тот же объект GiveMeTask.
При этом счетчик ссылок на GiveMeTask хранится в самом объекте GiveMeTask. Класс GiveMeTask наследуется от so_5::message_t:
struct GiveMeTask final : public so_5::message_t
{
const so_5::mbox_t _workerMbox;
GiveMeTask(so_5::mbox_t workerMbox)
: _workerMbox{ std::move(workerMbox) }
{}
};
А so_5::message_t наследуется от so_5::atomic_refcounted_t:
class message_t : public atomic_refcounted_t
{
...
};
В so_5::atomic_refcounted_t счетчик ссылок хранится в виде std::atomic. Т.е. операции инкремента-декремента количества ссылок происходят атомарно и не нуждаются в дополнительной синхронизации.
Получается, что на нити T7 создается новый экземпляр GiveMeTask, указатель на него сохраняется в локальном объекте intrusive_ptr_t и счетчик ссылок на GiveMeTask выставляется в 1.
На нити T7 вызывается send для GiveMeTask и формируется execution_demand_t для агента-координатора. Внутри execution_demand_t создается свой intrusive_ptr_t и счетчик ссылок для GiveMeTask получает значение 2.
Затем на нити T3 происходит обработка GiveMeTask, после чего начинается разрушение execution_demand_t и его содержимого (в том числе и второго intrusive_ptr_t).
Но чуть раньше на нити T7 происходит разрушение своего intrusive_ptr_t после чего счетчик ссылок в GiveMeTask опускается до 1.
А уже после этого на нити T3 счетчик ссылок на GiveMeTask обнуляется и происходит разрушение объекта GiveMeTask.
Происходят действия именно в этом порядке. Если бы сперва полностью разрушился execution_demand_t на нити T3 и лишь после этого началось уничтожение intrusive_ptr_t на нити T7, то деструктор GiveMeTask вызвался бы на нити T7, а не на нити T3.
Т.е. с моей точки зрения здесь все OK. Но TSan видит data race. А я не понимаю про какой data race идет речь.
Под катом выхлоп от TSan в текстовом виде.