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(Buf0), BufferQueue::ErrorCode::Ok); 51 BufferQueue::Buffer Buf1; 52 EXPECT_EQ(BufferQueue::ErrorCode::NotEnoughMemory, Buffers.getBuffer(Buf1)); 53 EXPECT_EQ(Buffers.releaseBuffer(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(Known), Eq(BufferQueue::ErrorCode::Ok)); 67 EXPECT_THAT(Buffers.releaseBuffer(Buf), 68 Eq(BufferQueue::ErrorCode::UnrecognizedBuffer)); 69 EXPECT_THAT(Buffers.releaseBuffer(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(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(B); 95 if (EC != BufferQueue::ErrorCode::Ok) 96 return; 97 Buffers.releaseBuffer(B); 98 } 99 }; 100 auto T0 = std::async(std::launch::async, F); 101 auto T1 = std::async(std::launch::async, F); 102 auto T2 = std::async(std::launch::async, [&] { 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(B), BufferQueue::ErrorCode::Ok); 117 ASSERT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); 118 } 119 Buffers.apply([&](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(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(kSize, 10), BufferQueue::ErrorCode::Ok); 134 135 BufferQueue::Buffer B1; 136 ASSERT_EQ(Buffers.getBuffer(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(B0), BufferQueue::ErrorCode::Ok); 147 148 // ... and that the new buffer is also accepted. 149 EXPECT_EQ(Buffers.releaseBuffer(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(kSize, 10), BufferQueue::ErrorCode::Ok); 155 EXPECT_EQ(Buffers.getBuffer(B0), BufferQueue::ErrorCode::Ok); 156 EXPECT_EQ(Buffers.getBuffer(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(B0), BufferQueue::ErrorCode::Ok); 164 EXPECT_EQ(Buffers.releaseBuffer(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(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(1, std::memory_order_acq_rel); 186 while (!Buffers.finalizing()) { 187 Buffers.releaseBuffer(B); 188 Buffers.getBuffer(B); 189 } 190 191 // Signal that we've exited the get/release buffer loop. 192 Counter.fetch_sub(1, std::memory_order_acq_rel); 193 if (B.Data != nullptr) 194 Buffers.releaseBuffer(B); 195 196 // Spin until we find that the Buffer Queue is no longer finalizing. 197 while (Buffers.getBuffer(B) != BufferQueue::ErrorCode::Ok) 198 ; 199 200 // Signal that we've successfully gotten a buffer in the thread. 201 Counter.fetch_add(1, std::memory_order_acq_rel); 202 203 EXPECT_NE(FirstGen, B.Generation); 204 EXPECT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); 205 206 // Signal that we've successfully exited. 207 Counter.fetch_sub(1, 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(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(std::memory_order_acquire) != 0) 222 ; 223 224 // Then we re-initialize. 225 EXPECT_EQ(Buffers.init(kSize, 10), BufferQueue::ErrorCode::Ok); 226 227 T0.join(); 228 T1.join(); 229 230 ASSERT_EQ(Counter.load(std::memory_order_acquire), 0); 231 } 232 233 } // namespace 234 } // namespace __xray 235