// 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_sample_token.h" #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/random/random.h" #include "absl/strings/cordz_test_helpers.h" #include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cordz_handle.h" #include "absl/strings/internal/cordz_info.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::Eq; using ::testing::Ne; // Used test values auto constexpr kTrackCordMethod = CordzUpdateTracker::kConstructorString; TEST(CordzSampleTokenTest, IteratorTraits) { static_assert(std::is_copy_constructible::value, ""); static_assert(std::is_copy_assignable::value, ""); static_assert(std::is_move_constructible::value, ""); static_assert(std::is_move_assignable::value, ""); static_assert( std::is_same< std::iterator_traits::iterator_category, std::input_iterator_tag>::value, ""); static_assert( std::is_same::value_type, const CordzInfo&>::value, ""); static_assert( std::is_same< std::iterator_traits::difference_type, ptrdiff_t>::value, ""); static_assert( std::is_same::pointer, const CordzInfo*>::value, ""); static_assert( std::is_same::reference, const CordzInfo&>::value, ""); } TEST(CordzSampleTokenTest, IteratorEmpty) { CordzSampleToken token; EXPECT_THAT(token.begin(), Eq(token.end())); } TEST(CordzSampleTokenTest, Iterator) { TestCordData cord1, cord2, cord3; CordzInfo::TrackCord(cord1.data, kTrackCordMethod); CordzInfo* info1 = cord1.data.cordz_info(); CordzInfo::TrackCord(cord2.data, kTrackCordMethod); CordzInfo* info2 = cord2.data.cordz_info(); CordzInfo::TrackCord(cord3.data, kTrackCordMethod); CordzInfo* info3 = cord3.data.cordz_info(); CordzSampleToken token; std::vector found; for (const CordzInfo& cord_info : token) { found.push_back(&cord_info); } EXPECT_THAT(found, ElementsAre(info3, info2, info1)); info1->Untrack(); info2->Untrack(); info3->Untrack(); } TEST(CordzSampleTokenTest, IteratorEquality) { TestCordData cord1; TestCordData cord2; TestCordData cord3; CordzInfo::TrackCord(cord1.data, kTrackCordMethod); CordzInfo* info1 = cord1.data.cordz_info(); CordzSampleToken token1; // lhs starts with the CordzInfo corresponding to cord1 at the head. CordzSampleToken::Iterator lhs = token1.begin(); CordzInfo::TrackCord(cord2.data, kTrackCordMethod); CordzInfo* info2 = cord2.data.cordz_info(); CordzSampleToken token2; // rhs starts with the CordzInfo corresponding to cord2 at the head. CordzSampleToken::Iterator rhs = token2.begin(); CordzInfo::TrackCord(cord3.data, kTrackCordMethod); CordzInfo* info3 = cord3.data.cordz_info(); // lhs is on cord1 while rhs is on cord2. EXPECT_THAT(lhs, Ne(rhs)); rhs++; // lhs and rhs are both on cord1, but they didn't come from the same // CordzSampleToken. EXPECT_THAT(lhs, Ne(rhs)); lhs++; rhs++; // Both lhs and rhs are done, so they are on nullptr. EXPECT_THAT(lhs, Eq(rhs)); info1->Untrack(); info2->Untrack(); info3->Untrack(); } TEST(CordzSampleTokenTest, MultiThreaded) { Notification stop; static constexpr int kNumThreads = 4; static constexpr int kNumCords = 3; static constexpr int kNumTokens = 3; absl::synchronization_internal::ThreadPool pool(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { pool.Schedule([&stop]() { absl::BitGen gen; TestCordData cords[kNumCords]; std::unique_ptr tokens[kNumTokens]; while (!stop.HasBeenNotified()) { // Randomly perform one of five actions: // 1) Untrack // 2) Track // 3) Iterate over Cords visible to a token. // 4) Unsample // 5) Sample int index = absl::Uniform(gen, 0, kNumCords); if (absl::Bernoulli(gen, 0.5)) { TestCordData& cord = cords[index]; // Track/untrack. if (cord.data.is_profiled()) { // 1) Untrack cord.data.cordz_info()->Untrack(); cord.data.clear_cordz_info();; } else { // 2) Track CordzInfo::TrackCord(cord.data, kTrackCordMethod); } } else { std::unique_ptr& token = tokens[index]; if (token) { if (absl::Bernoulli(gen, 0.5)) { // 3) Iterate over Cords visible to a token. for (const CordzInfo& info : *token) { // This is trivial work to allow us to compile the loop. EXPECT_THAT(info.Next(*token), Ne(&info)); } } else { // 4) Unsample token = nullptr; } } else { // 5) Sample token = absl::make_unique(); } } } for (TestCordData& cord : cords) { CordzInfo::MaybeUntrackCord(cord.data.cordz_info()); } }); } // 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