Здравствуйте. Некоторое время назад начал заниматься сопровождением довольно объемного программного продукта. Правка за правкой и как-то незаметно ко мне подкрались взаимоблокировки. Я быстро выяснил источник проблем — это вложенные блокировки. По незнанию основ программного продукта я неявно нарушил порядок вложения блокировок. Но найти вручную источник проблем не удалось.
Боржоми пить поздно, причитать насчёт архитектуры бессмысленно. Подключаем тяжелую артиллерию.
Итак, классическая проблема (псевдокод):
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
Автор: eshirshov