blob: 000c6f4056adf49f18f7835d87d925956eca5bd1 [file] [log] [blame]
// Copyright 2016 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 <map>
#include <string>
#include "nb/thread_local_object.h"
#include "nb/scoped_ptr.h"
#include "starboard/mutex.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
// Similar to C++11 std::atomic<T>.
// Atomic<T> may be instantiated with any TriviallyCopyable type T.
// Atomic<T> is neither copyable nor movable.
// TODO: Lift this class out into the library.
template <typename T>
class Atomic {
public:
// C++11 forbids a copy constructor for std::atomic<T>, it also forbids
// a move operation.
Atomic() : value_() {}
explicit Atomic(T v) : value_(v) {}
// Checks whether the atomic operations on all objects of this type
// are lock-free.
// Returns true if the atomic operations on the objects of this type
// are lock-free, false otherwise.
//
// All atomic types may be implemented using mutexes or other locking
// operations, rather than using the lock-free atomic CPU instructions.
// Atomic types are also allowed to be sometimes lock-free, e.g. if only
// aligned memory accesses are naturally atomic on a given architecture,
// misaligned objects of the same type have to use locks.
bool is_lock_free() const { return false; }
bool is_lock_free() const volatile { return false; }
// Atomically replaces the value of the atomic object
// and returns the value held previously.
T Swap(T new_val) {
int old_value = -1;
{
starboard::ScopedLock lock(mutex_);
old_value = value_;
value_ = new_val;
}
return old_value;
}
// Atomically obtains the value of the atomic object.
T Get() const {
starboard::ScopedLock lock(mutex_);
return value_;
}
// Returns the new updated value after the operation has been applied.
T Add(T val) {
starboard::ScopedLock lock(mutex_);
value_ += val;
return value_;
}
// TrySwap(...) sets the new value if and only if "expected_old_value"
// matches the actual value during the atomic assignment operation. If this
// succeeds then true is returned. If there is a mismatch then the value is
// left unchanged and false is returned.
// Inputs:
// new_value: Attempt to set the value to this new value.
// expected_old_value: A test condition for success. If the actual value
// matches the expected_old_value then the swap will succeed.
// optional_actual_value: If non-null, then the actual value at the time
// of the attempted operation is set to this value.
bool TrySwap(T new_value, T expected_old_value,
T* optional_actual_value) {
starboard::ScopedLock lock(mutex_);
if (optional_actual_value) {
*optional_actual_value = value_;
}
if (expected_old_value == value_) {
value_ = new_value;
return true;
}
return false;
}
private:
T value_;
starboard::Mutex mutex_;
};
// Simple atomic int class. This could be optimized for speed using
// compiler intrinsics for concurrent integer modification.
class AtomicInt : public Atomic<int> {
public:
AtomicInt() : Atomic<int>(0) {}
explicit AtomicInt(int initial_val) : Atomic<int>(initial_val) {}
void Increment() { Add(1); }
void Decrement() { Add(-1); }
};
// Simple atomic bool class. This could be optimized for speed using
// compiler intrinsics for concurrent integer modification.
class AtomicBool : public Atomic<bool> {
public:
AtomicBool() : Atomic<bool>(false) {}
explicit AtomicBool(bool initial_val) : Atomic<bool>(initial_val) {}
};
// AbstractTestThread that is a bare bones class wrapper around Starboard
// thread. Subclasses must override Run().
// TODO: Move this to nplb/thread_helpers.h
class AbstractTestThread {
public:
explicit AbstractTestThread() : thread_(kSbThreadInvalid) {}
virtual ~AbstractTestThread() {}
// Subclasses should override the Run method.
virtual void Run() = 0;
// Calls SbThreadCreate() with default parameters.
void Start() {
SbThreadEntryPoint entry_point = ThreadEntryPoint;
thread_ = SbThreadCreate(
0, // default stack_size.
kSbThreadNoPriority, // default priority.
kSbThreadNoAffinity, // default affinity.
true, // joinable.
"AbstractTestThread",
entry_point,
this);
if (kSbThreadInvalid == thread_) {
ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
}
return;
}
void Join() {
if (!SbThreadJoin(thread_, NULL)) {
ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread.";
}
}
private:
static void* ThreadEntryPoint(void* ptr) {
AbstractTestThread* this_ptr = static_cast<AbstractTestThread*>(ptr);
this_ptr->Run();
return NULL;
}
SbThread thread_;
};
// Simple class that counts the number of instances alive.
struct CountsInstances {
CountsInstances() { s_instances_.Increment(); }
~CountsInstances() { s_instances_.Decrement(); }
static AtomicInt s_instances_;
static int NumInstances() { return s_instances_.Get(); }
static void ResetNumInstances() { s_instances_.Swap(0); }
};
AtomicInt CountsInstances::s_instances_(0);
// A simple thread that just creates the an object from the supplied
// ThreadLocalObject<T> and then exits.
template <typename TYPE>
class CreateThreadLocalObjectThenExit : public AbstractTestThread {
public:
explicit CreateThreadLocalObjectThenExit(
ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {}
virtual void Run() {
// volatile as a defensive measure to prevent compiler from optimizing this
// statement out.
volatile TYPE* val = tlo_->GetOrCreate();
}
ThreadLocalObject<TYPE>* tlo_;
};
// A simple thread that just deletes the object supplied on a thread and then
// exists.
template <typename TYPE>
class DestroyTypeOnThread : public AbstractTestThread {
public:
explicit DestroyTypeOnThread(TYPE* ptr)
: ptr_(ptr) {}
virtual void Run() {
ptr_.reset(NULL); // Destroys the object.
}
private:
nb::scoped_ptr<TYPE> ptr_;
};
// Tests the expectation that a ThreadLocalObject can be simply used by
// the main thread.
TEST(ThreadLocalObject, MainThread) {
ThreadLocalObject<bool> tlo_bool;
EXPECT_TRUE(NULL == tlo_bool.GetIfExists());
bool* the_bool = tlo_bool.GetOrCreate();
EXPECT_TRUE(the_bool != NULL);
EXPECT_FALSE(*the_bool);
*the_bool = true;
EXPECT_TRUE(*(tlo_bool.GetIfExists()));
}
// Tests the expectation that a ThreadLocalObject can be used on
// complex objects type (i.e. non pod types).
TEST(ThreadLocalObject, MainThreadComplexObject) {
typedef std::map<std::string, int> Map;
ThreadLocalObject<Map> map_tlo;
EXPECT_FALSE(map_tlo.GetIfExists());
ASSERT_TRUE(map_tlo.GetOrCreate());
Map* map = map_tlo.GetIfExists();
const Map* const_map = map_tlo.GetIfExists();
ASSERT_TRUE(map);
ASSERT_TRUE(const_map);
// If the object is properly constructed then this find operation
// should succeed.
(*map)["my string"] = 15;
ASSERT_EQ(15, (*map)["my string"]);
}
// Tests that when a ThreadLocalObject is destroyed on the main thread that
// the pointers it contained are also destroyed.
TEST(ThreadLocalObject, DestroysObjectOnTLODestruction) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
// Create the TLO object and then immediately destroy it.
nb::scoped_ptr<TLO> tlo_ptr(new TLO);
tlo_ptr->GetOrCreate(); // Instantiate the internal object.
EXPECT_EQ(1, CountsInstances::NumInstances());
tlo_ptr.reset(NULL); // Should destroy all outstanding allocs.
// Now the TLO is destroyed and therefore the destructor should run on the
// internal object.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that the object can be released and that the pointer
// won't be deleted when the ThreadLocalObject that created it is destroyed.
TEST(ThreadLocalObject, ReleasesObject) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
nb::scoped_ptr<TLO> tlo_ptr(new TLO);
// Instantiate the internal object.
tlo_ptr->GetOrCreate();
// Now release the pointer into the container.
nb::scoped_ptr<CountsInstances> last_ref(tlo_ptr->Release());
// Destroying the TLO should not trigger the destruction of the object,
// because it was released.
tlo_ptr.reset(NULL);
// 1 instance left, which is held in last_ref.
EXPECT_EQ(1, CountsInstances::NumInstances());
last_ref.reset(NULL); // Now the object should be destroyed and the
// instance count drops to 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that a thread that creates an object from
// the ThreadLocalObject store will automatically be destroyed by the
// thread joining.
TEST(ThreadLocalObject, ThreadJoinDestroysObject) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
nb::scoped_ptr<TLO> tlo(new TLO);
{
AbstractTestThread* thread =
new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get());
thread->Start();
thread->Join();
// Once the thread joins, the object should be deleted and the instance
// counter falls to 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
delete thread;
}
tlo.reset(NULL); // Now TLO destructor runs.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that objects created on the main thread are not
// leaked.
TEST(ThreadLocalObject, NoLeaksOnMainThread) {
CountsInstances::ResetNumInstances();
ThreadLocalObject<CountsInstances>* tlo =
new ThreadLocalObject<CountsInstances>;
tlo->EnableDestructionByAnyThread();
// Creates the object on the main thread. This is important because the
// main thread will never join and therefore at-exit functions won't get
// run.
CountsInstances* main_thread_object = tlo->GetOrCreate();
EXPECT_EQ(1, CountsInstances::NumInstances());
// Thread will simply create the thread local object (CountsInstances)
// and then return.
nb::scoped_ptr<AbstractTestThread> thread_ptr(
new CreateThreadLocalObjectThenExit<CountsInstances>(tlo));
thread_ptr->Start(); // Object is now created.
thread_ptr->Join(); // ...then destroyed.
thread_ptr.reset(NULL);
// Only main_thread_object should be alive now, therefore the count is 1.
EXPECT_EQ(1, CountsInstances::NumInstances());
// We COULD destroy the TLO on the main thread, but to be even fancier lets
// create a thread that will destroy the object on a back ground thread.
// The end result is that the TLO entry should be cleared out.
thread_ptr.reset(
new DestroyTypeOnThread<ThreadLocalObject<CountsInstances> >(tlo));
thread_ptr->Start();
thread_ptr->Join();
thread_ptr.reset(NULL);
// Now we expect that number of instances to be 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
} // anonymous namespace
} // namespace base