Динамический поиск потенциальных взаимоблокировок

от автора

Здравствуйте. Некоторое время назад начал заниматься сопровождением довольно объемного программного продукта. Правка за правкой и как-то незаметно ко мне подкрались взаимоблокировки. Я быстро выяснил источник проблем — это вложенные блокировки. По незнанию основ программного продукта я неявно нарушил порядок вложения блокировок. Но найти вручную источник проблем не удалось.

Боржоми пить поздно, причитать насчёт архитектуры бессмысленно. Подключаем тяжелую артиллерию.


Итак, классическая проблема (псевдокод):

object m1; object m2;  // thread 1 quard<> g1(m1); .... // ожидаем освобождения m2 потоком thread 2  quard<> g2(m2);  // thread 2 quard<> g2(m2); .... // ожидаем освобождения m2 потоком thread 1 quard<> g1(m1);  

Если бы ресурсы блокировались одновременно или в одинаковой последовательности взаимоблокировки бы не произошло.

Идея.

Пусть класс блокировщик составляет граф последовательности блокировок объектов и когда при очередной вставке будет обнаружена циклическая ссылка это и будет искомая потенциальная взаимоблокировка.
Пример:
где то в коде потока: guard(m1) —> guard(m2)
где то в коде потока: guard(m2) —> guard(m1)!!! alarm!!! обнаружена циклическая ссылка.

Реализация

Делать реализацию графов самостоятельно это тяжко. Я задействовал «boost/graph».
Для учёта возможности блокировки в потоках модулей DLL — «boost/interprocess».
Расписывать внутреннее устройство смысла нет. Некоторые основные пункты:

  • для каждого потока хранится свой список текущих блокировок, для игнорирования реентабельных блокировок
  • граф хранится в разделяемой памяти процесса

Использование тривиально:

#define ENABLE_DEADLOCK_CHECKER /**/ #include "deadlock_checker.h" /*класс блокировщик ресурса*/ class guard {      guard()   {     deadlock_checker_t::push(this);   }      ~guard()   {     deadlock_checker_t::pop(this);   }  }; 

P.S. Всё закончилось хорошо. Я отловил deadlock. Познал дао сего программного продукта и отключил модуль контроля за ненадобностью.

рабочий код

 /* *	динамический поиск  потенциальных взаимо- блокировок.  */ #pragma once #include <assert.h> #include <stack> #include <map> #include <vector> #include <boost/thread/mutex.hpp> #include <boost/thread/tss.hpp> #include <boost/thread/once.hpp> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/depth_first_search.hpp> #include <boost/graph/visitors.hpp> #include <boost/filesystem.hpp> #include <boost/interprocess/managed_shared_memory.hpp> #ifdef _DEBUG //#define ENABLE_DEADLOCK_CHECKER #endif namespace deadlock_checker {      inline unsigned long get_current_process_id()     {           return GetCurrentProcessId();      }      template<typename T>     struct deadlock_graph_i     {         virtual bool _cdecl insert(const void* locker_child, const void* locker_root) = 0;     };     /***/     template<typename T>     class deadlock_graph : public deadlock_graph_i<T>     {         struct cycle_detector : public ::boost::dfs_visitor<>         {             cycle_detector(bool& has_cycle)                  : m_has_cycle(has_cycle) { }              template <class Edge, class Graph>             void back_edge(Edge, Graph&) { m_has_cycle = true;              }         protected:             bool& m_has_cycle;         };         typedef ::boost::adjacency_list<::boost::mapS, ::boost::mapS, ::boost::bidirectionalS,             ::boost::property<::boost::vertex_color_t, ::boost::default_color_type> > Graph;         typedef typename ::boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;         typedef typename ::boost::graph_traits<Graph>::edge_descriptor edge_descriptor;         typedef const void* type_value;     public:         ::boost::mutex _mutex;          Graph _graph;         ::std::map<type_value, vertex_descriptor> _vertex;         //std::vector < ::boost::default_color_type > _color_map;     public:         deadlock_graph()         {         }         /***/         bool _cdecl insert(const void* locker_child, const void* locker_root)         {             using namespace ::boost;             using namespace ::std;             mutex::scoped_lock scoped_lock(_mutex);              if(_vertex.end() == _vertex.find(locker_child))             {                 _vertex.insert(make_pair(locker_child, add_vertex(_graph) ));             }              if(_vertex.end() == _vertex.find(locker_root))             {                 _vertex.insert(make_pair(locker_root, add_vertex(_graph) ));             }             vertex_descriptor vertex_child = _vertex[locker_child];             pair<edge_descriptor, bool> ret = add_edge(vertex_child,_vertex[locker_root],_graph);             if(ret.second)             {                 bool has_cycle = false;                 cycle_detector vis(has_cycle);                 //_color_map.resize(num_vertices(_graph)); //= color_map(num_vertices(_graph));                 //::std::fill(_color_map.begin(),_color_map.end(),white_color);                  graph_traits<Graph>::vertex_iterator vi, vi_end;                 for (tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi)                     get(vertex_color, _graph)[*vi] = white_color;                  depth_first_visit(_graph, vertex_child, vis,                     get(vertex_color, _graph) );                 if(has_cycle)                 {                     // зачищаем граф, для построения заново.                     _graph.clear();                     _vertex.clear();                  }                 return !has_cycle;             }             return true;         }      };     inline char const* deadlock_shared_key()     {         return "1958EF20-6689-4e7b-9C53-3C115BCCF465";     }     /***/     template<typename T>     class deadlock_graph_common     {         deadlock_graph<T> _graph;         deadlock_graph_i<T>* _graph_ptr;         // флаг глобального использования         bool _graph_main;     public:         deadlock_graph_common()             : _graph_main(false)             , _graph_ptr(NULL)         {             using namespace boost::interprocess;             char process_id_str[20];             if(0 != _itoa_s(GetCurrentProcessId(),process_id_str,boost::size(process_id_str),10))             {                 throw ::std::runtime_error("error itoa()");             };              managed_shared_memory shmem(open_or_create, deadlock_shared_key(), 1024);                 _graph_ptr = *shmem.find_or_construct<deadlock_graph<T>*>(process_id_str)(&_graph);             _graph_main = _graph_ptr == &_graph;         }         ~deadlock_graph_common()         {             using namespace boost::interprocess;             try             {                 if(_graph_main)                 {                     char process_id_str[20];                     if(0 == _itoa_s(GetCurrentProcessId(),process_id_str,sizeof(process_id_str),10))                     {                         managed_shared_memory shmem(open_only, deadlock_shared_key());                             shmem.destroy<deadlock_graph<T>*>(process_id_str);                     };                  }             }             catch(...)             {             }         }         deadlock_graph_i<T>* operator->()         {             return _graph_ptr;         }     };     /**главный граф*/     template<typename T>     class main     {         deadlock_graph<T> _graph;     public:         main()         {             using namespace boost::interprocess;             char process_id_str[20];             if(0 != _itoa_s(get_current_process_id(),process_id_str,sizeof(process_id_str),10))             {                 throw ::std::runtime_error(__FUNCTION__);             }              managed_shared_memory shmem(open_or_create, deadlock_shared_key(), 1024);                 shmem.construct<deadlock_graph<T>*>(process_id_str)(&_graph);          }     };     /**глобальный интерфейс*/     template<typename T>     class checker     {     private:         /**глобальная ссылка на главный граф.*/         struct main_ref         {             deadlock_graph_i<T>* & _graph_ptr;         public:             main_ref(deadlock_graph_i<T>* & graph_ptr)                 : _graph_ptr(graph_ptr)             {             }             void operator()() const             {                 using namespace boost::interprocess;                 char process_id_str[20];                 if(0 != _itoa_s(get_current_process_id(),process_id_str,boost::size(process_id_str),10))                 {                     throw ::std::runtime_error(__FUNCTION__);                 }                 managed_shared_memory shmem(open_read_only, deadlock_shared_key());                     _graph_ptr = *shmem.find<deadlock_graph<T>*>(process_id_str).first;                 if(!_graph_ptr)                 {                     throw ::std::runtime_error(__FUNCTION__);                 }             }         };     private:         static boost::thread_specific_ptr<::std::stack<void*> > _stack;         static deadlock_graph_i<T>* _graph_ptr;         static boost::once_flag     _graph_flag;     public:         static bool push(T* locker, char* name)         {             if(!push(locker))             {                 // логируем потенциальную взимо-блокировку.                 if(::boost::filesystem::file_exist())                 {                 }             }         }         /***/         static bool push(void* locker)         {             bool cycle_error = false;             init_thread();             ::std::stack<void*>* lockers = _stack.get();             assert(lockers && lockers->size() < 8);             if(lockers->size() > 0)             {                 void* locker_root = lockers->top();                 if(locker_root != locker)                 {                     // логируем потенциальную взаимо-блокировку.                     char const * filename = ".\\Data\\deadlock.log";                     if(!::boost::filesystem::is_regular_file(filename))                     {                         // вставляем ребро в граф.                         cycle_error = !_graph_ptr->insert(locker,locker_root);                         assert(!cycle_error && "потенциальный deadlock");                         if(cycle_error)                         {                             ::std::ofstream file(filename);                             if(file.is_open())                             {                                 file << "обнаружена потенциальный deadlock" << ::std::endl;                             }                         }                     }                 }             }             lockers->push(locker);             return cycle_error;         };         /***/         static void pop(void* locker = 0)         {             ::std::stack<void*>* lockers = _stack.get();             assert(lockers && !lockers->empty());             assert(!locker || lockers->top() == locker);             if(!lockers->empty())             {                 lockers->pop();             };         };     private:         /***/         static void init_thread()         {             if(!_stack.get())             {                 boost::call_once(_graph_flag, main_ref(_graph_ptr));                 _stack.reset(new ::std::stack<T*>);             }         }     };       template<typename T>     boost::thread_specific_ptr<::std::stack<void*> > checker<T>::_stack;     template<typename T>     deadlock_graph_i<T>* checker<T>::_graph_ptr;     template<typename T>     boost::once_flag       checker<T>::_graph_flag;      template<>     class checker<bool>     {     public:         static bool push(void* /*locker*/, char* /*name*/)         {             return true;         }         /***/         static bool push(void* /*locker*/)         {             return true;         };         /***/         static void pop(void* /*locker*/ = 0)         {         };     };     template<>     class main<bool>     {     }; }  #if defined(ENABLE_DEADLOCK_CHECKER) typedef deadlock_checker::checker<void> deadlock_checker_t; typedef deadlock_checker::main<void> deadlock_checker_main_t; #else typedef deadlock_checker::checker<bool> deadlock_checker_t; typedef deadlock_checker::main<bool> deadlock_checker_main_t; #endif  

ссылка на оригинал статьи http://habrahabr.ru/post/201388/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *