| // Copyright 2017 The Cobalt Authors. 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 "starboard/thread.h" |
| |
| #include <process.h> |
| #include <memory> |
| |
| #include "starboard/common/log.h" |
| #include "starboard/once.h" |
| #include "starboard/shared/win32/error_utils.h" |
| #include "starboard/shared/win32/thread_private.h" |
| #include "starboard/shared/win32/wchar_utils.h" |
| |
| namespace sbwin32 = starboard::shared::win32; |
| |
| using sbwin32::DebugLogWinError; |
| using sbwin32::GetThreadSubsystemSingleton; |
| using sbwin32::SbThreadPrivate; |
| using sbwin32::ThreadSubsystemSingleton; |
| using sbwin32::wchar_tToUTF8; |
| |
| namespace { |
| |
| void ResetWinError() { |
| SetLastError(0); |
| } |
| |
| class ThreadCreateInfo { |
| public: |
| SbThreadPrivate thread_private_; |
| SbThreadEntryPoint entry_point_; |
| void* user_context_; |
| std::string name_; |
| }; |
| |
| int RunThreadLocalDestructors(ThreadSubsystemSingleton* singleton) { |
| int num_destructors_called = 0; |
| |
| for (auto it = singleton->thread_local_keys_.begin(); |
| it != singleton->thread_local_keys_.end();) { |
| auto curr_it = it++; |
| |
| if (!curr_it->second->destructor) { |
| continue; |
| } |
| auto key = curr_it->second; |
| void* entry = SbThreadGetLocalValue(key); |
| if (!entry) { |
| continue; |
| } |
| SbThreadSetLocalValue(key, nullptr); |
| ++num_destructors_called; |
| curr_it->second->destructor(entry); |
| } |
| return num_destructors_called; |
| } |
| |
| int CountTlsObjectsRemaining(ThreadSubsystemSingleton* singleton) { |
| int num_objects_remain = 0; |
| for (auto it = singleton->thread_local_keys_.begin(); |
| it != singleton->thread_local_keys_.end(); ++it) { |
| if (!it->second->destructor) { |
| continue; |
| } |
| auto key = it->second; |
| void* entry = SbThreadGetLocalValue(key); |
| if (!entry) { |
| continue; |
| } |
| ++num_objects_remain; |
| } |
| return num_objects_remain; |
| } |
| |
| void CallThreadLocalDestructorsMultipleTimes() { |
| // The number of passes conforms to the base_unittests. This is useful for |
| // destructors that insert new objects into thread local storage. These |
| // objects then need to be destroyed as well in subsequent passes. The total |
| // number of passes is 4, which is one more than what base_unittest tests |
| // for. |
| const int kNumDestructorPasses = 4; |
| |
| ThreadSubsystemSingleton* singleton = GetThreadSubsystemSingleton(); |
| int num_tls_objects_remaining = 0; |
| // TODO note that the implementation below holds a global lock |
| // while processing TLS destructors on thread exit. This could |
| // be a bottleneck in some scenarios. A lockless approach may be preferable. |
| SbMutexAcquire(&singleton->mutex_); |
| |
| for (int i = 0; i < kNumDestructorPasses; ++i) { |
| // Run through each destructor and call it. |
| const int num_destructors_called = RunThreadLocalDestructors(singleton); |
| if (0 == num_destructors_called) { |
| break; // No more destructors to call. |
| } |
| } |
| num_tls_objects_remaining = CountTlsObjectsRemaining(singleton); |
| SbMutexRelease(&singleton->mutex_); |
| |
| SB_DCHECK(num_tls_objects_remaining == 0) << "Dangling objects in TLS exist."; |
| } |
| |
| unsigned ThreadTrampoline(void* thread_create_info_context) { |
| std::unique_ptr<ThreadCreateInfo> info( |
| static_cast<ThreadCreateInfo*>(thread_create_info_context)); |
| |
| ThreadSubsystemSingleton* singleton = GetThreadSubsystemSingleton(); |
| SbThreadSetLocalValue(singleton->thread_private_key_, &info->thread_private_); |
| SbThreadSetName(info->name_.c_str()); |
| |
| void* result = info->entry_point_(info->user_context_); |
| |
| CallThreadLocalDestructorsMultipleTimes(); |
| |
| SbMutexAcquire(&info->thread_private_.mutex_); |
| info->thread_private_.result_ = result; |
| info->thread_private_.result_is_valid_ = true; |
| SbConditionVariableSignal(&info->thread_private_.condition_); |
| while (info->thread_private_.wait_for_join_) { |
| SbConditionVariableWait(&info->thread_private_.condition_, |
| &info->thread_private_.mutex_); |
| } |
| SbMutexRelease(&info->thread_private_.mutex_); |
| |
| return 0; |
| } |
| |
| int SbThreadPriorityToWin32Priority(SbThreadPriority priority) { |
| switch (priority) { |
| case kSbThreadPriorityLowest: |
| return THREAD_PRIORITY_LOWEST; |
| case kSbThreadPriorityLow: |
| return THREAD_PRIORITY_BELOW_NORMAL; |
| case kSbThreadPriorityNormal: |
| case kSbThreadNoPriority: |
| return THREAD_PRIORITY_NORMAL; |
| case kSbThreadPriorityHigh: |
| return THREAD_PRIORITY_ABOVE_NORMAL; |
| case kSbThreadPriorityHighest: |
| return THREAD_PRIORITY_HIGHEST; |
| case kSbThreadPriorityRealTime: |
| return THREAD_PRIORITY_TIME_CRITICAL; |
| } |
| SB_NOTREACHED() << "Invalid priority " << priority; |
| return 0; |
| } |
| } // namespace |
| |
| // Note that SetThreadAffinityMask() is not available on some |
| // platforms (eg UWP). If it's necessary for a non-UWP platform, |
| // please fork this implementation for UWP. |
| SbThread SbThreadCreate(int64_t stack_size, |
| SbThreadPriority priority, |
| SbThreadAffinity affinity, |
| bool joinable, |
| const char* name, |
| SbThreadEntryPoint entry_point, |
| void* context) { |
| if (entry_point == NULL) { |
| return kSbThreadInvalid; |
| } |
| ThreadCreateInfo* info = new ThreadCreateInfo(); |
| |
| info->entry_point_ = entry_point; |
| info->user_context_ = context; |
| info->thread_private_.wait_for_join_ = joinable; |
| if (name) { |
| info->name_ = name; |
| } |
| |
| // Create the thread suspended, and then resume once ThreadCreateInfo::handle_ |
| // has been set, so that it's always valid in the ThreadCreateInfo |
| // destructor. |
| uintptr_t handle = |
| _beginthreadex(NULL, static_cast<unsigned int>(stack_size), |
| ThreadTrampoline, info, CREATE_SUSPENDED, NULL); |
| SB_DCHECK(handle); |
| info->thread_private_.handle_ = reinterpret_cast<HANDLE>(handle); |
| ResetWinError(); |
| if (priority != kSbThreadNoPriority && |
| !SetThreadPriority(info->thread_private_.handle_, |
| SbThreadPriorityToWin32Priority(priority)) && |
| !GetLastError()) { |
| SB_LOG(ERROR) << "Failed to set priority for thread " << (name ? name : "") |
| << " to " << priority; |
| DebugLogWinError(); |
| } |
| |
| ResumeThread(info->thread_private_.handle_); |
| |
| return &info->thread_private_; |
| } |