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