📜 ⬆️ ⬇️

Dynamic search for potential deadlocks

Hello. Some time ago I began to be engaged in accompanying a fairly voluminous software product. Editing after editing and somehow unnoticeably, deadlocks crept up to me. I quickly found out the source of the problems - these are nested locks . Due to ignorance of the basics of a software product, I implicitly violated the procedure for nesting locks. But it was not possible to find the source of the problems manually.

Borjomi is too late to drink, it makes no sense to lament about architecture. Connecting heavy artillery.


So, the classic problem (pseudocode):
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); 

If resources were locked at the same time, or in the same sequence, deadlocks would not occur.

Idea.


Let the class blocker make a graph of the sequence of object locks, and when the next insertion detects a cyclic reference, this will be the potential potential deadlock.
Example:
somewhere in the stream code: guard (m1) -> guard (m2)
somewhere in the stream code: guard (m2) -> guard (m1) !!! alarm !!! detected circular reference.

Implementation


Doing the implementation of the graphs yourself is hard. I used boost / graph.
To take into account the possibility of blocking in DLL module streams - “boost / interprocess”.
It makes no sense to paint the internal structure. Some of the main points are:

Use is trivial:
 #define ENABLE_DEADLOCK_CHECKER /**/ #include "deadlock_checker.h" static deadlock_checker_main_t deadlock_checker_main; /*  */ class guard { guard() { deadlock_checker_t::push(this); } ~guard() { deadlock_checker_t::pop(this); } }; 

')
PS Everything ended well. I caught deadlock. I learned about this software product and turned off the control module for uselessness.

work code
 /* *    - . */ #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 { /**   * @return true     */ 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; public: deadlock_graph() { } /**   * @return true     */ 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 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: /**     "locker"*/ 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; }; /**     "locker"*/ 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 

Source: https://habr.com/ru/post/201388/


All Articles