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: |
32 | test_mutex(): m_locked(false) {} |
33 | void lock() |
34 | { |
35 | BOOST_CHECK(m_locked == false); |
36 | m_locked = true; |
37 | } |
38 | bool try_lock() |
39 | { |
40 | if(m_locked) return false; |
41 | lock(); |
42 | return true; |
43 | } |
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 | |
64 | SelfReference() |
65 | { |
66 | m_signal = boost::make_shared<Signal>(); |
67 | } |
68 | |
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 | |
82 | void ReleaseSelf() |
83 | { |
84 | m_self.reset(); |
85 | } |
86 | |
87 | static void DoNothing() |
88 | { |
89 | } |
90 | |
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(slot: Signal::slot_type(&SelfReference::ReleaseSelf, obj.get()).track(tracked: obj)); |
100 | obj->m_conDoNothing = signal->connect(slot: Signal::slot_type(&SelfReference::DoNothing)); |
101 | } |
102 | (*signal)(); |
103 | } |
104 | }; |
105 | |
106 | BOOST_AUTO_TEST_CASE(test_main) |
107 | { |
108 | SelfReference::Run(); |
109 | } |
110 |