Avoiding Deadlocks using Template Metaprogramming
Hi,
I've been experimenting with using template metaprogramming to enforce
deadlock-free code. The code below enforces an order that the locks
must be acquired in, and enforces that the locks are released in the
reverse order that they are acquired. This is not two-phase locking,
but should guarantee that no deadlocks occur.
It's not quite complete yet, and there is a lot of room for
improvement - there's a list of points at the bottom I'd appreciate
your thoughts on...
//
============================================================================
// Name : AvoidDeadlocks.cpp
// Author : Nicholas White
//
============================================================================
#include <boost/mpl/vector.hpp>
#include <boost/mpl/vector_c.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/distance.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/begin.hpp>
#include <boost/mpl/greater_equal.hpp>
#include <boost/mpl/comparison.hpp>
#include <boost/mpl/back.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/pop_back.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/exceptions.hpp>
#include <boost/strong_typedef.hpp>
#include <iostream>
namespace mpl = boost::mpl;
template<typename MyList, typename Item>
struct index_of {
typedef typename boost::mpl::begin<MyList>::type begin;
typedef typename boost::mpl::find<MyList, Item>::type end;
typedef typename boost::mpl::distance<begin, end>::type type;
};
template<typename AllLocksInOrder, typename CurrentLocks, typename
LockName>
struct can_lock {
typedef typename mpl::empty<CurrentLocks>::type is_empty;
typedef typename boost::mpl::back<CurrentLocks>::type
last_element;
typedef typename index_of<AllLocksInOrder, last_element>::type
last_index;
typedef typename index_of<AllLocksInOrder, LockName>::type
current;
typedef typename mpl::or_<is_empty,typename
mpl::greater_equal<current, last_index>::type>::type type;
};
template<typename AllLocksInOrder, typename CurrentLocks, typename
LockName>
struct can_unlock {
typedef typename boost::mpl::back<CurrentLocks>::type last_element;
typedef typename index_of<AllLocksInOrder, last_element>::type
last_index;
typedef typename index_of<AllLocksInOrder, LockName>::type
current_index;
typedef typename boost::mpl::equal_to<current_index,
last_index>::type type;
};
struct unit_test {
typedef boost::mpl::vector<char, int, unsigned, long, unsigned
long> types;
typedef boost::mpl::vector<unsigned, long> current_types;
BOOST_MPL_ASSERT(( mpl::equal_to<can_lock<types,
current_types,
char>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_lock<types,
current_types,
int>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_lock<types,
current_types,
unsigned>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_lock<types,
current_types,
long>::type, mpl::bool_<true> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_lock<types,
current_types,
unsigned long>::type, mpl::bool_<true> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_unlock<types,
current_types,
char>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_unlock<types,
current_types,
int>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_unlock<types,
current_types,
unsigned>::type, mpl::bool_<false> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_unlock<types,
current_types,
long>::type, mpl::bool_<true> > ));
BOOST_MPL_ASSERT(( mpl::equal_to<can_unlock<types,
current_types,
unsigned long>::type, mpl::bool_<false> > ));
};
template<typename Lockable, typename AllLocks, typename ThisLockName>
class TwoPhaseLock {
public:
template<typename CurrentLocks>
void lock() {
BOOST_MPL_ASSERT((can_lock<AllLocks, CurrentLocks,
ThisLockName>));
_lock.lock();
}
template<typename CurrentLocks>
void unlock() {
BOOST_MPL_ASSERT((can_unlock<AllLocks, CurrentLocks,
ThisLockName>));
_lock.unlock();
}
private:
Lockable _lock;
};
typedef mpl::vector<> no_locks;
#define TPL_LOCK(LockName, CurrentLocks, NewLocks) \
LockName##_name.lock<CurrentLocks>(); \
typedef mpl::push_back<CurrentLocks, LockName>::type NewLocks
#define TPL_UNLOCK(LockName, CurrentLocks, NewLocks) \
LockName##_name.unlock<CurrentLocks>(); \
typedef mpl::pop_back<CurrentLocks>::type NewLocks
#define TPL_DECLARE_LOCKS(LockType, A, B) \
BOOST_STRONG_TYPEDEF(int, A); \
BOOST_STRONG_TYPEDEF(int, B); \
typedef mpl::vector<A, B> LocksInOrder; \
static TwoPhaseLock<LockType, LocksInOrder, A> A##_name; \
static TwoPhaseLock<LockType, LocksInOrder, B> B##_name
// A working example:
TPL_DECLARE_LOCKS(boost::mutex, MyLockA, MyLockB);
int main(void) {
TPL_LOCK(MyLockA, no_locks, locked_a);
TPL_LOCK(MyLockB, locked_a, locked_both);
TPL_UNLOCK(MyLockB, locked_both, unlocked_b);
TPL_UNLOCK(MyLockA, unlocked_b, unlocked_both);
/*
//if these lines are uncommented, the code will not compile (as
the lock are released out-of-order)
TPL_LOCK(MyLockA, no_locks, la);
TPL_LOCK(MyLockB, la, la_lb);
TPL_UNLOCK(MyLockA, la_lb, la_lb_ua);
TPL_UNLOCK(MyLockB, la_lb_ua, la_lb_ua_ub);
*/
}
The list of problems:
[1]
Function calls are difficult. It is easy to pass the current locks as
a template parameter to the method, but I'm not sure how to pass the
new list of locks back to the caller function.
[2]
TPL_UNLOCK uses a typedef for the 'out' parameter. Typedefs can't be
overwritten, so every out parameter in the same scope has to have a
different name (as you can see in the example main() function, this
gets a bit messy).
[3]
There has to be a TPL_DECLARE_LOCKS definition for every number of
locks (the above code only has a definition for two locks).Could this
be made into a recursive macro declaration?
Any other thoughts you have would also be appreciated.
Many thanks,
Nicholas White
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]