|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | #ifndef NB_THREAD_LOCAL_OBJECT_H_ | 
|  | #define NB_THREAD_LOCAL_OBJECT_H_ | 
|  |  | 
|  | #include <set> | 
|  |  | 
|  | #include "starboard/configuration.h" | 
|  | #include "starboard/log.h" | 
|  | #include "starboard/mutex.h" | 
|  | #include "starboard/thread.h" | 
|  |  | 
|  | namespace nb { | 
|  |  | 
|  | // Like base::ThreadLocalPointer<T> but destroys objects. This is important | 
|  | // for using ThreadLocalObjects that aren't tied to a singleton, or for | 
|  | // access by threads which will call join(). | 
|  | // | 
|  | // FEATURE COMPARISON TABLE: | 
|  | //                         |  Thread Join       | Container Destroyed | 
|  | //   ------------------------------------------------------------------ | 
|  | //   ThreadLocalPointer<T> | LEAKS              | LEAKS | 
|  | //   ThreadLocalObject<T>  | Object Destroyed   | Objects Destroyed | 
|  | // | 
|  | // EXAMPLE: | 
|  | //  ThreadLocalObject<std::map<std::string, int> > map_tls; | 
|  | //  Map* map = map_tls->GetOrCreate(); | 
|  | //  (*map)["my string"] = 15; | 
|  | //  Thread t = new Thread(&map_tls); | 
|  | //  t->start(); | 
|  | //  // t creates it's own thread local map. | 
|  | //  t->join();  // Make sure that thread joins before map_tls is destroyed! | 
|  | // | 
|  | // OBJECT DESTRUCTION: | 
|  | //   There are two ways for an object to be destroyed by the ThreadLocalObject. | 
|  | //   The first way is via a thread join. In this case only the object | 
|  | //   associated with the thread is deleted. | 
|  | //   The second way an object is destroyed is by the ThreadLocalObject | 
|  | //   container to be destroyed, in this case ALL thread local objects are | 
|  | //   destroyed. | 
|  | // | 
|  | // PERFORMANCE: | 
|  | //   ThreadLocalObject is fast for the Get() function if the object has | 
|  | //   has already been created, requiring one extra pointer dereference | 
|  | //   over ThreadLocalPointer<T>. | 
|  | template <typename Type> | 
|  | class ThreadLocalObject { | 
|  | public: | 
|  | ThreadLocalObject() : slot_() { | 
|  | slot_ = SbThreadCreateLocalKey(DeleteEntry); | 
|  | SB_DCHECK(kSbThreadLocalKeyInvalid != slot_); | 
|  | constructing_thread_id_ = SbThreadGetId(); | 
|  | } | 
|  |  | 
|  | // Enables destruction by any other thread. Otherwise, the class instance | 
|  | // will warn when a different thread than the constructing destroys this. | 
|  | void EnableDestructionByAnyThread() { | 
|  | constructing_thread_id_ = kSbThreadInvalidId; | 
|  | } | 
|  |  | 
|  | // Thread Local Objects are destroyed after this call. | 
|  | ~ThreadLocalObject() { | 
|  | CheckCurrentThreadAllowedToDestruct(); | 
|  | if (SB_DLOG_IS_ON(FATAL)) { | 
|  | SB_DCHECK(entry_set_.size() < 2) | 
|  | << "Logic error: Some threads may still be accessing the objects that " | 
|  | << "are about to be destroyed. Only one object is expected and that " | 
|  | << "should be for the main thread. The caller should ensure that " | 
|  | << "other threads that access this object are externally " | 
|  | << "synchronized."; | 
|  | } | 
|  | // No locking is done because the entries should not be accessed by | 
|  | // different threads while this object is shutting down. If access is | 
|  | // occuring then the caller has a race condition, external to this class. | 
|  | typedef typename Set::iterator Iter; | 
|  | for (Iter it = entry_set_.begin(); it != entry_set_.end(); ++it) { | 
|  | Entry* entry = *it; | 
|  | SB_DCHECK(entry->owner_ == this); | 
|  | delete entry->ptr_; | 
|  | delete entry; | 
|  | } | 
|  |  | 
|  | // Cleanup the thread local key. | 
|  | SbThreadDestroyLocalKey(slot_); | 
|  | } | 
|  |  | 
|  | // Warns if there is a misuse of this object. | 
|  | void CheckCurrentThreadAllowedToDestruct() const { | 
|  | if (kSbThreadInvalidId == constructing_thread_id_) { | 
|  | return;   // EnableDestructionByAnyThread() called. | 
|  | } | 
|  | const SbThreadId curr_thread_id = SbThreadGetId(); | 
|  | if (curr_thread_id == constructing_thread_id_) { | 
|  | return;   // Same thread that constructed this. | 
|  | } | 
|  |  | 
|  | if (SB_DLOG_IS_ON(FATAL)) { | 
|  | SB_DCHECK(false) | 
|  | << "ThreadLocalObject<T> was created in thread " | 
|  | << constructing_thread_id_ << "\nbut was destroyed by " | 
|  | << curr_thread_id << ". If this is intentional then call " | 
|  | << "EnableDestructionByAnyThread() to silence this " | 
|  | << "warning."; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Either returns the created pointer for the current thread, or otherwise | 
|  | // constructs the object using the default constructor and returns it. | 
|  | Type* GetOrCreate() { | 
|  | Type* object = GetIfExists(); | 
|  | if (!object) {  // create object. | 
|  | object = new Type(); | 
|  | Entry* entry = new Entry(this, object); | 
|  | // Insert into the set of tls entries. | 
|  | // Performance: Its assumed that creation of objects is much less | 
|  | // frequent than getting an object. | 
|  | { | 
|  | starboard::ScopedLock lock(entry_set_mutex_); | 
|  | entry_set_.insert(entry); | 
|  | } | 
|  | SbThreadSetLocalValue(slot_, entry); | 
|  | } | 
|  | return object; | 
|  | } | 
|  |  | 
|  | template <typename ConstructorArg> | 
|  | Type* GetOrCreate(const ConstructorArg& arg) { | 
|  | Type* object = GetIfExists(); | 
|  | if (!object) {  // create object. | 
|  | object = new Type(arg); | 
|  | Entry* entry = new Entry(this, object); | 
|  | // Insert into the set of tls entries. | 
|  | // Performance: Its assumed that creation of objects is much less | 
|  | // frequent than getting an object. | 
|  | { | 
|  | starboard::ScopedLock lock(entry_set_mutex_); | 
|  | entry_set_.insert(entry); | 
|  | } | 
|  | SbThreadSetLocalValue(slot_, entry); | 
|  | } | 
|  | return object; | 
|  | } | 
|  |  | 
|  | // Returns the pointer if it exists in the current thread, otherwise NULL. | 
|  | Type* GetIfExists() const { | 
|  | Entry* entry = GetEntryIfExists(); | 
|  | if (!entry) { return NULL; } | 
|  | return entry->ptr_; | 
|  | } | 
|  |  | 
|  | // Releases ownership of the pointer FROM THE CURRENT THREAD. | 
|  | // The caller has responsibility to make sure that the pointer is destroyed. | 
|  | Type* Release() { | 
|  | if (Entry* entry = GetEntryIfExists()) { | 
|  | // The entry will no longer run it's destructor on thread join. | 
|  | SbThreadSetLocalValue(slot_, NULL);  // NULL out pointer for TLS. | 
|  | Type* object = entry->ptr_; | 
|  | RemoveEntry(entry); | 
|  | return object; | 
|  | } else { | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | struct Entry { | 
|  | Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) { | 
|  | } | 
|  | ~Entry() { | 
|  | ptr_ = NULL; | 
|  | owner_ = NULL; | 
|  | } | 
|  | ThreadLocalObject* owner_; | 
|  | Type* ptr_; | 
|  | }; | 
|  |  | 
|  | // Deletes the TLSEntry. | 
|  | static void DeleteEntry(void* ptr) { | 
|  | if (!ptr) { | 
|  | SB_NOTREACHED(); | 
|  | return; | 
|  | } | 
|  | Entry* entry = reinterpret_cast<Entry*>(ptr); | 
|  | ThreadLocalObject* tls = entry->owner_; | 
|  | Type* object = entry->ptr_; | 
|  | tls->RemoveEntry(entry); | 
|  | delete object; | 
|  | } | 
|  |  | 
|  | void RemoveEntry(Entry* entry) { | 
|  | { | 
|  | starboard::ScopedLock lock(entry_set_mutex_); | 
|  | entry_set_.erase(entry); | 
|  | } | 
|  | delete entry; | 
|  | } | 
|  |  | 
|  | Entry* GetEntryIfExists() const { | 
|  | void* ptr = SbThreadGetLocalValue(slot_); | 
|  | Entry* entry = static_cast<Entry*>(ptr); | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | typedef std::set<Entry*> Set; | 
|  | // Allows GetIfExists() to be const. | 
|  | mutable SbThreadLocalKey slot_; | 
|  | // entry_set_ contains all the outstanding entries for the thread local | 
|  | // objects that have been created. | 
|  | Set entry_set_; | 
|  | mutable starboard::Mutex entry_set_mutex_; | 
|  | // Used to warn when there is a mismatch between thread that constructed and | 
|  | // thread that destroyed this object. | 
|  | SbThreadId constructing_thread_id_; | 
|  |  | 
|  | SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>); | 
|  | }; | 
|  |  | 
|  | }  // namespace nb | 
|  |  | 
|  | #endif  // NB_THREAD_LOCAL_OBJECT_H_ |