/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 #if !FOLLY_HAVE_PTHREAD && defined(_WIN32) #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace folly { namespace portability { namespace pthread { int pthread_attr_init(pthread_attr_t* attr) { if (attr == nullptr) { errno = EINVAL; return -1; } attr->stackSize = 0; attr->detached = false; return 0; } int pthread_attr_setdetachstate(pthread_attr_t* attr, int state) { if (attr == nullptr) { errno = EINVAL; return -1; } attr->detached = state == PTHREAD_CREATE_DETACHED ? true : false; return 0; } int pthread_attr_setstacksize(pthread_attr_t* attr, size_t kb) { if (attr == nullptr) { errno = EINVAL; return -1; } attr->stackSize = kb; return 0; } namespace pthread_detail { pthread_t::~pthread_t() noexcept { if (handle != INVALID_HANDLE_VALUE && !detached) { CloseHandle(handle); } } } // namespace pthread_detail int pthread_equal(pthread_t threadA, pthread_t threadB) { if (threadA == threadB) { return 1; } // Note that, in the presence of detached threads, it is in theory possible // for two different pthread_t handles to be compared as the same due to // Windows HANDLE and Thread ID re-use. If you're doing anything useful with // a detached thread, you're probably doing it wrong, but I felt like leaving // this note here anyways. if (threadA->handle == threadB->handle && threadA->threadID == threadB->threadID) { return 1; } return 0; } namespace { thread_local pthread_t current_thread_self; struct pthread_startup_info { pthread_t thread; void* (*startupFunction)(void*); void* startupArgument; }; DWORD internal_pthread_thread_start(void* arg) { // We are now in the new thread. auto startupInfo = reinterpret_cast(arg); current_thread_self = startupInfo->thread; auto ret = startupInfo->startupFunction(startupInfo->startupArgument); if /* constexpr */ (sizeof(void*) != sizeof(DWORD)) { auto tmp = reinterpret_cast(ret); if (tmp > std::numeric_limits::max()) { throw std::out_of_range( "Exit code of the pthread is outside the range representable on Windows"); } } delete startupInfo; return static_cast(reinterpret_cast(ret)); } } // namespace int pthread_create( pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg) { if (thread == nullptr) { errno = EINVAL; return -1; } size_t stackSize = attr != nullptr ? attr->stackSize : 0; bool detach = attr != nullptr ? attr->detached : false; // Note that the start routine passed into pthread returns a void* and the // windows API expects DWORD's, so we need to stub around that. auto startupInfo = new pthread_startup_info(); startupInfo->startupFunction = start_routine; startupInfo->startupArgument = arg; startupInfo->thread = std::make_shared(); // We create the thread suspended so we can assign the handle and thread id // in the pthread_t. startupInfo->thread->handle = CreateThread( nullptr, stackSize, internal_pthread_thread_start, startupInfo, CREATE_SUSPENDED, &startupInfo->thread->threadID); ResumeThread(startupInfo->thread->handle); if (detach) { *thread = std::make_shared(); (*thread)->detached = true; (*thread)->handle = startupInfo->thread->handle; (*thread)->threadID = startupInfo->thread->threadID; } else { *thread = startupInfo->thread; } return 0; } pthread_t pthread_self() { // Not possible to race :) if (current_thread_self == nullptr) { current_thread_self = std::make_shared(); current_thread_self->threadID = GetCurrentThreadId(); // The handle returned by GetCurrentThread is a pseudo-handle and needs to // be swapped out for a real handle to be useful anywhere other than this // thread. DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), ¤t_thread_self->handle, DUPLICATE_SAME_ACCESS, TRUE, 0); } return current_thread_self; } int pthread_join(pthread_t thread, void** exitCode) { if (thread->detached) { errno = EINVAL; return -1; } if (WaitForSingleObjectEx(thread->handle, INFINITE, FALSE) == WAIT_FAILED) { return -1; } if (exitCode != nullptr) { DWORD e; if (!GetExitCodeThread(thread->handle, &e)) { return -1; } *exitCode = reinterpret_cast(static_cast(e)); } return 0; } HANDLE pthread_getw32threadhandle_np(pthread_t thread) { return thread->handle; } DWORD pthread_getw32threadid_np(pthread_t thread) { return thread->threadID; } int pthread_setschedparam( pthread_t thread, int policy, const sched_param* param) { if (thread->detached) { errno = EINVAL; return -1; } auto newPrior = param->sched_priority; if (newPrior > THREAD_PRIORITY_TIME_CRITICAL || newPrior < THREAD_PRIORITY_IDLE) { errno = EINVAL; return -1; } if (GetPriorityClass(GetCurrentProcess()) != REALTIME_PRIORITY_CLASS) { if (newPrior > THREAD_PRIORITY_IDLE && newPrior < THREAD_PRIORITY_LOWEST) { // The values between IDLE and LOWEST are invalid unless the process is // running as realtime. newPrior = THREAD_PRIORITY_LOWEST; } else if ( newPrior < THREAD_PRIORITY_TIME_CRITICAL && newPrior > THREAD_PRIORITY_HIGHEST) { // Same as above. newPrior = THREAD_PRIORITY_HIGHEST; } } if (!SetThreadPriority(thread->handle, newPrior)) { return -1; } return 0; } int pthread_mutexattr_init(pthread_mutexattr_t* attr) { if (attr == nullptr) { return EINVAL; } attr->type = PTHREAD_MUTEX_DEFAULT; return 0; } int pthread_mutexattr_destroy(pthread_mutexattr_t* attr) { if (attr == nullptr) { return EINVAL; } return 0; } int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type) { if (attr == nullptr) { return EINVAL; } if (type != PTHREAD_MUTEX_DEFAULT && type != PTHREAD_MUTEX_RECURSIVE) { return EINVAL; } attr->type = type; return 0; } struct pthread_mutex_t_ { private: int type; union { std::timed_mutex timed_mtx; std::recursive_timed_mutex recursive_timed_mtx; }; public: pthread_mutex_t_(int mutex_type) : type(mutex_type) { switch (type) { case PTHREAD_MUTEX_NORMAL: new (&timed_mtx) std::timed_mutex(); break; case PTHREAD_MUTEX_RECURSIVE: new (&recursive_timed_mtx) std::recursive_timed_mutex(); break; } } ~pthread_mutex_t_() noexcept { switch (type) { case PTHREAD_MUTEX_NORMAL: timed_mtx.~timed_mutex(); break; case PTHREAD_MUTEX_RECURSIVE: recursive_timed_mtx.~recursive_timed_mutex(); break; } } void lock() { switch (type) { case PTHREAD_MUTEX_NORMAL: timed_mtx.lock(); break; case PTHREAD_MUTEX_RECURSIVE: recursive_timed_mtx.lock(); break; } } bool try_lock() { switch (type) { case PTHREAD_MUTEX_NORMAL: return timed_mtx.try_lock(); case PTHREAD_MUTEX_RECURSIVE: return recursive_timed_mtx.try_lock(); } folly::assume_unreachable(); } bool timed_try_lock(std::chrono::system_clock::time_point until) { switch (type) { case PTHREAD_MUTEX_NORMAL: return timed_mtx.try_lock_until(until); case PTHREAD_MUTEX_RECURSIVE: return recursive_timed_mtx.try_lock_until(until); } folly::assume_unreachable(); } void unlock() { switch (type) { case PTHREAD_MUTEX_NORMAL: timed_mtx.unlock(); break; case PTHREAD_MUTEX_RECURSIVE: recursive_timed_mtx.unlock(); break; } } void condition_wait(std::condition_variable_any& cond) { switch (type) { case PTHREAD_MUTEX_NORMAL: { std::unique_lock lock(timed_mtx); cond.wait(lock); break; } case PTHREAD_MUTEX_RECURSIVE: { std::unique_lock lock(recursive_timed_mtx); cond.wait(lock); break; } } } bool condition_timed_wait( std::condition_variable_any& cond, std::chrono::system_clock::time_point until) { switch (type) { case PTHREAD_MUTEX_NORMAL: { std::unique_lock lock(timed_mtx); return cond.wait_until(lock, until) == std::cv_status::no_timeout; } case PTHREAD_MUTEX_RECURSIVE: { std::unique_lock lock(recursive_timed_mtx); return cond.wait_until(lock, until) == std::cv_status::no_timeout; } } folly::assume_unreachable(); } }; int pthread_mutex_init( pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) { if (mutex == nullptr) { return EINVAL; } auto type = attr != nullptr ? attr->type : PTHREAD_MUTEX_DEFAULT; auto ret = new pthread_mutex_t_(type); *mutex = ret; return 0; } int pthread_mutex_destroy(pthread_mutex_t* mutex) { if (mutex == nullptr) { return EINVAL; } delete *mutex; *mutex = nullptr; return 0; } int pthread_mutex_lock(pthread_mutex_t* mutex) { if (mutex == nullptr) { return EINVAL; } // This implementation does not implement deadlock detection, as the // STL mutexes we're wrapping don't either. (*mutex)->lock(); return 0; } int pthread_mutex_trylock(pthread_mutex_t* mutex) { if (mutex == nullptr) { return EINVAL; } if ((*mutex)->try_lock()) { return 0; } else { return EBUSY; } } static std::chrono::system_clock::time_point timespec_to_time_point( const timespec* t) { using time_point = std::chrono::system_clock::time_point; auto ns = std::chrono::seconds(t->tv_sec) + std::chrono::nanoseconds(t->tv_nsec); return time_point(std::chrono::duration_cast(ns)); } int pthread_mutex_timedlock( pthread_mutex_t* mutex, const timespec* abs_timeout) { if (mutex == nullptr || abs_timeout == nullptr) { return EINVAL; } auto time = timespec_to_time_point(abs_timeout); if ((*mutex)->timed_try_lock(time)) { return 0; } else { return ETIMEDOUT; } } int pthread_mutex_unlock(pthread_mutex_t* mutex) { if (mutex == nullptr) { return EINVAL; } // This implementation allows other threads to unlock it, // as the STL containers also do. (*mutex)->unlock(); return 0; } struct pthread_rwlock_t_ { std::shared_timed_mutex mtx; std::atomic writing{false}; }; int pthread_rwlock_init(pthread_rwlock_t* rwlock, const void* attr) { if (attr != nullptr) { return EINVAL; } if (rwlock == nullptr) { return EINVAL; } *rwlock = new pthread_rwlock_t_(); return 0; } int pthread_rwlock_destroy(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } delete *rwlock; *rwlock = nullptr; return 0; } int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } (*rwlock)->mtx.lock_shared(); return 0; } int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } if ((*rwlock)->mtx.try_lock_shared()) { return 0; } else { return EBUSY; } } int pthread_rwlock_timedrdlock( pthread_rwlock_t* rwlock, const timespec* abs_timeout) { if (rwlock == nullptr) { return EINVAL; } auto time = timespec_to_time_point(abs_timeout); if ((*rwlock)->mtx.try_lock_shared_until(time)) { return 0; } else { return ETIMEDOUT; } } int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } (*rwlock)->mtx.lock(); (*rwlock)->writing = true; return 0; } // Note: As far as I can tell, rwlock is technically supposed to // be an upgradable lock, but we don't implement it that way. int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } if ((*rwlock)->mtx.try_lock()) { (*rwlock)->writing = true; return 0; } else { return EBUSY; } } int pthread_rwlock_timedwrlock( pthread_rwlock_t* rwlock, const timespec* abs_timeout) { if (rwlock == nullptr) { return EINVAL; } auto time = timespec_to_time_point(abs_timeout); if ((*rwlock)->mtx.try_lock_until(time)) { (*rwlock)->writing = true; return 0; } else { return ETIMEDOUT; } } int pthread_rwlock_unlock(pthread_rwlock_t* rwlock) { if (rwlock == nullptr) { return EINVAL; } // Note: We don't have any checking to ensure we have actually // locked things first, so you'll actually be in undefined behavior // territory if you do attempt to unlock things you haven't locked. if ((*rwlock)->writing) { (*rwlock)->mtx.unlock(); // If we fail, then another thread has already immediately acquired // the write lock, so this should stay as true :) bool dump = true; (void)(*rwlock)->writing.compare_exchange_strong(dump, false); } else { (*rwlock)->mtx.unlock_shared(); } return 0; } struct pthread_cond_t_ { // pthread_mutex_t is backed by timed // mutexes, so no basic condition variable for // us :( std::condition_variable_any cond; }; int pthread_cond_init(pthread_cond_t* cond, const void* attr) { if (attr != nullptr) { return EINVAL; } if (cond == nullptr) { return EINVAL; } *cond = new pthread_cond_t_(); return 0; } int pthread_cond_destroy(pthread_cond_t* cond) { if (cond == nullptr) { return EINVAL; } delete *cond; *cond = nullptr; return 0; } int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) { if (cond == nullptr || mutex == nullptr) { return EINVAL; } (*mutex)->condition_wait((*cond)->cond); return 0; } int pthread_cond_timedwait( pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abstime) { if (cond == nullptr || mutex == nullptr || abstime == nullptr) { return EINVAL; } auto time = timespec_to_time_point(abstime); if ((*mutex)->condition_timed_wait((*cond)->cond, time)) { return 0; } else { return ETIMEDOUT; } } int pthread_cond_signal(pthread_cond_t* cond) { if (cond == nullptr) { return EINVAL; } (*cond)->cond.notify_one(); return 0; } int pthread_cond_broadcast(pthread_cond_t* cond) { if (cond == nullptr) { return EINVAL; } (*cond)->cond.notify_all(); return 0; } int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) { try { auto newKey = new boost::thread_specific_ptr(destructor); *key = newKey; return 0; } catch (boost::thread_resource_error) { return -1; } } int pthread_key_delete(pthread_key_t key) { try { auto realKey = reinterpret_cast*>(key); delete realKey; return 0; } catch (boost::thread_resource_error) { return -1; } } void* pthread_getspecific(pthread_key_t key) { auto realKey = reinterpret_cast*>(key); // This can't throw as-per the documentation. return realKey->get(); } int pthread_setspecific(pthread_key_t key, const void* value) { try { auto realKey = reinterpret_cast*>(key); // We can't just call reset here because that would invoke the cleanup // function, which we don't want to do. boost::detail::set_tss_data( realKey, #if BOOST_VERSION >= 107000 boost::detail::thread::cleanup_caller_t(), boost::detail::thread::cleanup_func_t(), #else boost::shared_ptr(), #endif const_cast(value), false); return 0; } catch (boost::thread_resource_error) { return -1; } } } // namespace pthread } // namespace portability } // namespace folly #endif