1 // Signals2 library 2 // 3 // Regression test based on bug report from Arian Alin Radu. 4 // The problem was that tracked objects could be released 5 // while holding the signal mutex during signal invocation. 6 // This could result in a recursive 7 // lock attempt if the tracked object manipulates the signal 8 // in its destructor. 9 10 // Copyright Frank Mori Hess 2019 11 // Use, modification and 12 // distribution is subject to the Boost Software License, Version 13 // 1.0. (See accompanying file LICENSE_1_0.txt or copy at 14 // http://www.boost.org/LICENSE_1_0.txt) 15 16 // For more information, see http://www.boost.org 17 18 #define BOOST_TEST_MODULE signals2 deadlock regression test 19 #include <boost/test/included/unit_test.hpp> 20 #include <boost/shared_ptr.hpp> 21 #include <boost/make_shared.hpp> 22 #include <boost/signals2/signal.hpp> 23 #include <boost/signals2/signal_type.hpp> 24 25 namespace bs2 = boost::signals2; 26 27 28 // dummy mutex that detects attempts to recursively lock 29 class test_mutex 30 { 31 public: test_mutex()32 test_mutex(): m_locked(false) {} lock()33 void lock() 34 { 35 BOOST_CHECK(m_locked == false); 36 m_locked = true; 37 } try_lock()38 bool try_lock() 39 { 40 if(m_locked) return false; 41 lock(); 42 return true; 43 } unlock()44 void unlock() 45 { 46 m_locked = false; 47 } 48 private: 49 bool m_locked; 50 }; 51 52 using namespace bs2::keywords; 53 typedef bs2::signal_type<void(), mutex_type<test_mutex> >::type Signal; 54 55 class SelfReference: private boost::noncopyable 56 { 57 public: 58 boost::shared_ptr<SelfReference> m_self; 59 boost::shared_ptr<Signal> m_signal; 60 61 boost::signals2::connection m_conReleaseSelf; 62 boost::signals2::connection m_conDoNothing; 63 SelfReference()64 SelfReference() 65 { 66 m_signal = boost::make_shared<Signal>(); 67 } 68 ~SelfReference()69 ~SelfReference() 70 { 71 // the first slot (ReleaseSelf) has been called; now the trackable object (this) 72 // was released, while the second slot is locked 73 BOOST_CHECK(!m_conReleaseSelf.connected()); 74 // the second slot is locked, and we enter a recursive (pthread: dead) lock 75 BOOST_CHECK(m_conDoNothing.connected()); 76 m_conReleaseSelf.disconnect(); 77 m_conDoNothing.disconnect(); 78 // enter recursive (pthread: dead) lock again: 79 BOOST_CHECK(m_signal->empty()); 80 } 81 ReleaseSelf()82 void ReleaseSelf() 83 { 84 m_self.reset(); 85 } 86 DoNothing()87 static void DoNothing() 88 { 89 } 90 Run()91 static void Run() 92 { 93 boost::shared_ptr<Signal> signal; 94 { 95 boost::shared_ptr<SelfReference> obj = boost::make_shared<SelfReference>(); 96 obj->m_self = obj; 97 signal = obj->m_signal; 98 99 obj->m_conReleaseSelf = signal->connect(Signal::slot_type(&SelfReference::ReleaseSelf, obj.get()).track(obj)); 100 obj->m_conDoNothing = signal->connect(Signal::slot_type(&SelfReference::DoNothing)); 101 } 102 (*signal)(); 103 } 104 }; 105 BOOST_AUTO_TEST_CASE(test_main)106BOOST_AUTO_TEST_CASE(test_main) 107 { 108 SelfReference::Run(); 109 } 110