Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "base/no_destructor.h" |
| 6 | |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 7 | #include <atomic> |
David Bienvenu | b4b441e | 2020-09-23 05:49:57 | [diff] [blame] | 8 | #include <memory> |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 9 | #include <string> |
| 10 | #include <utility> |
David Bienvenu | b4b441e | 2020-09-23 05:49:57 | [diff] [blame] | 11 | #include <vector> |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 12 | |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 13 | #include "base/barrier_closure.h" |
Hans Wennborg | c3cffa6 | 2020-04-27 10:09:12 | [diff] [blame] | 14 | #include "base/check.h" |
Avi Drissman | 63e1f99 | 2023-01-13 18:54:43 | [diff] [blame] | 15 | #include "base/functional/bind.h" |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 16 | #include "base/notreached.h" |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 17 | #include "base/system/sys_info.h" |
| 18 | #include "base/threading/platform_thread.h" |
| 19 | #include "base/threading/simple_thread.h" |
Jose Dapena Paz | 4b136a9 | 2018-03-19 20:11:20 | [diff] [blame] | 20 | #include "build/build_config.h" |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 21 | #include "testing/gtest/include/gtest/gtest.h" |
| 22 | |
| 23 | namespace base { |
| 24 | |
| 25 | namespace { |
| 26 | |
Daniel Cheng | 117c6a9 | 2022-10-10 23:47:28 | [diff] [blame] | 27 | static_assert(!std::is_trivially_destructible_v<std::string>); |
| 28 | static_assert( |
| 29 | std::is_trivially_destructible_v<base::NoDestructor<std::string>>); |
| 30 | |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 31 | struct NotreachedOnDestroy { |
| 32 | ~NotreachedOnDestroy() { NOTREACHED(); } |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 33 | }; |
| 34 | |
| 35 | TEST(NoDestructorTest, SkipsDestructors) { |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 36 | NoDestructor<NotreachedOnDestroy> destructor_should_not_run; |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 37 | } |
| 38 | |
Daniel Cheng | 13fe006d | 2020-10-29 07:33:15 | [diff] [blame] | 39 | struct UncopyableUnmovable { |
| 40 | UncopyableUnmovable() = default; |
| 41 | explicit UncopyableUnmovable(int value) : value(value) {} |
| 42 | |
| 43 | UncopyableUnmovable(const UncopyableUnmovable&) = delete; |
| 44 | UncopyableUnmovable& operator=(const UncopyableUnmovable&) = delete; |
| 45 | |
| 46 | int value = 1; |
Avi Drissman | ded7717 | 2021-07-02 18:23:00 | [diff] [blame] | 47 | std::string something_with_a_nontrivial_destructor; |
Daniel Cheng | 13fe006d | 2020-10-29 07:33:15 | [diff] [blame] | 48 | }; |
| 49 | |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 50 | struct CopyOnly { |
| 51 | CopyOnly() = default; |
| 52 | |
| 53 | CopyOnly(const CopyOnly&) = default; |
| 54 | CopyOnly& operator=(const CopyOnly&) = default; |
| 55 | |
| 56 | CopyOnly(CopyOnly&&) = delete; |
| 57 | CopyOnly& operator=(CopyOnly&&) = delete; |
| 58 | }; |
| 59 | |
| 60 | struct MoveOnly { |
| 61 | MoveOnly() = default; |
| 62 | |
| 63 | MoveOnly(const MoveOnly&) = delete; |
| 64 | MoveOnly& operator=(const MoveOnly&) = delete; |
| 65 | |
| 66 | MoveOnly(MoveOnly&&) = default; |
| 67 | MoveOnly& operator=(MoveOnly&&) = default; |
| 68 | }; |
| 69 | |
| 70 | struct ForwardingTestStruct { |
| 71 | ForwardingTestStruct(const CopyOnly&, MoveOnly&&) {} |
Avi Drissman | ded7717 | 2021-07-02 18:23:00 | [diff] [blame] | 72 | |
| 73 | std::string something_with_a_nontrivial_destructor; |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 74 | }; |
| 75 | |
Daniel Cheng | 13fe006d | 2020-10-29 07:33:15 | [diff] [blame] | 76 | TEST(NoDestructorTest, UncopyableUnmovable) { |
| 77 | static NoDestructor<UncopyableUnmovable> default_constructed; |
| 78 | EXPECT_EQ(1, default_constructed->value); |
| 79 | |
| 80 | static NoDestructor<UncopyableUnmovable> constructed_with_arg(-1); |
| 81 | EXPECT_EQ(-1, constructed_with_arg->value); |
| 82 | } |
| 83 | |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 84 | TEST(NoDestructorTest, ForwardsArguments) { |
| 85 | CopyOnly copy_only; |
| 86 | MoveOnly move_only; |
| 87 | |
| 88 | static NoDestructor<ForwardingTestStruct> test_forwarding( |
| 89 | copy_only, std::move(move_only)); |
| 90 | } |
| 91 | |
| 92 | TEST(NoDestructorTest, Accessors) { |
| 93 | static NoDestructor<std::string> awesome("awesome"); |
| 94 | |
| 95 | EXPECT_EQ("awesome", *awesome); |
| 96 | EXPECT_EQ(0, awesome->compare("awesome")); |
| 97 | EXPECT_EQ(0, awesome.get()->compare("awesome")); |
| 98 | } |
| 99 | |
Jose Dapena Paz | 4b136a9 | 2018-03-19 20:11:20 | [diff] [blame] | 100 | // Passing initializer list to a NoDestructor like in this test |
| 101 | // is ambiguous in GCC. |
| 102 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84849 |
| 103 | #if !defined(COMPILER_GCC) && !defined(__clang__) |
Daniel Cheng | bae2473 | 2018-02-14 19:42:14 | [diff] [blame] | 104 | TEST(NoDestructorTest, InitializerList) { |
| 105 | static NoDestructor<std::vector<std::string>> vector({"a", "b", "c"}); |
| 106 | } |
Jose Dapena Paz | 4b136a9 | 2018-03-19 20:11:20 | [diff] [blame] | 107 | #endif |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 108 | } // namespace |
| 109 | |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 110 | namespace { |
| 111 | |
| 112 | // A class whose constructor busy-loops until it is told to complete |
| 113 | // construction. |
| 114 | class BlockingConstructor { |
| 115 | public: |
| 116 | BlockingConstructor() { |
| 117 | EXPECT_FALSE(WasConstructorCalled()); |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 118 | constructor_called_.store(true, std::memory_order_relaxed); |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 119 | EXPECT_TRUE(WasConstructorCalled()); |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 120 | while (!complete_construction_.load(std::memory_order_relaxed)) { |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 121 | PlatformThread::YieldCurrentThread(); |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 122 | } |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 123 | done_construction_ = true; |
| 124 | } |
David Bienvenu | b4b441e | 2020-09-23 05:49:57 | [diff] [blame] | 125 | BlockingConstructor(const BlockingConstructor&) = delete; |
| 126 | BlockingConstructor& operator=(const BlockingConstructor&) = delete; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 127 | ~BlockingConstructor() = delete; |
| 128 | |
| 129 | // Returns true if BlockingConstructor() was entered. |
| 130 | static bool WasConstructorCalled() { |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 131 | return constructor_called_.load(std::memory_order_relaxed); |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | // Instructs BlockingConstructor() that it may now unblock its construction. |
| 135 | static void CompleteConstructionNow() { |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 136 | complete_construction_.store(true, std::memory_order_relaxed); |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 137 | } |
| 138 | |
David Bienvenu | b4b441e | 2020-09-23 05:49:57 | [diff] [blame] | 139 | bool done_construction() const { return done_construction_; } |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 140 | |
| 141 | private: |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 142 | static std::atomic<bool> constructor_called_; |
| 143 | static std::atomic<bool> complete_construction_; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 144 | |
| 145 | bool done_construction_ = false; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 146 | }; |
| 147 | |
| 148 | // static |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 149 | std::atomic<bool> BlockingConstructor::constructor_called_ = false; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 150 | // static |
Benoît Lizé | aac3718 | 2024-12-09 12:42:24 | [diff] [blame] | 151 | std::atomic<bool> BlockingConstructor::complete_construction_ = false; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 152 | |
Zhibo Wang | d9e4a00 | 2022-07-07 04:34:59 | [diff] [blame] | 153 | // A SimpleThread running at |thread_type| which invokes |before_get| (optional) |
| 154 | // and then invokes thread-safe scoped-static-initializationconstruction on its |
| 155 | // NoDestructor instance. |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 156 | class BlockingConstructorThread : public SimpleThread { |
| 157 | public: |
Zhibo Wang | d9e4a00 | 2022-07-07 04:34:59 | [diff] [blame] | 158 | BlockingConstructorThread(ThreadType thread_type, OnceClosure before_get) |
| 159 | : SimpleThread("BlockingConstructorThread", Options(thread_type)), |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 160 | before_get_(std::move(before_get)) {} |
David Bienvenu | b4b441e | 2020-09-23 05:49:57 | [diff] [blame] | 161 | BlockingConstructorThread(const BlockingConstructorThread&) = delete; |
| 162 | BlockingConstructorThread& operator=(const BlockingConstructorThread&) = |
| 163 | delete; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 164 | |
| 165 | void Run() override { |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 166 | if (before_get_) { |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 167 | std::move(before_get_).Run(); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 168 | } |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 169 | |
| 170 | static NoDestructor<BlockingConstructor> instance; |
| 171 | EXPECT_TRUE(instance->done_construction()); |
| 172 | } |
| 173 | |
| 174 | private: |
| 175 | OnceClosure before_get_; |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 176 | }; |
| 177 | |
| 178 | } // namespace |
| 179 | |
| 180 | // Tests that if the thread assigned to construct the local-static |
| 181 | // initialization of the NoDestructor runs at background priority : the |
| 182 | // foreground threads will yield to it enough for it to eventually complete |
| 183 | // construction. While local-static thread-safe initialization isn't specific to |
| 184 | // NoDestructor, it is tested here as NoDestructor is set to replace |
| 185 | // LazyInstance and this is an important regression test for it |
| 186 | // (https://crbug.com/797129). |
| 187 | TEST(NoDestructorTest, PriorityInversionAtStaticInitializationResolves) { |
| 188 | TimeTicks test_begin = TimeTicks::Now(); |
| 189 | |
Bruce Dawson | 232b916 | 2022-01-20 17:37:23 | [diff] [blame] | 190 | // Construct BlockingConstructor from a thread that is lower priority than the |
| 191 | // other threads that will be constructed. This thread used to be BACKGROUND |
| 192 | // priority but that caused it to be starved by other simultaneously running |
| 193 | // test processes, leading to false-positive failures. |
Zhibo Wang | d9e4a00 | 2022-07-07 04:34:59 | [diff] [blame] | 194 | BlockingConstructorThread background_getter(ThreadType::kDefault, |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 195 | OnceClosure()); |
| 196 | background_getter.Start(); |
| 197 | |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 198 | while (!BlockingConstructor::WasConstructorCalled()) { |
Peter Kasting | 53fd6ee | 2021-10-05 20:40:48 | [diff] [blame] | 199 | PlatformThread::Sleep(Milliseconds(1)); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 200 | } |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 201 | |
| 202 | // Spin 4 foreground thread per core contending to get the already under |
| 203 | // construction NoDestructor. When they are all running and poking at it : |
| 204 | // allow the background thread to complete its work. |
| 205 | const int kNumForegroundThreads = 4 * SysInfo::NumberOfProcessors(); |
| 206 | std::vector<std::unique_ptr<SimpleThread>> foreground_threads; |
| 207 | RepeatingClosure foreground_thread_ready_callback = |
| 208 | BarrierClosure(kNumForegroundThreads, |
| 209 | BindOnce(&BlockingConstructor::CompleteConstructionNow)); |
| 210 | for (int i = 0; i < kNumForegroundThreads; ++i) { |
Bruce Dawson | 232b916 | 2022-01-20 17:37:23 | [diff] [blame] | 211 | // Create threads that are higher priority than background_getter. See above |
| 212 | // for why these particular priorities are chosen. |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 213 | foreground_threads.push_back(std::make_unique<BlockingConstructorThread>( |
Zhibo Wang | d9e4a00 | 2022-07-07 04:34:59 | [diff] [blame] | 214 | ThreadType::kDisplayCritical, foreground_thread_ready_callback)); |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 215 | foreground_threads.back()->Start(); |
| 216 | } |
| 217 | |
| 218 | // This test will hang if the foreground threads become stuck in |
| 219 | // NoDestructor's construction per the background thread never being scheduled |
| 220 | // to complete construction. |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 221 | for (auto& foreground_thread : foreground_threads) { |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 222 | foreground_thread->Join(); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 223 | } |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 224 | background_getter.Join(); |
| 225 | |
| 226 | // Fail if this test takes more than 5 seconds (it takes 5-10 seconds on a |
Bruce Dawson | 232b916 | 2022-01-20 17:37:23 | [diff] [blame] | 227 | // Z840 without https://crrev.com/527445 but is expected to be fast (~30ms) |
| 228 | // with the fix). |
Peter Kasting | 53fd6ee | 2021-10-05 20:40:48 | [diff] [blame] | 229 | EXPECT_LT(TimeTicks::Now() - test_begin, Seconds(5)); |
Gabriel Charette | 773e856 | 2019-01-25 22:03:40 | [diff] [blame] | 230 | } |
| 231 | |
Daniel Cheng | 73999fe6 | 2018-01-19 00:28:15 | [diff] [blame] | 232 | } // namespace base |