blob: 058452247a809ba53d0335764de2b712b2dd20be [file] [log] [blame]
// 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 <pthread.h>
#include <memory>
#include "starboard/common/log.h"
#include "starboard/common/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::ThreadCreateInfo;
using sbwin32::ThreadGetLocalValue;
using sbwin32::ThreadSetLocalValue;
using sbwin32::ThreadSubsystemSingleton;
using sbwin32::wchar_tToUTF8;
void ResetWinError() {
SetLastError(0);
}
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 = ThreadGetLocalValue(reinterpret_cast<SbThreadLocalKey>(key));
if (!entry) {
continue;
}
ThreadSetLocalValue(reinterpret_cast<SbThreadLocalKey>(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 = ThreadGetLocalValue(reinterpret_cast<SbThreadLocalKey>(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.
pthread_mutex_lock(&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);
pthread_mutex_unlock(&singleton->mutex_);
SB_DCHECK(num_tls_objects_remaining == 0) << "Dangling objects in TLS exist.";
}
namespace {
unsigned ThreadTrampoline(void* thread_create_info_context) {
std::unique_ptr<ThreadCreateInfo> info(
static_cast<ThreadCreateInfo*>(thread_create_info_context));
ThreadSubsystemSingleton* singleton = GetThreadSubsystemSingleton();
ThreadSetLocalValue(singleton->thread_private_key_, &info->thread_private_);
pthread_setname_np(pthread_self(), info->name_.c_str());
void* result = info->entry_point_(info->user_context_);
CallThreadLocalDestructorsMultipleTimes();
pthread_mutex_lock(&info->thread_private_.mutex_);
info->thread_private_.result_ = result;
info->thread_private_.result_is_valid_ = true;
pthread_cond_signal(&info->thread_private_.condition_);
while (info->thread_private_.wait_for_join_) {
pthread_cond_wait(&info->thread_private_.condition_,
&info->thread_private_.mutex_);
}
pthread_mutex_destroy(&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;
}
SbThreadPriority Win32PriorityToSbThreadPriority(int priority) {
switch (priority) {
case THREAD_PRIORITY_LOWEST:
return kSbThreadPriorityLowest;
case THREAD_PRIORITY_BELOW_NORMAL:
return kSbThreadPriorityLow;
case THREAD_PRIORITY_NORMAL:
return kSbThreadPriorityNormal;
case THREAD_PRIORITY_ABOVE_NORMAL:
return kSbThreadPriorityHigh;
case THREAD_PRIORITY_HIGHEST:
return kSbThreadPriorityHighest;
case THREAD_PRIORITY_TIME_CRITICAL:
return kSbThreadPriorityRealTime;
}
SB_NOTREACHED() << "Invalid priority " << priority;
return kSbThreadPriorityNormal;
}
} // namespace
#if SB_API_VERSION < 16
// 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_;
}
#endif // SB_API_VERSION < 16
bool SbThreadSetPriority(SbThreadPriority priority) {
return SetThreadPriority(GetCurrentThread(),
SbThreadPriorityToWin32Priority(priority));
}
bool SbThreadGetPriority(SbThreadPriority* priority) {
int res = GetThreadPriority(GetCurrentThread());
*priority = Win32PriorityToSbThreadPriority(res);
return true;
}