// Copyright 2019 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/strings/internal/cordz_handle.h" #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/synchronization/internal/thread_pool.h" #include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "absl/time/time.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { namespace { using ::testing::ElementsAre; using ::testing::Gt; using ::testing::IsEmpty; using ::testing::SizeIs; // Local less verbose helper std::vector DeleteQueue() { return CordzHandle::DiagnosticsGetDeleteQueue(); } struct CordzHandleDeleteTracker : public CordzHandle { bool* deleted; explicit CordzHandleDeleteTracker(bool* deleted) : deleted(deleted) {} ~CordzHandleDeleteTracker() override { *deleted = true; } }; TEST(CordzHandleTest, DeleteQueueIsEmpty) { EXPECT_THAT(DeleteQueue(), SizeIs(0)); } TEST(CordzHandleTest, CordzHandleCreateDelete) { bool deleted = false; auto* handle = new CordzHandleDeleteTracker(&deleted); EXPECT_FALSE(handle->is_snapshot()); EXPECT_TRUE(handle->SafeToDelete()); EXPECT_THAT(DeleteQueue(), SizeIs(0)); CordzHandle::Delete(handle); EXPECT_THAT(DeleteQueue(), SizeIs(0)); EXPECT_TRUE(deleted); } TEST(CordzHandleTest, CordzSnapshotCreateDelete) { auto* snapshot = new CordzSnapshot(); EXPECT_TRUE(snapshot->is_snapshot()); EXPECT_TRUE(snapshot->SafeToDelete()); EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot)); delete snapshot; EXPECT_THAT(DeleteQueue(), SizeIs(0)); } TEST(CordzHandleTest, CordzHandleCreateDeleteWithSnapshot) { bool deleted = false; auto* snapshot = new CordzSnapshot(); auto* handle = new CordzHandleDeleteTracker(&deleted); EXPECT_FALSE(handle->SafeToDelete()); CordzHandle::Delete(handle); EXPECT_THAT(DeleteQueue(), ElementsAre(handle, snapshot)); EXPECT_FALSE(deleted); EXPECT_FALSE(handle->SafeToDelete()); delete snapshot; EXPECT_THAT(DeleteQueue(), SizeIs(0)); EXPECT_TRUE(deleted); } TEST(CordzHandleTest, MultiSnapshot) { bool deleted[3] = {false, false, false}; CordzSnapshot* snapshot[3]; CordzHandleDeleteTracker* handle[3]; for (int i = 0; i < 3; ++i) { snapshot[i] = new CordzSnapshot(); handle[i] = new CordzHandleDeleteTracker(&deleted[i]); CordzHandle::Delete(handle[i]); } EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2], handle[1], snapshot[1], handle[0], snapshot[0])); EXPECT_THAT(deleted, ElementsAre(false, false, false)); delete snapshot[1]; EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2], handle[1], handle[0], snapshot[0])); EXPECT_THAT(deleted, ElementsAre(false, false, false)); delete snapshot[0]; EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2])); EXPECT_THAT(deleted, ElementsAre(true, true, false)); delete snapshot[2]; EXPECT_THAT(DeleteQueue(), SizeIs(0)); EXPECT_THAT(deleted, ElementsAre(true, true, deleted)); } TEST(CordzHandleTest, DiagnosticsHandleIsSafeToInspect) { CordzSnapshot snapshot1; EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(nullptr)); auto* handle1 = new CordzHandle(); EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1)); CordzHandle::Delete(handle1); EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1)); CordzSnapshot snapshot2; auto* handle2 = new CordzHandle(); EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1)); EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle2)); EXPECT_FALSE(snapshot2.DiagnosticsHandleIsSafeToInspect(handle1)); EXPECT_TRUE(snapshot2.DiagnosticsHandleIsSafeToInspect(handle2)); CordzHandle::Delete(handle2); EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1)); } TEST(CordzHandleTest, DiagnosticsGetSafeToInspectDeletedHandles) { EXPECT_THAT(DeleteQueue(), IsEmpty()); auto* handle = new CordzHandle(); auto* snapshot1 = new CordzSnapshot(); // snapshot1 should be able to see handle. EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot1)); EXPECT_TRUE(snapshot1->DiagnosticsHandleIsSafeToInspect(handle)); EXPECT_THAT(snapshot1->DiagnosticsGetSafeToInspectDeletedHandles(), IsEmpty()); // This handle will be safe to inspect as long as snapshot1 is alive. However, // since only snapshot1 can prove that it's alive, it will be hidden from // snapshot2. CordzHandle::Delete(handle); // This snapshot shouldn't be able to see handle because handle was already // sent to Delete. auto* snapshot2 = new CordzSnapshot(); // DeleteQueue elements are LIFO order. EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot2, handle, snapshot1)); EXPECT_TRUE(snapshot1->DiagnosticsHandleIsSafeToInspect(handle)); EXPECT_FALSE(snapshot2->DiagnosticsHandleIsSafeToInspect(handle)); EXPECT_THAT(snapshot1->DiagnosticsGetSafeToInspectDeletedHandles(), ElementsAre(handle)); EXPECT_THAT(snapshot2->DiagnosticsGetSafeToInspectDeletedHandles(), IsEmpty()); CordzHandle::Delete(snapshot1); EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot2)); CordzHandle::Delete(snapshot2); EXPECT_THAT(DeleteQueue(), IsEmpty()); } // Create and delete CordzHandle and CordzSnapshot objects in multiple threads // so that tsan has some time to chew on it and look for memory problems. TEST(CordzHandleTest, MultiThreaded) { Notification stop; static constexpr int kNumThreads = 4; // Keep the number of handles relatively small so that the test will naturally // transition to an empty delete queue during the test. If there are, say, 100 // handles, that will virtually never happen. With 10 handles and around 50k // iterations in each of 4 threads, the delete queue appears to become empty // around 200 times. static constexpr int kNumHandles = 10; // Each thread is going to pick a random index and atomically swap its // CordzHandle with one in handles. This way, each thread can avoid // manipulating a CordzHandle that might be operated upon in another thread. std::vector> handles(kNumHandles); absl::synchronization_internal::ThreadPool pool(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { pool.Schedule([&stop, &handles]() { std::minstd_rand gen; std::uniform_int_distribution dist_type(0, 2); std::uniform_int_distribution dist_handle(0, kNumHandles - 1); size_t max_safe_to_inspect = 0; while (!stop.HasBeenNotified()) { CordzHandle* handle; switch (dist_type(gen)) { case 0: handle = new CordzHandle(); break; case 1: handle = new CordzSnapshot(); break; default: handle = nullptr; break; } CordzHandle* old_handle = handles[dist_handle(gen)].exchange(handle); if (old_handle != nullptr) { std::vector safe_to_inspect = old_handle->DiagnosticsGetSafeToInspectDeletedHandles(); for (const CordzHandle* handle : safe_to_inspect) { // We're in a tight loop, so don't generate too many error messages. ASSERT_FALSE(handle->is_snapshot()); } if (safe_to_inspect.size() > max_safe_to_inspect) { max_safe_to_inspect = safe_to_inspect.size(); } CordzHandle::Delete(old_handle); } } // Confirm that the test did *something*. This check will be satisfied as // long as this thread has delete a CordzSnapshot object and a // non-snapshot CordzHandle was deleted after the CordzSnapshot was // created. This max_safe_to_inspect count will often reach around 30 // (assuming 4 threads and 10 slots for live handles). Don't use a strict // bound to avoid making this test flaky. EXPECT_THAT(max_safe_to_inspect, Gt(0)); // Have each thread attempt to clean up everything. Some thread will be // the last to reach this cleanup code, and it will be guaranteed to clean // up everything because nothing remains to create new handles. for (auto& h : handles) { if (CordzHandle* handle = h.exchange(nullptr)) { CordzHandle::Delete(handle); } } }); } // The threads will hammer away. Give it a little bit of time for tsan to // spot errors. absl::SleepFor(absl::Seconds(3)); stop.Notify(); } } // namespace } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl