|  | // 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/scoped_ptr.h" | 
|  | #include "nb/test_thread.h" | 
|  | #include "nb/thread_local_object.h" | 
|  | #include "starboard/atomic.h" | 
|  | #include "starboard/common/mutex.h" | 
|  | #include "starboard/thread.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace nb { | 
|  | namespace { | 
|  |  | 
|  | // Simple class that counts the number of instances alive. | 
|  | struct CountsInstances { | 
|  | CountsInstances() { s_instances_.increment(); } | 
|  | ~CountsInstances() { s_instances_.decrement(); } | 
|  | static starboard::atomic_int32_t s_instances_; | 
|  | static int NumInstances() { return s_instances_.load(); } | 
|  | static void ResetNumInstances() { s_instances_.exchange(0); } | 
|  | }; | 
|  | starboard::atomic_int32_t 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 TestThread { | 
|  | 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 TestThread { | 
|  | public: | 
|  | explicit DestroyTypeOnThread(TYPE* ptr) | 
|  | : ptr_(ptr) {} | 
|  | virtual void Run() { | 
|  | ptr_.reset(NULL);  // Destroys the object. | 
|  | } | 
|  | private: | 
|  | nb::scoped_ptr<TYPE> ptr_; | 
|  | }; | 
|  |  | 
|  | // A simple struct that wraps an integer, and cannot be default constructed. | 
|  | struct IntegerWrapper { | 
|  | explicit IntegerWrapper(int value) : value(value) {} | 
|  | int value; | 
|  | }; | 
|  |  | 
|  | // 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())); | 
|  | } | 
|  |  | 
|  | // The same as |MainThread|, except with |tlo_bool| initialized to |true| | 
|  | // rather than |false|, via passing |true| to |GetOrCreate|. | 
|  | TEST(ThreadLocalObject, MainThreadWithArg) { | 
|  | ThreadLocalObject<bool> tlo_bool; | 
|  | EXPECT_TRUE(NULL == tlo_bool.GetIfExists()); | 
|  | bool* the_bool = tlo_bool.GetOrCreate(true); | 
|  | EXPECT_TRUE(the_bool != NULL); | 
|  | EXPECT_TRUE(*the_bool); | 
|  | *the_bool = false; | 
|  | EXPECT_FALSE(*(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"]); | 
|  | } | 
|  |  | 
|  | // The complex object analog of |MainThreadWithArg|. | 
|  | TEST(ThreadLocalObject, MainThreadComplexObjectWithArg) { | 
|  | ThreadLocalObject<IntegerWrapper> integer_wrapper_tlo; | 
|  | EXPECT_FALSE(integer_wrapper_tlo.GetIfExists()); | 
|  | ASSERT_TRUE(integer_wrapper_tlo.GetOrCreate(14)); | 
|  | IntegerWrapper* integer_wrapper = integer_wrapper_tlo.GetIfExists(); | 
|  | const IntegerWrapper* const_integer_wrapper = | 
|  | integer_wrapper_tlo.GetIfExists(); | 
|  | ASSERT_TRUE(integer_wrapper); | 
|  | ASSERT_TRUE(const_integer_wrapper); | 
|  | integer_wrapper->value = 15; | 
|  | ASSERT_EQ(15, integer_wrapper->value); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | { | 
|  | TestThread* 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<TestThread> 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 | 
|  | }  // nb namespace |