blob: f9ce9f81568c7e7dbecfa400df2dfc4490ce8ec6 [file] [log] [blame]
/*
* 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/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.