|
11 | 11 | namespace testing { |
12 | 12 |
|
13 | 13 | /// A transactional queue for testing purposes. |
14 | | -template <class Value> struct queue_tm { |
| 14 | +template <class Value> class queue_tm { |
| 15 | + struct node_t { |
| 16 | + trade::atom<std::shared_ptr<node_t>> m_next; |
| 17 | + Value m_value; |
| 18 | +#ifndef NDEBUG |
| 19 | + ~node_t() { --s_live_nodes; } |
| 20 | +#endif |
| 21 | + template <class ForwardableValue> |
| 22 | + node_t(ForwardableValue &&value) |
| 23 | + : m_value(std::forward<ForwardableValue>(value)) { |
| 24 | +#ifndef NDEBUG |
| 25 | + ++s_live_nodes; |
| 26 | +#endif |
| 27 | + } |
| 28 | + }; |
| 29 | + |
| 30 | + trade::atom<std::shared_ptr<node_t>> m_first; |
| 31 | + trade::atom<std::shared_ptr<node_t>> m_last; |
| 32 | + |
| 33 | +public: |
15 | 34 | using value_t = Value; |
16 | 35 |
|
17 | 36 | queue_tm() = default; |
18 | 37 |
|
19 | 38 | queue_tm(const queue_tm &) = delete; |
20 | 39 | queue_tm &operator=(const queue_tm &) = delete; |
21 | 40 |
|
22 | | - size_t size() { |
23 | | - return trade::atomically(trade::assume_readonly, [&]() { |
24 | | - size_t n = 0; |
25 | | - for (auto node = m_first.load(); node; node = node->m_next) |
26 | | - n += 1; |
27 | | - return n; |
28 | | - }); |
29 | | - } |
30 | | - |
31 | | - operator std::vector<value_t>() const { |
32 | | - std::vector<value_t> values; |
33 | | - trade::atomically(trade::assume_readonly, [&]() { |
34 | | - values.clear(); |
35 | | - for (auto node = m_first.load(); node; node = node->m_next) |
36 | | - values.push_back(node->m_value); |
37 | | - }); |
38 | | - return values; |
39 | | - } |
40 | | - |
41 | | - void clear() { |
42 | | - trade::atomically([&]() { m_first = m_last = nullptr; }); |
43 | | - } |
44 | | - |
45 | | - void push_back(const value_t &value) { |
46 | | - std::shared_ptr<node_t> node(new node_t{value}); |
47 | | - trade::atomically([&]() { |
48 | | - if (auto prev = m_last.load()) |
49 | | - prev->m_next = m_last = node; |
50 | | - else |
51 | | - m_first = m_last = node; |
52 | | - }); |
53 | | - } |
54 | | - |
55 | | - void push_front(const value_t &value) { |
56 | | - std::shared_ptr<node_t> node(new node_t{value}); |
57 | | - trade::atomically([&]() { |
58 | | - if (auto next = m_first.load()) { |
59 | | - m_first = node; |
60 | | - node->m_next = next; |
61 | | - } else { |
62 | | - m_last = m_first = node; |
63 | | - } |
64 | | - }); |
65 | | - } |
66 | | - |
67 | | - std::optional<value_t> try_pop_front() { |
68 | | - return trade::atomically([&]() -> std::optional<value_t> { |
69 | | - if (auto first = m_first.load()) { |
70 | | - if (auto next = first->m_next.load()) |
71 | | - m_first = next; |
72 | | - else |
73 | | - m_last = m_first = nullptr; |
74 | | - return first->m_value; |
75 | | - } |
76 | | - return std::nullopt; |
77 | | - }); |
78 | | - } |
79 | | - |
80 | | - bool empty() { |
81 | | - return trade::atomically(trade::assume_readonly, |
82 | | - [&]() { return !m_first.load(); }); |
83 | | - } |
84 | | - |
85 | | - value_t pop_front() { |
86 | | - return trade::atomically([&]() { |
87 | | - if (auto opt_value = try_pop_front()) |
88 | | - return opt_value.value(); |
89 | | - trade::retry(); |
90 | | - }); |
91 | | - } |
92 | | - |
93 | | - static std::atomic<size_t> s_live_nodes; // Only for testing purposes |
94 | | - |
95 | | -private: |
96 | | - struct node_t { |
97 | | - trade::atom<std::shared_ptr<node_t>> m_next; |
98 | | - value_t m_value; |
99 | | - ~node_t() { --s_live_nodes; } |
100 | | - node_t(const value_t &value) : m_value(value) { ++s_live_nodes; } |
101 | | - }; |
| 41 | + size_t size() const; |
102 | 42 |
|
103 | | - trade::atom<std::shared_ptr<node_t>> m_first; |
104 | | - trade::atom<std::shared_ptr<node_t>> m_last; |
| 43 | + bool empty() const; |
| 44 | + |
| 45 | + void clear(); |
| 46 | + |
| 47 | + template <class ForwardableValue> void push_back(ForwardableValue &&value); |
| 48 | + |
| 49 | + template <class ForwardableValue> void push_front(ForwardableValue &&value); |
| 50 | + |
| 51 | + std::optional<Value> try_pop_front(); |
| 52 | + |
| 53 | + Value pop_front(); |
| 54 | + |
| 55 | + operator std::vector<Value>() const; |
| 56 | + |
| 57 | +#ifndef NDEBUG |
| 58 | + static std::atomic<size_t> s_live_nodes; |
| 59 | +#endif |
105 | 60 | }; |
106 | 61 |
|
| 62 | +template <class Value> size_t queue_tm<Value>::size() const { |
| 63 | + return trade::atomically(trade::assume_readonly, [&]() { |
| 64 | + size_t n = 0; |
| 65 | + for (auto node = m_first.load(); node; node = node->m_next) |
| 66 | + n += 1; |
| 67 | + return n; |
| 68 | + }); |
| 69 | +} |
| 70 | + |
| 71 | +template <class Value> bool queue_tm<Value>::empty() const { |
| 72 | + return trade::atomically(trade::assume_readonly, |
| 73 | + [&]() { return !m_last.load(); }); |
| 74 | +} |
| 75 | + |
| 76 | +template <class Value> void queue_tm<Value>::clear() { |
| 77 | + trade::atomically([&]() { m_first = m_last = nullptr; }); |
| 78 | +} |
| 79 | + |
| 80 | +template <class Value> |
| 81 | +template <class ForwardableValue> |
| 82 | +void queue_tm<Value>::push_back(ForwardableValue &&value) { |
| 83 | + std::shared_ptr<node_t> node( |
| 84 | + new node_t(std::forward<ForwardableValue>(value))); |
| 85 | + trade::atomically([&]() { |
| 86 | + if (auto prev = m_last.load()) |
| 87 | + prev->m_next = m_last = node; |
| 88 | + else |
| 89 | + m_first = m_last = node; |
| 90 | + }); |
| 91 | +} |
| 92 | + |
| 93 | +template <class Value> |
| 94 | +template <class ForwardableValue> |
| 95 | +void queue_tm<Value>::push_front(ForwardableValue &&value) { |
| 96 | + std::shared_ptr<node_t> node( |
| 97 | + new node_t(std::forward<ForwardableValue>(value))); |
| 98 | + trade::atomically([&]() { |
| 99 | + if (auto next = m_first.load()) { |
| 100 | + m_first = node; |
| 101 | + node->m_next = next; |
| 102 | + } else { |
| 103 | + m_last = m_first = node; |
| 104 | + } |
| 105 | + }); |
| 106 | +} |
| 107 | + |
| 108 | +template <class Value> std::optional<Value> queue_tm<Value>::try_pop_front() { |
| 109 | + return trade::atomically([&]() -> std::optional<Value> { |
| 110 | + if (auto first = m_first.load()) { |
| 111 | + if (auto next = first->m_next.load()) |
| 112 | + m_first = next; |
| 113 | + else |
| 114 | + m_last = m_first = nullptr; |
| 115 | + return first->m_value; |
| 116 | + } |
| 117 | + return std::nullopt; |
| 118 | + }); |
| 119 | +} |
| 120 | + |
| 121 | +template <class Value> Value queue_tm<Value>::pop_front() { |
| 122 | + return trade::atomically([&]() { |
| 123 | + if (auto opt_value = try_pop_front()) |
| 124 | + return opt_value.value(); |
| 125 | + trade::retry(); |
| 126 | + }); |
| 127 | +} |
| 128 | + |
| 129 | +template <class Value> queue_tm<Value>::operator std::vector<Value>() const { |
| 130 | + std::vector<Value> values; |
| 131 | + trade::atomically(trade::assume_readonly, [&]() { |
| 132 | + values.clear(); |
| 133 | + for (auto node = m_first.load(); node; node = node->m_next) |
| 134 | + values.push_back(node->m_value); |
| 135 | + }); |
| 136 | + return values; |
| 137 | +} |
| 138 | + |
| 139 | +#ifndef NDEBUG |
107 | 140 | template <class Value> std::atomic<size_t> queue_tm<Value>::s_live_nodes = 0; |
| 141 | +#endif |
108 | 142 |
|
109 | 143 | } // namespace testing |
0 commit comments