| /* |
| * Copyright 2014 Google Inc. All Rights Reserved. |
| * |
| * 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 |
| * |
| * http://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 "nb/concurrent_ptr.h" |
| |
| #include "nb/simple_thread.h" |
| #include "starboard/common/atomic.h" |
| #include "starboard/common/semaphore.h" |
| #include "starboard/thread.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| // Number of threads for the thread test. |
| #define NUM_THREADS 8 |
| |
| namespace nb { |
| |
| using atomic_int32 = starboard::atomic_int32_t; |
| using atomic_int64 = starboard::atomic_int64_t; |
| using Semaphore = starboard::Semaphore; |
| |
| struct CountLifetime { |
| explicit CountLifetime(atomic_int32* shared_int) { |
| shared_int_ = shared_int; |
| shared_int_->increment(); |
| } |
| |
| ~CountLifetime() { shared_int_->decrement(); } |
| |
| atomic_int32* shared_int_; |
| }; |
| |
| TEST(ConcurrentPtr, nullptr) { |
| ConcurrentPtr<int> concurrent_ptr(nullptr); |
| ConcurrentPtr<int>::Access access_ptr = |
| concurrent_ptr.access_ptr(SbThreadGetId()); |
| ASSERT_FALSE(access_ptr); |
| ASSERT_FALSE(access_ptr.valid()); |
| ASSERT_EQ(access_ptr.get(), nullptr); |
| access_ptr.release(); |
| concurrent_ptr.reset(nullptr); |
| } |
| |
| TEST(ConcurrentPtr, UseSingleThreaded) { |
| int* int_ptr = new int(1234); |
| ConcurrentPtr<int> concurrent_ptr(int_ptr); |
| ConcurrentPtr<int>::Access access_ptr = |
| concurrent_ptr.access_ptr(SbThreadGetId()); |
| |
| ASSERT_TRUE(access_ptr); |
| ASSERT_TRUE(access_ptr.valid()); |
| ASSERT_EQ(int_ptr, access_ptr.get()); |
| ASSERT_EQ(int_ptr, &(*access_ptr)); |
| access_ptr.release(); |
| int* int_ptr2 = concurrent_ptr.release(); |
| ASSERT_EQ(int_ptr, int_ptr2); |
| ASSERT_EQ(1234, *int_ptr); |
| delete int_ptr; |
| } |
| |
| TEST(ConcurrentPtr, ResetDeltesInternalObject) { |
| atomic_int32 counter(0); |
| |
| CountLifetime* ptr = new CountLifetime(&counter); |
| EXPECT_EQ(1, counter.load()); |
| |
| ConcurrentPtr<CountLifetime> concurrent_ptr(ptr); |
| concurrent_ptr.reset(nullptr); |
| EXPECT_EQ(0, counter.load()); |
| } |
| |
| TEST(ConcurrentPtr, UsePointerOperators) { |
| ConcurrentPtr<atomic_int32> concurrent_ptr(new atomic_int32()); |
| ConcurrentPtr<atomic_int32>::Access access_ptr = |
| concurrent_ptr.access_ptr(SbThreadGetId()); |
| ASSERT_TRUE(access_ptr.valid()); |
| ASSERT_TRUE(access_ptr.get()); |
| |
| { |
| atomic_int32& int_ref = *access_ptr; |
| ASSERT_EQ(0, int_ref.load()); |
| } |
| |
| ASSERT_EQ(0, access_ptr->load()); |
| access_ptr->store(2); |
| ASSERT_EQ(2, access_ptr->load()); |
| } |
| |
| class ConcurrentPtrThreadWorker : public SimpleThread { |
| public: |
| ConcurrentPtrThreadWorker(Semaphore* thread_seal, |
| ConcurrentPtr<atomic_int32>* concurrent_ptr) |
| : SimpleThread("ConcurrentPtrThreadWorker"), |
| thread_seal_(thread_seal), |
| concurrent_ptr_(concurrent_ptr), |
| finished_(false) { |
| Start(); |
| } |
| |
| ConcurrentPtrThreadWorker(const ConcurrentPtrThreadWorker&) = delete; |
| |
| ~ConcurrentPtrThreadWorker() { |
| finished_ = true; |
| Join(); |
| } |
| |
| virtual void Run() { |
| thread_seal_->Take(); // Pause until the main thread unblocks us. |
| int64_t yield_counter = 0; |
| while (!finished_) { |
| if (yield_counter++ % 512 == 0) { // Be nice to other threads. |
| SbThreadYield(); |
| } |
| ConcurrentPtr<atomic_int32>::Access access_ptr = |
| concurrent_ptr_->access_ptr(SbThreadGetId()); |
| if (access_ptr) { |
| access_ptr->load(); |
| access_ptr->store(SbThreadGetId()); |
| } |
| } |
| } |
| |
| virtual void Cancel() { finished_ = true; } |
| |
| private: |
| Semaphore* thread_seal_; |
| ConcurrentPtr<atomic_int32>* concurrent_ptr_; |
| bool finished_; |
| }; |
| |
| // Tests the expectation that the ConcurrentPtr can have it's underlying |
| // pointer repeatedly reset and that all the reading threads will be guaranteed |
| // to either be able to lock the pointer for it's use or be receive nullptr. |
| TEST(ConcurrentPtr, ThreadStressTest) { |
| Semaphore thread_seal; |
| ConcurrentPtr<atomic_int32> concurrent_ptr(new atomic_int32); |
| |
| std::vector<ConcurrentPtrThreadWorker*> threads; |
| const int kThreads = NUM_THREADS; |
| for (int i = 0; i < kThreads; ++i) { |
| threads.push_back( |
| new ConcurrentPtrThreadWorker(&thread_seal, &concurrent_ptr)); |
| } |
| |
| // Launch threads. |
| for (int i = 0; i < kThreads; ++i) { |
| thread_seal.Put(); |
| } |
| |
| // Repeatedly reset the underlying pointer. If there is a race |
| // condition then the program will crash. |
| const SbTime start_time = SbTimeGetNow(); |
| const SbTime end_time = start_time + (kSbTimeMillisecond * 100); |
| while (SbTimeGetNow() < end_time) { |
| concurrent_ptr.reset(nullptr); |
| SbThreadYield(); |
| concurrent_ptr.reset(new atomic_int32); |
| concurrent_ptr.reset(new atomic_int32); |
| SbThreadYield(); |
| } |
| |
| for (auto it = threads.begin(); it != threads.end(); ++it) { |
| delete *it; |
| } |
| threads.clear(); |
| } |
| |
| } // namespace nb. |