1 | //===-- buffer_queue_test.cpp ---------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file is a part of XRay, a function call tracing system. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | #include "xray_buffer_queue.h" |
13 | #include "gmock/gmock.h" |
14 | #include "gtest/gtest.h" |
15 | |
16 | #include <atomic> |
17 | #include <future> |
18 | #include <thread> |
19 | #include <unistd.h> |
20 | |
21 | namespace __xray { |
22 | namespace { |
23 | |
24 | static constexpr size_t kSize = 4096; |
25 | |
26 | using ::testing::Eq; |
27 | |
28 | TEST(BufferQueueTest, API) { |
29 | bool Success = false; |
30 | BufferQueue Buffers(kSize, 1, Success); |
31 | ASSERT_TRUE(Success); |
32 | } |
33 | |
34 | TEST(BufferQueueTest, GetAndRelease) { |
35 | bool Success = false; |
36 | BufferQueue Buffers(kSize, 1, Success); |
37 | ASSERT_TRUE(Success); |
38 | BufferQueue::Buffer Buf; |
39 | ASSERT_EQ(Buffers.getBuffer(Buf), BufferQueue::ErrorCode::Ok); |
40 | ASSERT_NE(nullptr, Buf.Data); |
41 | ASSERT_EQ(Buffers.releaseBuffer(Buf), BufferQueue::ErrorCode::Ok); |
42 | ASSERT_EQ(nullptr, Buf.Data); |
43 | } |
44 | |
45 | TEST(BufferQueueTest, GetUntilFailed) { |
46 | bool Success = false; |
47 | BufferQueue Buffers(kSize, 1, Success); |
48 | ASSERT_TRUE(Success); |
49 | BufferQueue::Buffer Buf0; |
50 | EXPECT_EQ(Buffers.getBuffer(Buf&: Buf0), BufferQueue::ErrorCode::Ok); |
51 | BufferQueue::Buffer Buf1; |
52 | EXPECT_EQ(BufferQueue::ErrorCode::NotEnoughMemory, Buffers.getBuffer(Buf&: Buf1)); |
53 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: Buf0), BufferQueue::ErrorCode::Ok); |
54 | } |
55 | |
56 | TEST(BufferQueueTest, ReleaseUnknown) { |
57 | bool Success = false; |
58 | BufferQueue Buffers(kSize, 1, Success); |
59 | ASSERT_TRUE(Success); |
60 | BufferQueue::Buffer Buf; |
61 | Buf.Data = reinterpret_cast<void *>(0xdeadbeef); |
62 | Buf.Size = kSize; |
63 | Buf.Generation = Buffers.generation(); |
64 | |
65 | BufferQueue::Buffer Known; |
66 | EXPECT_THAT(Buffers.getBuffer(Buf&: Known), Eq(BufferQueue::ErrorCode::Ok)); |
67 | EXPECT_THAT(Buffers.releaseBuffer(Buf), |
68 | Eq(BufferQueue::ErrorCode::UnrecognizedBuffer)); |
69 | EXPECT_THAT(Buffers.releaseBuffer(Buf&: Known), Eq(BufferQueue::ErrorCode::Ok)); |
70 | } |
71 | |
72 | TEST(BufferQueueTest, ErrorsWhenFinalising) { |
73 | bool Success = false; |
74 | BufferQueue Buffers(kSize, 2, Success); |
75 | ASSERT_TRUE(Success); |
76 | BufferQueue::Buffer Buf; |
77 | ASSERT_EQ(Buffers.getBuffer(Buf), BufferQueue::ErrorCode::Ok); |
78 | ASSERT_NE(nullptr, Buf.Data); |
79 | ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); |
80 | BufferQueue::Buffer OtherBuf; |
81 | ASSERT_EQ(BufferQueue::ErrorCode::QueueFinalizing, |
82 | Buffers.getBuffer(Buf&: OtherBuf)); |
83 | ASSERT_EQ(BufferQueue::ErrorCode::QueueFinalizing, Buffers.finalize()); |
84 | ASSERT_EQ(Buffers.releaseBuffer(Buf), BufferQueue::ErrorCode::Ok); |
85 | } |
86 | |
87 | TEST(BufferQueueTest, MultiThreaded) { |
88 | bool Success = false; |
89 | BufferQueue Buffers(kSize, 100, Success); |
90 | ASSERT_TRUE(Success); |
91 | auto F = [&] { |
92 | BufferQueue::Buffer B; |
93 | while (true) { |
94 | auto EC = Buffers.getBuffer(Buf&: B); |
95 | if (EC != BufferQueue::ErrorCode::Ok) |
96 | return; |
97 | Buffers.releaseBuffer(Buf&: B); |
98 | } |
99 | }; |
100 | auto T0 = std::async(policy: std::launch::async, fn&: F); |
101 | auto T1 = std::async(policy: std::launch::async, fn&: F); |
102 | auto T2 = std::async(policy: std::launch::async, fn: [&] { |
103 | while (Buffers.finalize() != BufferQueue::ErrorCode::Ok) |
104 | ; |
105 | }); |
106 | F(); |
107 | } |
108 | |
109 | TEST(BufferQueueTest, Apply) { |
110 | bool Success = false; |
111 | BufferQueue Buffers(kSize, 10, Success); |
112 | ASSERT_TRUE(Success); |
113 | auto Count = 0; |
114 | BufferQueue::Buffer B; |
115 | for (int I = 0; I < 10; ++I) { |
116 | ASSERT_EQ(Buffers.getBuffer(Buf&: B), BufferQueue::ErrorCode::Ok); |
117 | ASSERT_EQ(Buffers.releaseBuffer(Buf&: B), BufferQueue::ErrorCode::Ok); |
118 | } |
119 | Buffers.apply(Fn: [&](const BufferQueue::Buffer &B) { ++Count; }); |
120 | ASSERT_EQ(Count, 10); |
121 | } |
122 | |
123 | TEST(BufferQueueTest, GenerationalSupport) { |
124 | bool Success = false; |
125 | BufferQueue Buffers(kSize, 10, Success); |
126 | ASSERT_TRUE(Success); |
127 | BufferQueue::Buffer B0; |
128 | ASSERT_EQ(Buffers.getBuffer(Buf&: B0), BufferQueue::ErrorCode::Ok); |
129 | ASSERT_EQ(Buffers.finalize(), |
130 | BufferQueue::ErrorCode::Ok); // No more new buffers. |
131 | |
132 | // Re-initialise the queue. |
133 | ASSERT_EQ(Buffers.init(BS: kSize, BC: 10), BufferQueue::ErrorCode::Ok); |
134 | |
135 | BufferQueue::Buffer B1; |
136 | ASSERT_EQ(Buffers.getBuffer(Buf&: B1), BufferQueue::ErrorCode::Ok); |
137 | |
138 | // Validate that the buffers come from different generations. |
139 | ASSERT_NE(B0.Generation, B1.Generation); |
140 | |
141 | // We stash the current generation, for use later. |
142 | auto PrevGen = B1.Generation; |
143 | |
144 | // At this point, we want to ensure that we can return the buffer from the |
145 | // first "generation" would still be accepted in the new generation... |
146 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: B0), BufferQueue::ErrorCode::Ok); |
147 | |
148 | // ... and that the new buffer is also accepted. |
149 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: B1), BufferQueue::ErrorCode::Ok); |
150 | |
151 | // A next round will do the same, ensure that we are able to do multiple |
152 | // rounds in this case. |
153 | ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); |
154 | ASSERT_EQ(Buffers.init(BS: kSize, BC: 10), BufferQueue::ErrorCode::Ok); |
155 | EXPECT_EQ(Buffers.getBuffer(Buf&: B0), BufferQueue::ErrorCode::Ok); |
156 | EXPECT_EQ(Buffers.getBuffer(Buf&: B1), BufferQueue::ErrorCode::Ok); |
157 | |
158 | // Here we ensure that the generation is different from the previous |
159 | // generation. |
160 | EXPECT_NE(B0.Generation, PrevGen); |
161 | EXPECT_EQ(B1.Generation, B1.Generation); |
162 | ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); |
163 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: B0), BufferQueue::ErrorCode::Ok); |
164 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: B1), BufferQueue::ErrorCode::Ok); |
165 | } |
166 | |
167 | TEST(BufferQueueTest, GenerationalSupportAcrossThreads) { |
168 | bool Success = false; |
169 | BufferQueue Buffers(kSize, 10, Success); |
170 | ASSERT_TRUE(Success); |
171 | |
172 | std::atomic<int> Counter{0}; |
173 | |
174 | // This function allows us to use thread-local storage to isolate the |
175 | // instances of the buffers to be used. It also allows us signal the threads |
176 | // of a new generation, and allow those to get new buffers. This is |
177 | // representative of how we expect the buffer queue to be used by the XRay |
178 | // runtime. |
179 | auto Process = [&] { |
180 | thread_local BufferQueue::Buffer B; |
181 | ASSERT_EQ(Buffers.getBuffer(Buf&: B), BufferQueue::ErrorCode::Ok); |
182 | auto FirstGen = B.Generation; |
183 | |
184 | // Signal that we've gotten a buffer in the thread. |
185 | Counter.fetch_add(i: 1, m: std::memory_order_acq_rel); |
186 | while (!Buffers.finalizing()) { |
187 | Buffers.releaseBuffer(Buf&: B); |
188 | Buffers.getBuffer(Buf&: B); |
189 | } |
190 | |
191 | // Signal that we've exited the get/release buffer loop. |
192 | Counter.fetch_sub(i: 1, m: std::memory_order_acq_rel); |
193 | if (B.Data != nullptr) |
194 | Buffers.releaseBuffer(Buf&: B); |
195 | |
196 | // Spin until we find that the Buffer Queue is no longer finalizing. |
197 | while (Buffers.getBuffer(Buf&: B) != BufferQueue::ErrorCode::Ok) |
198 | ; |
199 | |
200 | // Signal that we've successfully gotten a buffer in the thread. |
201 | Counter.fetch_add(i: 1, m: std::memory_order_acq_rel); |
202 | |
203 | EXPECT_NE(FirstGen, B.Generation); |
204 | EXPECT_EQ(Buffers.releaseBuffer(Buf&: B), BufferQueue::ErrorCode::Ok); |
205 | |
206 | // Signal that we've successfully exited. |
207 | Counter.fetch_sub(i: 1, m: std::memory_order_acq_rel); |
208 | }; |
209 | |
210 | // Spawn two threads running Process. |
211 | std::thread T0(Process), T1(Process); |
212 | |
213 | // Spin until we find the counter is up to 2. |
214 | while (Counter.load(m: std::memory_order_acquire) != 2) |
215 | ; |
216 | |
217 | // Then we finalize, then re-initialize immediately. |
218 | Buffers.finalize(); |
219 | |
220 | // Spin until we find the counter is down to 0. |
221 | while (Counter.load(m: std::memory_order_acquire) != 0) |
222 | ; |
223 | |
224 | // Then we re-initialize. |
225 | EXPECT_EQ(Buffers.init(BS: kSize, BC: 10), BufferQueue::ErrorCode::Ok); |
226 | |
227 | T0.join(); |
228 | T1.join(); |
229 | |
230 | ASSERT_EQ(Counter.load(m: std::memory_order_acquire), 0); |
231 | } |
232 | |
233 | } // namespace |
234 | } // namespace __xray |
235 | |