235 lines · cpp
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-exception6//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 21namespace __xray {22namespace {23 24static constexpr size_t kSize = 4096;25 26using ::testing::Eq;27 28TEST(BufferQueueTest, API) {29 bool Success = false;30 BufferQueue Buffers(kSize, 1, Success);31 ASSERT_TRUE(Success);32}33 34TEST(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 45TEST(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 56TEST(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 72TEST(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 87TEST(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 109TEST(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 123TEST(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 the145 // 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 multiple152 // 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 previous159 // 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 167TEST(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 the175 // instances of the buffers to be used. It also allows us signal the threads176 // of a new generation, and allow those to get new buffers. This is177 // representative of how we expect the buffer queue to be used by the XRay178 // 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} // namespace234} // namespace __xray235