| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2016 The Android Open Source Project | 
|  | 3 | * | 
|  | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 5 | * you may not use this file except in compliance with the License. | 
|  | 6 | * You may obtain a copy of the License at | 
|  | 7 | * | 
|  | 8 | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 9 | * | 
|  | 10 | * Unless required by applicable law or agreed to in writing, software | 
|  | 11 | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 13 | * See the License for the specific language governing permissions and | 
|  | 14 | * limitations under the License. | 
|  | 15 | */ | 
|  | 16 |  | 
|  | 17 | #include <gtest/gtest.h> | 
|  | 18 |  | 
|  | 19 | #include <utils/StrongPointer.h> | 
|  | 20 | #include <utils/RefBase.h> | 
|  | 21 |  | 
|  | 22 | #include <thread> | 
|  | 23 | #include <atomic> | 
|  | 24 | #include <sched.h> | 
|  | 25 | #include <errno.h> | 
|  | 26 |  | 
|  | 27 | // Enhanced version of StrongPointer_test, but using RefBase underneath. | 
|  | 28 |  | 
|  | 29 | using namespace android; | 
|  | 30 |  | 
|  | 31 | static constexpr int NITERS = 1000000; | 
|  | 32 |  | 
|  | 33 | static constexpr int INITIAL_STRONG_VALUE = 1 << 28;  // Mirroring RefBase definition. | 
|  | 34 |  | 
|  | 35 | class Foo : public RefBase { | 
|  | 36 | public: | 
|  | 37 | Foo(bool* deleted_check) : mDeleted(deleted_check) { | 
|  | 38 | *mDeleted = false; | 
|  | 39 | } | 
|  | 40 |  | 
|  | 41 | ~Foo() { | 
|  | 42 | *mDeleted = true; | 
|  | 43 | } | 
|  | 44 | private: | 
|  | 45 | bool* mDeleted; | 
|  | 46 | }; | 
|  | 47 |  | 
| Hans Boehm | 6e75ad6 | 2019-03-13 17:34:46 +0000 | [diff] [blame] | 48 | // A version of Foo that ensures that all objects are allocated at the same | 
|  | 49 | // address. No more than one can be allocated at a time. Thread-hostile. | 
|  | 50 | class FooFixedAlloc : public RefBase { | 
|  | 51 | public: | 
|  | 52 | static void* operator new(size_t size) { | 
|  | 53 | if (mAllocCount != 0) { | 
|  | 54 | abort(); | 
|  | 55 | } | 
|  | 56 | mAllocCount = 1; | 
|  | 57 | if (theMemory == nullptr) { | 
|  | 58 | theMemory = malloc(size); | 
|  | 59 | } | 
|  | 60 | return theMemory; | 
|  | 61 | } | 
|  | 62 |  | 
|  | 63 | static void operator delete(void *p) { | 
|  | 64 | if (mAllocCount != 1 || p != theMemory) { | 
|  | 65 | abort(); | 
|  | 66 | } | 
|  | 67 | mAllocCount = 0; | 
|  | 68 | } | 
|  | 69 |  | 
|  | 70 | FooFixedAlloc(bool* deleted_check) : mDeleted(deleted_check) { | 
|  | 71 | *mDeleted = false; | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | ~FooFixedAlloc() { | 
|  | 75 | *mDeleted = true; | 
|  | 76 | } | 
|  | 77 | private: | 
|  | 78 | bool* mDeleted; | 
|  | 79 | static int mAllocCount; | 
|  | 80 | static void* theMemory; | 
|  | 81 | }; | 
|  | 82 |  | 
|  | 83 | int FooFixedAlloc::mAllocCount(0); | 
|  | 84 | void* FooFixedAlloc::theMemory(nullptr); | 
|  | 85 |  | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 86 | TEST(RefBase, StrongMoves) { | 
|  | 87 | bool isDeleted; | 
|  | 88 | Foo* foo = new Foo(&isDeleted); | 
|  | 89 | ASSERT_EQ(INITIAL_STRONG_VALUE, foo->getStrongCount()); | 
|  | 90 | ASSERT_FALSE(isDeleted) << "Already deleted...?"; | 
|  | 91 | sp<Foo> sp1(foo); | 
|  | 92 | wp<Foo> wp1(sp1); | 
|  | 93 | ASSERT_EQ(1, foo->getStrongCount()); | 
|  | 94 | // Weak count includes both strong and weak references. | 
|  | 95 | ASSERT_EQ(2, foo->getWeakRefs()->getWeakCount()); | 
|  | 96 | { | 
|  | 97 | sp<Foo> sp2 = std::move(sp1); | 
|  | 98 | ASSERT_EQ(1, foo->getStrongCount()) | 
|  | 99 | << "std::move failed, incremented refcnt"; | 
|  | 100 | ASSERT_EQ(nullptr, sp1.get()) << "std::move failed, sp1 is still valid"; | 
|  | 101 | // The strong count isn't increasing, let's double check the old object | 
|  | 102 | // is properly reset and doesn't early delete | 
|  | 103 | sp1 = std::move(sp2); | 
|  | 104 | } | 
|  | 105 | ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!"; | 
|  | 106 | { | 
|  | 107 | // Now let's double check it deletes on time | 
|  | 108 | sp<Foo> sp2 = std::move(sp1); | 
|  | 109 | } | 
|  | 110 | ASSERT_TRUE(isDeleted) << "foo was leaked!"; | 
|  | 111 | ASSERT_TRUE(wp1.promote().get() == nullptr); | 
|  | 112 | } | 
|  | 113 |  | 
|  | 114 | TEST(RefBase, WeakCopies) { | 
|  | 115 | bool isDeleted; | 
|  | 116 | Foo* foo = new Foo(&isDeleted); | 
|  | 117 | EXPECT_EQ(0, foo->getWeakRefs()->getWeakCount()); | 
|  | 118 | ASSERT_FALSE(isDeleted) << "Foo (weak) already deleted...?"; | 
|  | 119 | wp<Foo> wp1(foo); | 
|  | 120 | EXPECT_EQ(1, foo->getWeakRefs()->getWeakCount()); | 
|  | 121 | { | 
|  | 122 | wp<Foo> wp2 = wp1; | 
|  | 123 | ASSERT_EQ(2, foo->getWeakRefs()->getWeakCount()); | 
|  | 124 | } | 
|  | 125 | EXPECT_EQ(1, foo->getWeakRefs()->getWeakCount()); | 
|  | 126 | ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!"; | 
|  | 127 | wp1 = nullptr; | 
| Hans Boehm | 23c857e | 2016-08-02 18:39:30 -0700 | [diff] [blame] | 128 | ASSERT_FALSE(isDeleted) << "Deletion on wp destruction should no longer occur"; | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 129 | } | 
|  | 130 |  | 
| Hans Boehm | 6e75ad6 | 2019-03-13 17:34:46 +0000 | [diff] [blame] | 131 | TEST(RefBase, Comparisons) { | 
|  | 132 | bool isDeleted, isDeleted2, isDeleted3; | 
|  | 133 | Foo* foo = new Foo(&isDeleted); | 
|  | 134 | Foo* foo2 = new Foo(&isDeleted2); | 
|  | 135 | sp<Foo> sp1(foo); | 
|  | 136 | sp<Foo> sp2(foo2); | 
|  | 137 | wp<Foo> wp1(sp1); | 
|  | 138 | wp<Foo> wp2(sp1); | 
|  | 139 | wp<Foo> wp3(sp2); | 
|  | 140 | ASSERT_TRUE(wp1 == wp2); | 
|  | 141 | ASSERT_TRUE(wp1 == sp1); | 
|  | 142 | ASSERT_TRUE(wp3 == sp2); | 
|  | 143 | ASSERT_TRUE(wp1 != sp2); | 
|  | 144 | ASSERT_TRUE(wp1 <= wp2); | 
|  | 145 | ASSERT_TRUE(wp1 >= wp2); | 
|  | 146 | ASSERT_FALSE(wp1 != wp2); | 
|  | 147 | ASSERT_FALSE(wp1 > wp2); | 
|  | 148 | ASSERT_FALSE(wp1 < wp2); | 
|  | 149 | ASSERT_FALSE(sp1 == sp2); | 
|  | 150 | ASSERT_TRUE(sp1 != sp2); | 
|  | 151 | bool sp1_smaller = sp1 < sp2; | 
|  | 152 | wp<Foo>wp_smaller = sp1_smaller ? wp1 : wp3; | 
|  | 153 | wp<Foo>wp_larger = sp1_smaller ? wp3 : wp1; | 
|  | 154 | ASSERT_TRUE(wp_smaller < wp_larger); | 
|  | 155 | ASSERT_TRUE(wp_smaller != wp_larger); | 
|  | 156 | ASSERT_TRUE(wp_smaller <= wp_larger); | 
|  | 157 | ASSERT_FALSE(wp_smaller == wp_larger); | 
|  | 158 | ASSERT_FALSE(wp_smaller > wp_larger); | 
|  | 159 | ASSERT_FALSE(wp_smaller >= wp_larger); | 
|  | 160 | sp2 = nullptr; | 
|  | 161 | ASSERT_TRUE(isDeleted2); | 
|  | 162 | ASSERT_FALSE(isDeleted); | 
|  | 163 | ASSERT_FALSE(wp3 == sp2); | 
|  | 164 | // Comparison results on weak pointers should not be affected. | 
|  | 165 | ASSERT_TRUE(wp_smaller < wp_larger); | 
|  | 166 | ASSERT_TRUE(wp_smaller != wp_larger); | 
|  | 167 | ASSERT_TRUE(wp_smaller <= wp_larger); | 
|  | 168 | ASSERT_FALSE(wp_smaller == wp_larger); | 
|  | 169 | ASSERT_FALSE(wp_smaller > wp_larger); | 
|  | 170 | ASSERT_FALSE(wp_smaller >= wp_larger); | 
|  | 171 | wp2 = nullptr; | 
|  | 172 | ASSERT_FALSE(wp1 == wp2); | 
|  | 173 | ASSERT_TRUE(wp1 != wp2); | 
|  | 174 | wp1.clear(); | 
|  | 175 | ASSERT_TRUE(wp1 == wp2); | 
|  | 176 | ASSERT_FALSE(wp1 != wp2); | 
|  | 177 | wp3.clear(); | 
|  | 178 | ASSERT_TRUE(wp1 == wp3); | 
|  | 179 | ASSERT_FALSE(wp1 != wp3); | 
|  | 180 | ASSERT_FALSE(isDeleted); | 
|  | 181 | sp1.clear(); | 
|  | 182 | ASSERT_TRUE(isDeleted); | 
|  | 183 | ASSERT_TRUE(sp1 == sp2); | 
|  | 184 | // Try to check that null pointers are properly initialized. | 
|  | 185 | { | 
|  | 186 | // Try once with non-null, to maximize chances of getting junk on the | 
|  | 187 | // stack. | 
|  | 188 | sp<Foo> sp3(new Foo(&isDeleted3)); | 
|  | 189 | wp<Foo> wp4(sp3); | 
|  | 190 | wp<Foo> wp5; | 
|  | 191 | ASSERT_FALSE(wp4 == wp5); | 
|  | 192 | ASSERT_TRUE(wp4 != wp5); | 
|  | 193 | ASSERT_FALSE(sp3 == wp5); | 
|  | 194 | ASSERT_FALSE(wp5 == sp3); | 
|  | 195 | ASSERT_TRUE(sp3 != wp5); | 
|  | 196 | ASSERT_TRUE(wp5 != sp3); | 
|  | 197 | ASSERT_TRUE(sp3 == wp4); | 
|  | 198 | } | 
|  | 199 | { | 
|  | 200 | sp<Foo> sp3; | 
|  | 201 | wp<Foo> wp4(sp3); | 
|  | 202 | wp<Foo> wp5; | 
|  | 203 | ASSERT_TRUE(wp4 == wp5); | 
|  | 204 | ASSERT_FALSE(wp4 != wp5); | 
|  | 205 | ASSERT_TRUE(sp3 == wp5); | 
|  | 206 | ASSERT_TRUE(wp5 == sp3); | 
|  | 207 | ASSERT_FALSE(sp3 != wp5); | 
|  | 208 | ASSERT_FALSE(wp5 != sp3); | 
|  | 209 | ASSERT_TRUE(sp3 == wp4); | 
|  | 210 | } | 
|  | 211 | } | 
|  | 212 |  | 
|  | 213 | // Check whether comparison against dead wp works, even if the object referenced | 
|  | 214 | // by the new wp happens to be at the same address. | 
|  | 215 | TEST(RefBase, ReplacedComparison) { | 
|  | 216 | bool isDeleted, isDeleted2; | 
|  | 217 | FooFixedAlloc* foo = new FooFixedAlloc(&isDeleted); | 
|  | 218 | sp<FooFixedAlloc> sp1(foo); | 
|  | 219 | wp<FooFixedAlloc> wp1(sp1); | 
|  | 220 | ASSERT_TRUE(wp1 == sp1); | 
|  | 221 | sp1.clear();  // Deallocates the object. | 
|  | 222 | ASSERT_TRUE(isDeleted); | 
|  | 223 | FooFixedAlloc* foo2 = new FooFixedAlloc(&isDeleted2); | 
|  | 224 | ASSERT_FALSE(isDeleted2); | 
|  | 225 | ASSERT_EQ(foo, foo2);  // Not technically a legal comparison, but ... | 
|  | 226 | sp<FooFixedAlloc> sp2(foo2); | 
|  | 227 | wp<FooFixedAlloc> wp2(sp2); | 
|  | 228 | ASSERT_TRUE(sp2 == wp2); | 
|  | 229 | ASSERT_FALSE(sp2 != wp2); | 
|  | 230 | ASSERT_TRUE(sp2 != wp1); | 
|  | 231 | ASSERT_FALSE(sp2 == wp1); | 
|  | 232 | ASSERT_FALSE(sp2 == sp1);  // sp1 is null. | 
|  | 233 | ASSERT_FALSE(wp1 == wp2);  // wp1 refers to old object. | 
|  | 234 | ASSERT_TRUE(wp1 != wp2); | 
|  | 235 | ASSERT_TRUE(wp1 > wp2 || wp1 < wp2); | 
|  | 236 | ASSERT_TRUE(wp1 >= wp2 || wp1 <= wp2); | 
|  | 237 | ASSERT_FALSE(wp1 >= wp2 && wp1 <= wp2); | 
|  | 238 | ASSERT_FALSE(wp1 == nullptr); | 
|  | 239 | wp1 = sp2; | 
|  | 240 | ASSERT_TRUE(wp1 == wp2); | 
|  | 241 | ASSERT_FALSE(wp1 != wp2); | 
|  | 242 | } | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 243 |  | 
|  | 244 | // Set up a situation in which we race with visit2AndRremove() to delete | 
|  | 245 | // 2 strong references.  Bar destructor checks that there are no early | 
|  | 246 | // deletions and prior updates are visible to destructor. | 
|  | 247 | class Bar : public RefBase { | 
|  | 248 | public: | 
|  | 249 | Bar(std::atomic<int>* delete_count) : mVisited1(false), mVisited2(false), | 
|  | 250 | mDeleteCount(delete_count) { | 
|  | 251 | } | 
|  | 252 |  | 
|  | 253 | ~Bar() { | 
|  | 254 | EXPECT_TRUE(mVisited1); | 
|  | 255 | EXPECT_TRUE(mVisited2); | 
|  | 256 | (*mDeleteCount)++; | 
|  | 257 | } | 
|  | 258 | bool mVisited1; | 
|  | 259 | bool mVisited2; | 
|  | 260 | private: | 
|  | 261 | std::atomic<int>* mDeleteCount; | 
|  | 262 | }; | 
|  | 263 |  | 
|  | 264 | static sp<Bar> buffer; | 
|  | 265 | static std::atomic<bool> bufferFull(false); | 
|  | 266 |  | 
|  | 267 | // Wait until bufferFull has value val. | 
|  | 268 | static inline void waitFor(bool val) { | 
|  | 269 | while (bufferFull != val) {} | 
|  | 270 | } | 
|  | 271 |  | 
|  | 272 | cpu_set_t otherCpus; | 
|  | 273 |  | 
| Hans Boehm | 23c857e | 2016-08-02 18:39:30 -0700 | [diff] [blame] | 274 | // Divide the cpus we're allowed to run on into myCpus and otherCpus. | 
|  | 275 | // Set origCpus to the processors we were originally allowed to run on. | 
|  | 276 | // Return false if origCpus doesn't include at least processors 0 and 1. | 
|  | 277 | static bool setExclusiveCpus(cpu_set_t* origCpus /* out */, | 
|  | 278 | cpu_set_t* myCpus /* out */, cpu_set_t* otherCpus) { | 
|  | 279 | if (sched_getaffinity(0, sizeof(cpu_set_t), origCpus) != 0) { | 
|  | 280 | return false; | 
|  | 281 | } | 
|  | 282 | if (!CPU_ISSET(0,  origCpus) || !CPU_ISSET(1, origCpus)) { | 
|  | 283 | return false; | 
|  | 284 | } | 
|  | 285 | CPU_ZERO(myCpus); | 
|  | 286 | CPU_ZERO(otherCpus); | 
|  | 287 | CPU_OR(myCpus, myCpus, origCpus); | 
|  | 288 | CPU_OR(otherCpus, otherCpus, origCpus); | 
|  | 289 | for (unsigned i = 0; i < CPU_SETSIZE; ++i) { | 
|  | 290 | // I get the even cores, the other thread gets the odd ones. | 
|  | 291 | if (i & 1) { | 
|  | 292 | CPU_CLR(i, myCpus); | 
|  | 293 | } else { | 
|  | 294 | CPU_CLR(i, otherCpus); | 
|  | 295 | } | 
|  | 296 | } | 
|  | 297 | return true; | 
|  | 298 | } | 
|  | 299 |  | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 300 | static void visit2AndRemove() { | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 301 | if (sched_setaffinity(0, sizeof(cpu_set_t), &otherCpus) != 0) { | 
|  | 302 | FAIL() << "setaffinity returned:" << errno; | 
|  | 303 | } | 
|  | 304 | for (int i = 0; i < NITERS; ++i) { | 
|  | 305 | waitFor(true); | 
|  | 306 | buffer->mVisited2 = true; | 
|  | 307 | buffer = nullptr; | 
|  | 308 | bufferFull = false; | 
|  | 309 | } | 
|  | 310 | } | 
|  | 311 |  | 
|  | 312 | TEST(RefBase, RacingDestructors) { | 
|  | 313 | cpu_set_t origCpus; | 
|  | 314 | cpu_set_t myCpus; | 
|  | 315 | // Restrict us and the helper thread to disjoint cpu sets. | 
|  | 316 | // This prevents us from getting scheduled against each other, | 
| Hans Boehm | 23c857e | 2016-08-02 18:39:30 -0700 | [diff] [blame] | 317 | // which would be atrociously slow. | 
|  | 318 | if (setExclusiveCpus(&origCpus, &myCpus, &otherCpus)) { | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 319 | std::thread t(visit2AndRemove); | 
|  | 320 | std::atomic<int> deleteCount(0); | 
| Hans Boehm | 4a8276c | 2016-06-01 15:29:55 -0700 | [diff] [blame] | 321 | if (sched_setaffinity(0, sizeof(cpu_set_t), &myCpus) != 0) { | 
|  | 322 | FAIL() << "setaffinity returned:" << errno; | 
|  | 323 | } | 
|  | 324 | for (int i = 0; i < NITERS; ++i) { | 
|  | 325 | waitFor(false); | 
|  | 326 | Bar* bar = new Bar(&deleteCount); | 
|  | 327 | sp<Bar> sp3(bar); | 
|  | 328 | buffer = sp3; | 
|  | 329 | bufferFull = true; | 
|  | 330 | ASSERT_TRUE(bar->getStrongCount() >= 1); | 
|  | 331 | // Weak count includes strong count. | 
|  | 332 | ASSERT_TRUE(bar->getWeakRefs()->getWeakCount() >= 1); | 
|  | 333 | sp3->mVisited1 = true; | 
|  | 334 | sp3 = nullptr; | 
|  | 335 | } | 
|  | 336 | t.join(); | 
|  | 337 | if (sched_setaffinity(0, sizeof(cpu_set_t), &origCpus) != 0) { | 
|  | 338 | FAIL(); | 
|  | 339 | } | 
|  | 340 | ASSERT_EQ(NITERS, deleteCount) << "Deletions missed!"; | 
|  | 341 | }  // Otherwise this is slow and probably pointless on a uniprocessor. | 
|  | 342 | } | 
| Hans Boehm | 23c857e | 2016-08-02 18:39:30 -0700 | [diff] [blame] | 343 |  | 
|  | 344 | static wp<Bar> wpBuffer; | 
|  | 345 | static std::atomic<bool> wpBufferFull(false); | 
|  | 346 |  | 
|  | 347 | // Wait until wpBufferFull has value val. | 
|  | 348 | static inline void wpWaitFor(bool val) { | 
|  | 349 | while (wpBufferFull != val) {} | 
|  | 350 | } | 
|  | 351 |  | 
|  | 352 | static void visit3AndRemove() { | 
|  | 353 | if (sched_setaffinity(0, sizeof(cpu_set_t), &otherCpus) != 0) { | 
|  | 354 | FAIL() << "setaffinity returned:" << errno; | 
|  | 355 | } | 
|  | 356 | for (int i = 0; i < NITERS; ++i) { | 
|  | 357 | wpWaitFor(true); | 
|  | 358 | { | 
|  | 359 | sp<Bar> sp1 = wpBuffer.promote(); | 
|  | 360 | // We implicitly check that sp1 != NULL | 
|  | 361 | sp1->mVisited2 = true; | 
|  | 362 | } | 
|  | 363 | wpBuffer = nullptr; | 
|  | 364 | wpBufferFull = false; | 
|  | 365 | } | 
|  | 366 | } | 
|  | 367 |  | 
|  | 368 | TEST(RefBase, RacingPromotions) { | 
|  | 369 | cpu_set_t origCpus; | 
|  | 370 | cpu_set_t myCpus; | 
|  | 371 | // Restrict us and the helper thread to disjoint cpu sets. | 
|  | 372 | // This prevents us from getting scheduled against each other, | 
|  | 373 | // which would be atrociously slow. | 
|  | 374 | if (setExclusiveCpus(&origCpus, &myCpus, &otherCpus)) { | 
|  | 375 | std::thread t(visit3AndRemove); | 
|  | 376 | std::atomic<int> deleteCount(0); | 
|  | 377 | if (sched_setaffinity(0, sizeof(cpu_set_t), &myCpus) != 0) { | 
|  | 378 | FAIL() << "setaffinity returned:" << errno; | 
|  | 379 | } | 
|  | 380 | for (int i = 0; i < NITERS; ++i) { | 
|  | 381 | Bar* bar = new Bar(&deleteCount); | 
|  | 382 | wp<Bar> wp1(bar); | 
|  | 383 | bar->mVisited1 = true; | 
|  | 384 | if (i % (NITERS / 10) == 0) { | 
|  | 385 | // Do this rarely, since it generates a log message. | 
|  | 386 | wp1 = nullptr;  // No longer destroys the object. | 
|  | 387 | wp1 = bar; | 
|  | 388 | } | 
|  | 389 | wpBuffer = wp1; | 
|  | 390 | ASSERT_EQ(bar->getWeakRefs()->getWeakCount(), 2); | 
|  | 391 | wpBufferFull = true; | 
|  | 392 | // Promotion races with that in visit3AndRemove. | 
|  | 393 | // This may or may not succeed, but it shouldn't interfere with | 
|  | 394 | // the concurrent one. | 
|  | 395 | sp<Bar> sp1 = wp1.promote(); | 
|  | 396 | wpWaitFor(false);  // Waits for other thread to drop strong pointer. | 
|  | 397 | sp1 = nullptr; | 
|  | 398 | // No strong pointers here. | 
|  | 399 | sp1 = wp1.promote(); | 
|  | 400 | ASSERT_EQ(sp1.get(), nullptr) << "Dead wp promotion succeeded!"; | 
|  | 401 | } | 
|  | 402 | t.join(); | 
|  | 403 | if (sched_setaffinity(0, sizeof(cpu_set_t), &origCpus) != 0) { | 
|  | 404 | FAIL(); | 
|  | 405 | } | 
|  | 406 | ASSERT_EQ(NITERS, deleteCount) << "Deletions missed!"; | 
|  | 407 | }  // Otherwise this is slow and probably pointless on a uniprocessor. | 
|  | 408 | } |