/************************************************************************* * * * Open Dynamics Engine, Copyright (C) 2001-2003 Russell L. Smith. * * All rights reserved. Email: russ@q12.org Web: www.q12.org * * * * Threading POSIX implementation file. * * Copyright (C) 2011-2019 Oleh Derevenko. All rights reserved. * * e-mail: odar@eleks.com (change all "a" to "e") * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of EITHER: * * (1) The GNU Lesser General Public License as published by the Free * * Software Foundation; either version 2.1 of the License, or (at * * your option) any later version. The text of the GNU Lesser * * General Public License is included with this library in the * * file LICENSE.TXT. * * (2) The BSD-style license that is included with this library in * * the file LICENSE-BSD.TXT. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files * * LICENSE.TXT and LICENSE-BSD.TXT for more details. * * * *************************************************************************/ /* * Threading POSIX implementation for built-in threading support provider. */ #ifndef _ODE_THREADING_IMPL_POSIX_H_ #define _ODE_THREADING_IMPL_POSIX_H_ #include #if !defined(_WIN32) #include "threading_impl_templates.h" #include "threading_fake_sync.h" #include "threading_atomics_provs.h" #if dBUILTIN_THREADING_IMPL_ENABLED #include #include #include #if !defined(EOK) #define EOK 0 #endif #if defined(__APPLE__) #if HAVE_GETTIMEOFDAY #include #if !defined(CLOCK_MONOTONIC) #define CLOCK_MONOTONIC 2 #endif static inline int _condvar_clock_gettime(int clock_type, timespec *ts) { (void)clock_type; // Unused timeval tv; return gettimeofday(&tv, NULL) == 0 ? (ts->tv_sec = tv.tv_sec, ts->tv_nsec = tv.tv_usec * 1000, 0) : (-1); } #else // #if !HAVE_GETTIMEOFDAY #error It is necessary to check manuals for the correct way of getting condvar wait time for this Apple system #endif // #if !HAVE_GETTIMEOFDAY #else // #if !defined(__APPLE__) #if !HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_NO_PTHREAD_CONDATTR_SETCLOCK // The code must be compiled without autoconf run, having the project generated by other means. // Assume the pthread_condattr_setclock() is available as it is true in most cases and it is the best we can do in such cases. #define HAVE_PTHREAD_CONDATTR_SETCLOCK 1 #endif // #if !HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_NO_PTHREAD_CONDATTR_SETCLOCK #if HAVE_PTHREAD_CONDATTR_SETCLOCK static inline int _condvar_clock_gettime(int clock_type, timespec *ts) { return clock_gettime(clock_type, ts); } #else // #if !HAVE_PTHREAD_CONDATTR_SETCLOCK #error It is necessary to check manuals for the correct way of getting condvar wait time for this system #endif // #if !HAVE_PTHREAD_CONDATTR_SETCLOCK #endif // #if !defined(__APPLE__) /************************************************************************/ /* dxCondvarWakeup class implementation */ /************************************************************************/ class dxCondvarWakeup { public: dxCondvarWakeup(): m_waiters_list(NULL), m_signaled_state(false), m_state_is_permanent(false), m_object_initialized(false) {} ~dxCondvarWakeup() { DoFinalizeObject(); } bool InitializeObject() { return DoInitializeObject(); } private: bool DoInitializeObject(); void DoFinalizeObject(); public: void ResetWakeup(); void WakeupAThread(); void WakeupAllThreads(); bool WaitWakeup(const dThreadedWaitTime *timeout_time_ptr); private: bool BlockAsAWaiter(const dThreadedWaitTime *timeout_time_ptr); private: struct dxWaiterInfo { dxWaiterInfo(): m_signal_state(false) {} dxWaiterInfo **m_prev_info_ptr; dxWaiterInfo *m_next_info; bool m_signal_state; }; void RegisterWaiterInList(dxWaiterInfo *waiter_info); void UnregisterWaiterFromList(dxWaiterInfo *waiter_info); bool MarkSignaledFirstWaiter(); static bool MarkSignaledFirstWaiterMeaningful(dxWaiterInfo *first_waiter); bool MarkSignaledAllWaiters(); static bool MarkSignaledAllWaitersMeaningful(dxWaiterInfo *first_waiter); private: dxWaiterInfo *m_waiters_list; bool m_signaled_state; bool m_state_is_permanent; bool m_object_initialized; pthread_mutex_t m_wakeup_mutex; pthread_cond_t m_wakeup_cond; }; bool dxCondvarWakeup::DoInitializeObject() { dIASSERT(!m_object_initialized); bool init_result = false; pthread_condattr_t cond_condattr; bool mutex_initialized = false, condattr_initialized = false; do { int mutex_result = pthread_mutex_init(&m_wakeup_mutex, NULL); if (mutex_result != EOK) { errno = mutex_result; break; } mutex_initialized = true; int condattr_init_result = pthread_condattr_init(&cond_condattr); if (condattr_init_result != EOK) { errno = condattr_init_result; break; } condattr_initialized = true; #if HAVE_PTHREAD_CONDATTR_SETCLOCK int condattr_clock_result = pthread_condattr_setclock(&cond_condattr, CLOCK_MONOTONIC); if (condattr_clock_result != EOK) { errno = condattr_clock_result; break; } #endif // #if HAVE_PTHREAD_CONDATTR_SETCLOCK int cond_result = pthread_cond_init(&m_wakeup_cond, &cond_condattr); if (cond_result != EOK) { errno = cond_result; break; } pthread_condattr_destroy(&cond_condattr); // result can be ignored m_object_initialized = true; init_result = true; } while (false); if (!init_result) { if (mutex_initialized) { if (condattr_initialized) { int condattr_destroy_result = pthread_condattr_destroy(&cond_condattr); dICHECK(condattr_destroy_result == EOK || ((errno = condattr_destroy_result), false)); } int mutex_destroy_result = pthread_mutex_destroy(&m_wakeup_mutex); dICHECK(mutex_destroy_result == EOK || ((errno = mutex_destroy_result), false)); } } return init_result; } void dxCondvarWakeup::DoFinalizeObject() { if (m_object_initialized) { int cond_result = pthread_cond_destroy(&m_wakeup_cond); dICHECK(cond_result == EOK || ((errno = cond_result), false)); int mutex_result = pthread_mutex_destroy(&m_wakeup_mutex); dICHECK(mutex_result == EOK || ((errno = mutex_result), false)); m_object_initialized = false; } } void dxCondvarWakeup::ResetWakeup() { int lock_result = pthread_mutex_lock(&m_wakeup_mutex); dICHECK(lock_result == EOK || ((errno = lock_result), false)); m_signaled_state = false; m_state_is_permanent = false; int unlock_result = pthread_mutex_unlock(&m_wakeup_mutex); dICHECK(unlock_result == EOK || ((errno = unlock_result), false)); } void dxCondvarWakeup::WakeupAThread() { int lock_result = pthread_mutex_lock(&m_wakeup_mutex); dICHECK(lock_result == EOK || ((errno = lock_result), false)); dIASSERT(!m_state_is_permanent); // Wakeup should not be used after permanent signal if (!m_signaled_state) { if (MarkSignaledFirstWaiter()) { // All threads must be woken up regardless to the fact that only one waiter is marked. // It is not possible to wake up a chosen thread personally // and if a random thread is woken up it can't know if there was a condition signal for it // or the sleep was interrupted by POSIX signal. // On the other hand, without this it is not possible to guarantee that a thread // will be woken up per each WakeupAThread() call if there is more than one waiter // and wakeup requests will not accumulate if there are no waiters. int broadcast_result = pthread_cond_broadcast(&m_wakeup_cond); dICHECK(broadcast_result == EOK || ((errno = broadcast_result), false)); } else { m_signaled_state = true; } } int unlock_result = pthread_mutex_unlock(&m_wakeup_mutex); dICHECK(unlock_result == EOK || ((errno = unlock_result), false)); } void dxCondvarWakeup::WakeupAllThreads() { int lock_result = pthread_mutex_lock(&m_wakeup_mutex); dICHECK(lock_result == EOK || ((errno = lock_result), false)); m_state_is_permanent = true; if (!m_signaled_state) { m_signaled_state = true; if (MarkSignaledAllWaiters()) { int broadcast_result = pthread_cond_broadcast(&m_wakeup_cond); dICHECK(broadcast_result == EOK || ((errno = broadcast_result), false)); } } int unlock_result = pthread_mutex_unlock(&m_wakeup_mutex); dICHECK(unlock_result == EOK || ((errno = unlock_result), false)); } bool dxCondvarWakeup::WaitWakeup(const dThreadedWaitTime *timeout_time_ptr) { bool wait_result; int lock_result = pthread_mutex_lock(&m_wakeup_mutex); dICHECK(lock_result == EOK || ((errno = lock_result), false)); if (!m_signaled_state) { if (!timeout_time_ptr || timeout_time_ptr->wait_nsec != 0 || timeout_time_ptr->wait_sec != 0) { wait_result = BlockAsAWaiter(timeout_time_ptr); } else { wait_result = false; } } else { m_signaled_state = m_state_is_permanent; wait_result = true; } int unlock_result = pthread_mutex_unlock(&m_wakeup_mutex); dICHECK(unlock_result == EOK || ((errno = unlock_result), false)); return wait_result; } bool dxCondvarWakeup::BlockAsAWaiter(const dThreadedWaitTime *timeout_time_ptr) { bool wait_result = false; dxWaiterInfo waiter_info; RegisterWaiterInList(&waiter_info); timespec wakeup_time; if (timeout_time_ptr != NULL) { timespec current_time; int clock_result = _condvar_clock_gettime(CLOCK_MONOTONIC, ¤t_time); dICHECK(clock_result != -1); time_t wakeup_sec = current_time.tv_sec + timeout_time_ptr->wait_sec; unsigned long wakeup_nsec = current_time.tv_nsec + timeout_time_ptr->wait_nsec; if (wakeup_nsec >= 1000000000) { wakeup_nsec -= 1000000000; wakeup_sec += 1; } wakeup_time.tv_sec = wakeup_sec; wakeup_time.tv_nsec = wakeup_nsec; } while (true) { int cond_result = (timeout_time_ptr != NULL) ? pthread_cond_timedwait(&m_wakeup_cond, &m_wakeup_mutex, &wakeup_time) : pthread_cond_wait(&m_wakeup_cond, &m_wakeup_mutex); dICHECK(cond_result == EOK || cond_result == ETIMEDOUT || ((errno = cond_result), false)); if (waiter_info.m_signal_state) { wait_result = true; break; } if (cond_result == ETIMEDOUT) { dIASSERT(timeout_time_ptr != NULL); break; } } UnregisterWaiterFromList(&waiter_info); return wait_result; } void dxCondvarWakeup::RegisterWaiterInList(dxWaiterInfo *waiter_info) { dxWaiterInfo *const first_waiter = m_waiters_list; if (first_waiter == NULL) { waiter_info->m_next_info = waiter_info; waiter_info->m_prev_info_ptr = &waiter_info->m_next_info; m_waiters_list = waiter_info; } else { waiter_info->m_next_info = first_waiter; waiter_info->m_prev_info_ptr = first_waiter->m_prev_info_ptr; *first_waiter->m_prev_info_ptr = waiter_info; first_waiter->m_prev_info_ptr = &waiter_info->m_next_info; } } void dxCondvarWakeup::UnregisterWaiterFromList(dxWaiterInfo *waiter_info) { dxWaiterInfo *next_info = waiter_info->m_next_info; if (next_info == waiter_info) { m_waiters_list = NULL; } else { next_info->m_prev_info_ptr = waiter_info->m_prev_info_ptr; *waiter_info->m_prev_info_ptr = next_info; if (waiter_info == m_waiters_list) { m_waiters_list = next_info; } } } bool dxCondvarWakeup::MarkSignaledFirstWaiter() { bool waiter_found = false; dxWaiterInfo *const first_waiter = m_waiters_list; if (first_waiter) { waiter_found = MarkSignaledFirstWaiterMeaningful(first_waiter); } return waiter_found; } bool dxCondvarWakeup::MarkSignaledFirstWaiterMeaningful(dxWaiterInfo *first_waiter) { bool waiter_found = false; dxWaiterInfo *current_waiter = first_waiter; while (true) { if (!current_waiter->m_signal_state) { current_waiter->m_signal_state = true; waiter_found = true; break; } current_waiter = current_waiter->m_next_info; if (current_waiter == first_waiter) { break; } } return waiter_found; } bool dxCondvarWakeup::MarkSignaledAllWaiters() { bool waiter_found = false; dxWaiterInfo *const first_waiter = m_waiters_list; if (first_waiter) { waiter_found = MarkSignaledAllWaitersMeaningful(first_waiter); } return waiter_found; } bool dxCondvarWakeup::MarkSignaledAllWaitersMeaningful(dxWaiterInfo *first_waiter) { bool waiter_found = false; dxWaiterInfo *current_waiter = first_waiter; while (true) { if (!current_waiter->m_signal_state) { current_waiter->m_signal_state = true; waiter_found = true; } current_waiter = current_waiter->m_next_info; if (current_waiter == first_waiter) { break; } } return waiter_found; } /************************************************************************/ /* dxMutexMutex class implementation */ /************************************************************************/ class dxMutexMutex { public: dxMutexMutex(): m_mutex_allocated(false) {} ~dxMutexMutex() { DoFinalizeObject(); } bool InitializeObject() { return DoInitializeObject(); } private: bool DoInitializeObject(); void DoFinalizeObject(); public: void LockMutex(); bool TryLockMutex(); void UnlockMutex(); private: pthread_mutex_t m_mutex_instance; bool m_mutex_allocated; }; bool dxMutexMutex::DoInitializeObject() { dIASSERT(!m_mutex_allocated); bool init_result = false; do { int mutex_result = pthread_mutex_init(&m_mutex_instance, NULL); if (mutex_result != EOK) { errno = mutex_result; break; } m_mutex_allocated = true; init_result = true; } while (false); return init_result; } void dxMutexMutex::DoFinalizeObject() { if (m_mutex_allocated) { int mutex_result = pthread_mutex_destroy(&m_mutex_instance); dICHECK(mutex_result == EOK || ((errno = mutex_result), false)); m_mutex_allocated = false; } } void dxMutexMutex::LockMutex() { int lock_result = pthread_mutex_lock(&m_mutex_instance); dICHECK(lock_result == EOK || ((errno = lock_result), false)); } bool dxMutexMutex::TryLockMutex() { int trylock_result = pthread_mutex_trylock(&m_mutex_instance); dICHECK(trylock_result == EOK || trylock_result == EBUSY || ((errno = trylock_result), false)); return trylock_result == EOK; } void dxMutexMutex::UnlockMutex() { int unlock_result = pthread_mutex_unlock(&m_mutex_instance); dICHECK(unlock_result == EOK || ((errno = unlock_result), false)); } #endif // #if dBUILTIN_THREADING_IMPL_ENABLED /************************************************************************/ /* Self-threaded job list definition */ /************************************************************************/ typedef dxtemplateJobListContainer dxSelfThreadedJobListContainer; typedef dxtemplateJobListSelfHandler dxSelfThreadedJobListHandler; typedef dxtemplateThreadingImplementation dxSelfThreadedThreading; #if dBUILTIN_THREADING_IMPL_ENABLED /************************************************************************/ /* Multi-threaded job list definition */ /************************************************************************/ typedef dxtemplateJobListContainer, dxMutexMutex, dxOUAtomicsProvider> dxMultiThreadedJobListContainer; typedef dxtemplateJobListThreadedHandler dxMultiThreadedJobListHandler; typedef dxtemplateThreadingImplementation dxMultiThreadedThreading; #endif // #if dBUILTIN_THREADING_IMPL_ENABLED #endif // #if !defined(_WIN32) #endif // #ifndef _ODE_THREADING_IMPL_POSIX_H_