// 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); // global bool which is set when any thread did get some 'safe to inspect' // handles. On some platforms and OSS tests, we might risk that some pool // threads are starved, stalled, or just got a few unlikely random 'handle' // coin tosses, so we satisfy this test with simply observing 'some' thread // did something meaningful, which should minimize the potential for flakes. std::atomic found_safe_to_inspect(false); { absl::synchronization_internal::ThreadPool pool(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { pool.Schedule([&stop, &handles, &found_safe_to_inspect]() { std::minstd_rand gen; std::uniform_int_distribution dist_type(0, 2); std::uniform_int_distribution dist_handle(0, kNumHandles - 1); 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.empty()) { found_safe_to_inspect.store(true); } CordzHandle::Delete(old_handle); } } // 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(); } // Confirm that the test did *something*. This check will be satisfied as // long as any thread has deleted a CordzSnapshot object and a non-snapshot // CordzHandle was deleted after the CordzSnapshot was created. // See also comments on `found_safe_to_inspect` EXPECT_TRUE(found_safe_to_inspect.load()); } } // namespace } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl