diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..078409f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +project(KodiThreads) + +set(SOURCES KodiThreads/threads/Atomics.cpp + KodiThreads/threads/Event.cpp + KodiThreads/threads/Thread.cpp + KodiThreads/threads/Timer.cpp + KodiThreads/threads/SystemClock.cpp + KodiThreads/commons/Exception.cpp + KodiThreads/system.cpp) + +set(HEADERS KodiThreads/threads/platform/Implementation.cpp + + KodiThreads/threads/Atomics.h + KodiThreads/threads/Condition.h + KodiThreads/threads/CriticalSection.h + KodiThreads/threads/Event.h + KodiThreads/threads/Helpers.h + KodiThreads/threads/Lockables.h + KodiThreads/threads/MipsAtomics.h + KodiThreads/threads/SharedSection.h + KodiThreads/threads/SingleLock.h + KodiThreads/threads/SystemClock.h + KodiThreads/threads/Thread.h + KodiThreads/threads/ThreadImpl.h + KodiThreads/threads/ThreadLocal.h + KodiThreads/threads/Timer.h + KodiThreads/threads/platform/Condition.h + KodiThreads/threads/platform/CriticalSection.h + KodiThreads/threads/platform/ThreadImpl.h + KodiThreads/threads/platform/ThreadLocal.h + + KodiThreads/system.h + KodiThreads/commons/Exception.h) + +include_directories(${p8-platform_INCLUDE_DIRS}) + +if(MSVC) + list(APPEND SOURCES KodiThreads/threads/platform/win/Win32Exception.cpp) + list(APPEND HEADERS KodiThreads/threads/platform/win/Win32Exception.h) +endif() + +addon_source_group("${SOURCES}") +addon_source_group("${HEADERS}") + +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) +add_library(kodi::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +target_include_directories(${PROJECT_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/KodiThreads + INTERFACE $ + $) +if(MSVC) + target_link_libraries(${PROJECT_NAME} PUBLIC winmm.lib) +endif() + +# install all specific module files +install(TARGETS ${PROJECT_NAME} EXPORT kodi DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/KodiThreads/commons/Exception.cpp b/KodiThreads/commons/Exception.cpp new file mode 100644 index 0000000..6ba0613 --- /dev/null +++ b/KodiThreads/commons/Exception.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "Exception.h" + +namespace XbmcCommons +{ + Exception::~Exception() {} +} + diff --git a/KodiThreads/commons/Exception.h b/KodiThreads/commons/Exception.h new file mode 100644 index 0000000..75e066e --- /dev/null +++ b/KodiThreads/commons/Exception.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include +#include +#include "system.h" + +#ifdef __GNUC__ +// The 'this' pointer counts as a parameter on member methods. +#define XBMCCOMMONS_ATTRIB_EXCEPTION_FORMAT __attribute__((format(printf,2,3))) +#else +#define XBMCCOMMONS_ATTRIB_EXCEPTION_FORMAT +#endif + +#define XBMCCOMMONS_COPYVARARGS(fmt) va_list argList; va_start(argList, fmt); Set(fmt, argList); va_end(argList) +#define XBMCCOMMONS_STANDARD_EXCEPTION(E) \ + class E : public XbmcCommons::Exception \ + { \ + public: \ + inline E(const char* message,...) XBMCCOMMONS_ATTRIB_EXCEPTION_FORMAT : Exception(#E) { XBMCCOMMONS_COPYVARARGS(message); } \ + \ + inline E(const E& other) : Exception(other) {} \ + } + +namespace XbmcCommons +{ + /** + * This class a superclass for exceptions that want to utilize some + * utility functionality including autologging with the specific + * exception name. + */ + class Exception + { + private: + + std::string classname; + std::string message; + + protected: + + inline Exception(const char* classname_) : classname(classname_) { } + inline Exception(const char* classname_, const char* message_) : classname(classname_), message(message_) { } + inline Exception(const Exception& other) : classname(other.classname), message(other.message) { } + + /** + * This method is called from the constructor of subclasses. It + * will set the message from varargs as well as call log message + */ + inline void Set(const char* fmt, va_list& argList) + { + message = StringUtils::FormatV(fmt, argList); + } + + /** + * This message can be called from the constructor of subclasses. + * It will set the message and log the throwing. + */ + inline void SetMessage(const char* fmt, ...) XBMCCOMMONS_ATTRIB_EXCEPTION_FORMAT + { + // calls 'set' + XBMCCOMMONS_COPYVARARGS(fmt); + } + + inline void setClassname(const char* cn) { classname = cn; } + + public: + virtual ~Exception(); + + inline virtual void LogThrowMessage(const char* prefix = NULL) const + { + LOG(LOGERROR, "EXCEPTION Thrown (%s) : %s", classname.c_str(), message.c_str()); + } + + inline virtual const char* GetMessage() const { return message.c_str(); } + }; + + /** + * This class forms the base class for unchecked exceptions. Unchecked exceptions + * are those that really shouldn't be handled explicitly. For example, on windows + * when a access violaton is converted to a win32_exception, there's nothing + * that can be done in most code. The outer most stack frame might try to + * do some error logging prior to shutting down, but that's really it. + */ + XBMCCOMMONS_STANDARD_EXCEPTION(UncheckedException); + +/** + * In cases where you catch(...){} you will (may) inadvertently be + * catching UncheckedException's. Therefore this macro will allow + * you to do something equivalent to: + * catch (anything except UncheckedException) {} + * + * In order to avoid catching UncheckedException, use the macro as follows: + * + * try { ... } + * XBMCCOMMONS_HANDLE_UNCHECKED + * catch(...){ ... } + */ +// Yes. I recognize that the name of this macro is an oxymoron. +#define XBMCCOMMONS_HANDLE_UNCHECKED \ + catch (const XbmcCommons::UncheckedException& ) { throw; } \ + catch (const XbmcCommons::UncheckedException* ) { throw; } +} diff --git a/KodiThreads/system.cpp b/KodiThreads/system.cpp new file mode 100644 index 0000000..7fa42f5 --- /dev/null +++ b/KodiThreads/system.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2016 Team KODI + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kodi; see the file COPYING. If not, see + * . + * + */ + + + +#include "system.h" +#include "threads/CriticalSection.h" +#include "threads/SingleLock.h" + +static CCriticalSection loc_CriticalSection; +static LogCallback_t loc_LoggerCallback; + +int g_LOGERROR; +int g_LOGDEBUG; +int g_LOGINFO; +int g_LOGNOTICE; + + +void KodiThreadsLogger(int LogLevel, const char *format, ...) +{ + CSingleLock lock(loc_CriticalSection); + + if (loc_LoggerCallback) + { + } +} + +void SetKodiThreadsLogger(LogCallback_t LoggerCallback) +{ + CSingleLock lock(loc_CriticalSection); + + loc_LoggerCallback = LoggerCallback; +} + +void ReleaseKodiThreadsLogger() +{ + CSingleLock lock(loc_CriticalSection); + + loc_LoggerCallback = nullptr; +} diff --git a/KodiThreads/system.h b/KodiThreads/system.h new file mode 100644 index 0000000..e9f8791 --- /dev/null +++ b/KodiThreads/system.h @@ -0,0 +1,42 @@ +#pragma once + +/* + * Copyright (C) 2005-2016 Team KODI + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kodi; see the file COPYING. If not, see + * . + * + */ + + + +#include + +extern int g_LOGERROR; +extern int g_LOGDEBUG; +extern int g_LOGINFO; +extern int g_LOGNOTICE; + +typedef void (*LogCallback_t)(int loglevel, const char *format, ...); + +void KodiThreadsLogger(int LogLevel, const char *format, ...); +void SetKodiThreadsLogger(LogCallback_t LoggerCallback); +void ReleaseKodiThreadsLogger(); + +#define LOG KodiThreadsLogger +#define LOGERROR g_LOGERROR +#define LOGDEBUG g_LOGDEBUG +#define LOGINFO g_LOGINFO +#define LOGNOTICE g_LOGNOTICE diff --git a/KodiThreads/threads/Atomics.cpp b/KodiThreads/threads/Atomics.cpp new file mode 100644 index 0000000..ecba3f1 --- /dev/null +++ b/KodiThreads/threads/Atomics.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "Atomics.h" +#include "system.h" + +#if defined(__mips__) +#include "MipsAtomics.h" +pthread_mutex_t cmpxchg_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/////////////////////////////////////////////////////////////////////////// +// 32-bit atomic compare-and-swap +// Returns previous value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long cas(volatile long *pAddr, long expectedVal, long swapVal) +{ +#if defined(HAS_BUILTIN_SYNC_VAL_COMPARE_AND_SWAP) + return(__sync_val_compare_and_swap(pAddr, expectedVal, swapVal)); +#elif defined(__ppc__) || defined(__powerpc__) // PowerPC + unsigned int prev; + __asm__ __volatile__ ( + " 1: lwarx %0,0,%2 \n" /* Load the current value of *pAddr(%2) into prev (%0) and lock pAddr, */ + " cmpw 0,%0,%3 \n" /* Verify that the current value (%2) == old value (%3) */ + " bne- 2f \n" /* Bail if the two values are not equal [not as expected] */ + " stwcx. %4,0,%2 \n" /* Attempt to store swapVal (%4) value into *pAddr (%2) [p must still be reserved] */ + " bne- 1b \n" /* Loop if p was no longer reserved */ + " isync \n" /* Reconcile multiple processors [if present] */ + " 2: \n" + : "=&r" (prev), "+m" (*pAddr) /* Outputs [prev, *pAddr] */ + : "r" (pAddr), "r" (expectedVal), "r" (swapVal) /* Inputs [pAddr, expectedVal, swapVal] */ + : "cc", "memory"); /* Clobbers */ + return prev; + +#elif defined(__arm__) + long prev; + asm volatile ( + "dmb ish \n" // Memory barrier. Make sure all memory accesses appearing before this complete before any that appear after + "1: \n" + "ldrex %0, [%1] \n" // Load the current value of *pAddr(%1) into prev (%0) and lock pAddr, + "cmp %0, %2 \n" // Verify that the current value (%0) == old value (%2) + "bne 2f \n" // Bail if the two values are not equal [not as expected] + "strex r1, %3, [%1] \n" + "cmp r1, #0 \n" + "bne 1b \n" + "dmb ish \n" // Memory barrier. + "2: \n" + : "=&r" (prev) + : "r"(pAddr), "r"(expectedVal),"r"(swapVal) + : "r1" + ); + return prev; + +#elif defined(__mips__) + return cmpxchg32(pAddr, expectedVal, swapVal); + +#elif defined(TARGET_WINDOWS) + long prev; + __asm + { + // Load parameters + mov eax, expectedVal ; + mov ebx, pAddr ; + mov ecx, swapVal ; + + // Do Swap + lock cmpxchg dword ptr [ebx], ecx ; + + // Store the return value + mov prev, eax; + } + return prev; + +#else // Linux / OSX86 (GCC) + long prev; + __asm__ __volatile__ ( + "lock/cmpxchg %1, %2" + : "=a" (prev) + : "r" (swapVal), "m" (*pAddr), "0" (expectedVal) + : "memory" ); + return prev; + +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// 64-bit atomic compare-and-swap +// Returns previous value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long long cas2(volatile long long* pAddr, long long expectedVal, long long swapVal) +{ +#if defined(__ppc__) || defined(__powerpc__) || defined(__arm__) || defined(__aarch64__)// PowerPC and ARM +// Not available/required +// Hack to allow compilation + throw "cas2 is not implemented"; + +#elif defined(__mips__) + return cmpxchg64(pAddr, expectedVal, swapVal); + +#elif defined(TARGET_WINDOWS) + long long prev; + __asm + { + mov esi, pAddr ; + mov eax, dword ptr [expectedVal] ; + mov edx, dword ptr expectedVal[4] ; + mov ebx, dword ptr [swapVal] ; + mov ecx, dword ptr swapVal[4] ; + lock cmpxchg8b qword ptr [esi] ; + mov dword ptr [prev], eax ; + mov dword ptr prev[4], edx ; + } + return prev; + +#else // Linux / OSX86 (GCC) + #if !defined (__x86_64) + long long prev; + __asm__ volatile ( + " push %%ebx \n" // We have to manually handle ebx, because PIC uses it and the compiler refuses to build anything that touches it + " mov %4, %%ebx \n" + " lock/cmpxchg8b (%%esi) \n" + " pop %%ebx" + : "=A" (prev) + : "c" ((unsigned long)(swapVal >> 32)), "0" (expectedVal), "S" (pAddr), "m" (swapVal) + : "memory"); + return prev; + #else + // Hack to allow compilation on x86_64 + throw "cas2 is not implemented on x86_64!"; + #endif +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// 32-bit atomic increment +// Returns new value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long AtomicIncrement(volatile long* pAddr) +{ +#if defined(HAS_BUILTIN_SYNC_ADD_AND_FETCH) + return __sync_add_and_fetch(pAddr, 1); + +#elif defined(__ppc__) || defined(__powerpc__) // PowerPC + long val; + __asm__ __volatile__ ( + "sync \n" + "1: lwarx %0, 0, %1 \n" + "addic %0, %0, 1 \n" + "stwcx. %0, 0, %1 \n" + "bne- 1b \n" + "isync" + : "=&r" (val) + : "r" (pAddr) + : "cc", "xer", "memory"); + return val; + +#elif defined(__arm__) && !defined(__ARM_ARCH_5__) + long val; + asm volatile ( + "dmb ish \n" // Memory barrier. Make sure all memory accesses appearing before this complete before any that appear after + "1: \n" + "ldrex %0, [%1] \n" // (val = *pAddr) + "add %0, #1 \n" // (val += 1) + "strex r1, %0, [%1] \n" + "cmp r1, #0 \n" + "bne 1b \n" + "dmb ish \n" // Memory barrier. + : "=&r" (val) + : "r"(pAddr) + : "r1" + ); + return val; + +#elif defined(__mips__) + return atomic_add(1, pAddr); + +#elif defined(TARGET_WINDOWS) + long val; + __asm + { + mov eax, pAddr ; + lock inc dword ptr [eax] ; + mov eax, [eax] ; + mov val, eax ; + } + return val; + +#elif defined(__x86_64__) + long result; + __asm__ __volatile__ ( + "lock/xaddq %q0, %1" + : "=r" (result), "=m" (*pAddr) + : "0" ((long) (1)), "m" (*pAddr)); + return *pAddr; + +#else // Linux / OSX86 (GCC) + long reg __asm__ ("eax") = 1; + __asm__ __volatile__ ( + "lock/xadd %0, %1 \n" + "inc %%eax" + : "+r" (reg) + : "m" (*pAddr) + : "memory" ); + return reg; + +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// 32-bit atomic add +// Returns new value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long AtomicAdd(volatile long* pAddr, long amount) +{ +#if defined(HAS_BUILTIN_SYNC_ADD_AND_FETCH) + return __sync_add_and_fetch(pAddr, amount); + +#elif defined(__ppc__) || defined(__powerpc__) // PowerPC + long val; + __asm__ __volatile__ ( + "sync \n" + "1: lwarx %0, 0, %1 \n" + "add %0, %2, %0 \n" + "stwcx. %0, 0, %1 \n" + "bne- 1b \n" + "isync" + : "=&r" (val) + : "r" (pAddr), "r" (amount) + : "cc", "memory"); + return val; + +#elif defined(__arm__) && !defined(__ARM_ARCH_5__) + long val; + asm volatile ( + "dmb ish \n" // Memory barrier. Make sure all memory accesses appearing before this complete before any that appear after + "1: \n" + "ldrex %0, [%1] \n" // (val = *pAddr) + "add %0, %2 \n" // (val += amount) + "strex r1, %0, [%1] \n" + "cmp r1, #0 \n" + "bne 1b \n" + "dmb ish \n" // Memory barrier. + : "=&r" (val) + : "r"(pAddr), "r"(amount) + : "r1" + ); + return val; + +#elif defined(__mips__) + return atomic_add(amount, pAddr); + +#elif defined(TARGET_WINDOWS) + __asm + { + mov eax, amount; + mov ebx, pAddr; + lock xadd dword ptr [ebx], eax; + mov ebx, [ebx]; + mov amount, ebx; + } + return amount; + +#elif defined(__x86_64__) + long result; + __asm__ __volatile__ ( + "lock/xaddq %q0, %1" + : "=r" (result), "=m" (*pAddr) + : "0" ((long) (amount)), "m" (*pAddr)); + return *pAddr; + +#else // Linux / OSX86 (GCC) + long reg __asm__ ("eax") = amount; + __asm__ __volatile__ ( + "lock/xadd %0, %1 \n" + "dec %%eax" + : "+r" (reg) + : "m" (*pAddr) + : "memory" ); + return reg; + +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// 32-bit atomic decrement +// Returns new value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long AtomicDecrement(volatile long* pAddr) +{ +#if defined(HAS_BUILTIN_SYNC_SUB_AND_FETCH) + return __sync_sub_and_fetch(pAddr, 1); + +#elif defined(__ppc__) || defined(__powerpc__) // PowerPC + long val; + __asm__ __volatile__ ( + "sync \n" +"1: lwarx %0, 0, %1 \n" + "addic %0, %0, -1 \n" + "stwcx. %0, 0, %1 \n" + "bne- 1b \n" + "isync" + : "=&r" (val) + : "r" (pAddr) + : "cc", "xer", "memory"); + return val; + +#elif defined(__arm__) + long val; + asm volatile ( + "dmb ish \n" // Memory barrier. Make sure all memory accesses appearing before this complete before any that appear after + "1: \n" + "ldrex %0, [%1] \n" // (val = *pAddr) + "sub %0, #1 \n" // (val -= 1) + "strex r1, %0, [%1] \n" + "cmp r1, #0 \n" + "bne 1b \n" + "dmb ish \n" // Memory barrier. + : "=&r" (val) + : "r"(pAddr) + : "r1" + ); + return val; + +#elif defined(__mips__) + return atomic_sub(1, pAddr); + +#elif defined(TARGET_WINDOWS) + long val; + __asm + { + mov eax, pAddr ; + lock dec dword ptr [eax] ; + mov eax, [eax] ; + mov val, eax ; + } + return val; + +#elif defined(__x86_64__) + long result; + __asm__ __volatile__ ( + "lock/xaddq %q0, %1" + : "=r" (result), "=m" (*pAddr) + : "0" ((long) (-1)), "m" (*pAddr)); + return *pAddr; + +#else // Linux / OSX86 (GCC) + long reg __asm__ ("eax") = -1; + __asm__ __volatile__ ( + "lock/xadd %0, %1 \n" + "dec %%eax" + : "+r" (reg) + : "m" (*pAddr) + : "memory" ); + return reg; + +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// 32-bit atomic subtract +// Returns new value of *pAddr +/////////////////////////////////////////////////////////////////////////// +long AtomicSubtract(volatile long* pAddr, long amount) +{ +#if defined(HAS_BUILTIN_SYNC_SUB_AND_FETCH) + return __sync_sub_and_fetch(pAddr, amount); + +#elif defined(__ppc__) || defined(__powerpc__) // PowerPC + long val; + amount *= -1; + __asm__ __volatile__ ( + "sync \n" + "1: lwarx %0, 0, %1 \n" + "add %0, %2, %0 \n" + "stwcx. %0, 0, %1 \n" + "bne- 1b \n" + "isync" + : "=&r" (val) + : "r" (pAddr), "r" (amount) + : "cc", "memory"); + return val; + +#elif defined(__arm__) + long val; + asm volatile ( + "dmb ish \n" // Memory barrier. Make sure all memory accesses appearing before this complete before any that appear after + "1: \n" + "ldrex %0, [%1] \n" // (val = *pAddr) + "sub %0, %2 \n" // (val -= amount) + "strex r1, %0, [%1] \n" + "cmp r1, #0 \n" + "bne 1b \n" + "dmb ish \n" // Memory barrier. + : "=&r" (val) + : "r"(pAddr), "r"(amount) + : "r1" + ); + return val; + +#elif defined(__mips__) + return atomic_sub(amount, pAddr); + +#elif defined(TARGET_WINDOWS) + amount *= -1; + __asm + { + mov eax, amount; + mov ebx, pAddr; + lock xadd dword ptr [ebx], eax; + mov ebx, [ebx]; + mov amount, ebx; + } + return amount; + +#elif defined(__x86_64__) + long result; + __asm__ __volatile__ ( + "lock/xaddq %q0, %1" + : "=r" (result), "=m" (*pAddr) + : "0" ((long) (-1 * amount)), "m" (*pAddr)); + return *pAddr; + +#else // Linux / OSX86 (GCC) + long reg __asm__ ("eax") = -1 * amount; + __asm__ __volatile__ ( + "lock/xadd %0, %1 \n" + "dec %%eax" + : "+r" (reg) + : "m" (*pAddr) + : "memory" ); + return reg; + +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// Fast spinlock implmentation. No backoff when busy +/////////////////////////////////////////////////////////////////////////// +CAtomicSpinLock::CAtomicSpinLock(long& lock) : m_Lock(lock) +{ + while (cas(&m_Lock, 0, 1) != 0) {} // Lock +} +CAtomicSpinLock::~CAtomicSpinLock() +{ + m_Lock = 0; // Unlock +} diff --git a/KodiThreads/threads/Atomics.h b/KodiThreads/threads/Atomics.h new file mode 100644 index 0000000..d28d62d --- /dev/null +++ b/KodiThreads/threads/Atomics.h @@ -0,0 +1,41 @@ +#pragma once + +/* + * Copyright (C) 2005-2015 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kodi; see the file COPYING. If not, see + * . + * + */ + +//! @todo Inline these methods +long cas(volatile long *pAddr, long expectedVal, long swapVal); +#if !defined(__ppc__) && !defined(__powerpc__) && !defined(__arm__) +long long cas2(volatile long long* pAddr, long long expectedVal, long long swapVal); +#endif +long AtomicIncrement(volatile long* pAddr); +long AtomicDecrement(volatile long* pAddr); +long AtomicAdd(volatile long* pAddr, long amount); +long AtomicSubtract(volatile long* pAddr, long amount); + +class CAtomicSpinLock +{ +public: + CAtomicSpinLock(long& lock); + ~CAtomicSpinLock(); +private: + long& m_Lock; +}; + diff --git a/KodiThreads/threads/CMakeLists.txt b/KodiThreads/threads/CMakeLists.txt new file mode 100644 index 0000000..947357e --- /dev/null +++ b/KodiThreads/threads/CMakeLists.txt @@ -0,0 +1,27 @@ +set(SOURCES Atomics.cpp + Event.cpp + Thread.cpp + Timer.cpp + SystemClock.cpp + platform/Implementation.cpp) + +set(HEADERS Atomics.h + Condition.h + CriticalSection.h + Event.h + Helpers.h + Lockables.h + MipsAtomics.h + SharedSection.h + SingleLock.h + SystemClock.h + Thread.h + ThreadImpl.h + ThreadLocal.h + Timer.h + platform/Condition.h + platform/CriticalSection.h + platform/ThreadImpl.h + platform/ThreadLocal.h) + +core_add_library(threads) diff --git a/KodiThreads/threads/Condition.h b/KodiThreads/threads/Condition.h new file mode 100644 index 0000000..30359ac --- /dev/null +++ b/KodiThreads/threads/Condition.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/platform/Condition.h" + +#include "threads/SystemClock.h" +#include + +namespace XbmcThreads +{ + + /** + * This is a condition variable along with its predicate. This allows the use of a + * condition variable without the spurious returns since the state being monitored + * is also part of the condition. + * + * L should implement the Lockable concept + * + * The requirements on P are that it can act as a predicate (that is, I can use + * it in an 'while(!predicate){...}' where 'predicate' is of type 'P'). + */ + template class TightConditionVariable + { + ConditionVariable& cond; + P predicate; + + public: + inline TightConditionVariable(ConditionVariable& cv, P predicate_) : cond(cv), predicate(predicate_) {} + + template inline void wait(L& lock) { while(!predicate) cond.wait(lock); } + template inline bool wait(L& lock, unsigned long milliseconds) + { + bool ret = true; + if (!predicate) + { + if (!milliseconds) + { + cond.wait(lock,milliseconds /* zero */); + return !(!predicate); // eh? I only require the ! operation on P + } + else + { + EndTime endTime((unsigned int)milliseconds); + for (bool notdone = true; notdone && ret == true; + ret = (notdone = (!predicate)) ? ((milliseconds = endTime.MillisLeft()) != 0) : true) + cond.wait(lock,milliseconds); + } + } + return ret; + } + + inline void notifyAll() { cond.notifyAll(); } + inline void notify() { cond.notify(); } + }; +} + diff --git a/KodiThreads/threads/CriticalSection.h b/KodiThreads/threads/CriticalSection.h new file mode 100644 index 0000000..6257218 --- /dev/null +++ b/KodiThreads/threads/CriticalSection.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/platform/CriticalSection.h" diff --git a/KodiThreads/threads/Event.cpp b/KodiThreads/threads/Event.cpp new file mode 100644 index 0000000..b7e5282 --- /dev/null +++ b/KodiThreads/threads/Event.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2002 Frodo + * Portions Copyright (c) by the authors of ffmpeg and xvid + * Copyright (C) 2002-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include +#include + +#include "Event.h" + +void CEvent::addGroup(XbmcThreads::CEventGroup* group) +{ + CSingleLock lock(groupListMutex); + if (groups == NULL) + groups = new std::vector(); + + groups->push_back(group); +} + +void CEvent::removeGroup(XbmcThreads::CEventGroup* group) +{ + CSingleLock lock(groupListMutex); + if (groups) + { + for (std::vector::iterator iter = groups->begin(); iter != groups->end(); ++iter) + { + if ((*iter) == group) + { + groups->erase(iter); + break; + } + } + + if (groups->size() <= 0) + { + delete groups; + groups = NULL; + } + } +} + +// locking is ALWAYS done in this order: +// CEvent::groupListMutex -> CEventGroup::mutex -> CEvent::mutex +void CEvent::Set() +{ + // Originally I had this without locking. Thanks to FernetMenta who + // pointed out that this creates a race condition between setting + // checking the signal and calling wait() on the Wait call in the + // CEvent class. This now perfectly matches the boost example here: + // http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + { + CSingleLock slock(mutex); + signaled = true; + } + + condVar.notifyAll(); + + CSingleLock l(groupListMutex); + if (groups) + { + for (std::vector::iterator iter = groups->begin(); + iter != groups->end(); ++iter) + (*iter)->Set(this); + } +} + +namespace XbmcThreads +{ + /** + * This will block until any one of the CEvents in the group are + * signaled at which point a pointer to that CEvents will be + * returned. + */ + CEvent* CEventGroup::wait() + { + return wait(std::numeric_limits::max()); + } + + /** + * This will block until any one of the CEvents in the group are + * signaled or the timeout is reachec. If an event is signaled then + * it will return a pointer to that CEvent, otherwise it will return + * NULL. + */ + // locking is ALWAYS done in this order: + // CEvent::groupListMutex -> CEventGroup::mutex -> CEvent::mutex + // + // Notice that this method doesn't grab the CEvent::groupListMutex at all. This + // is fine. It just grabs the CEventGroup::mutex and THEN the individual + // CEvent::mutex's + CEvent* CEventGroup::wait(unsigned int milliseconds) + { + CSingleLock lock(mutex); // grab CEventGroup::mutex + numWaits++; + + // ================================================== + // This block checks to see if any child events are + // signaled and sets 'signaled' to the first one it + // finds. + // ================================================== + signaled = NULL; + for (std::vector::iterator iter = events.begin(); + signaled == NULL && iter != events.end(); ++iter) + { + CEvent* cur = *iter; + CSingleLock lock2(cur->mutex); + if (cur->signaled) + signaled = cur; + } + // ================================================== + + if(!signaled) + { + // both of these release the CEventGroup::mutex + if (milliseconds == std::numeric_limits::max()) + condVar.wait(mutex); + else + condVar.wait(mutex,milliseconds); + } // at this point the CEventGroup::mutex is reacquired + numWaits--; + + // signaled should have been set by a call to CEventGroup::Set + CEvent* ret = signaled; + if (numWaits == 0) + { + if (signaled) + // This acquires and releases the CEvent::mutex. This is fine since the + // CEventGroup::mutex is already being held + signaled->WaitMSec(0); // reset the event if needed + signaled = NULL; // clear the signaled if all the waiters are gone + } + return ret; + } + + CEventGroup::CEventGroup(int num, CEvent* v1, ...) : signaled(NULL), condVar(actualCv,signaled), numWaits(0) + { + va_list ap; + + va_start(ap, v1); + if (v1) + events.push_back(v1); + num--; // account for v1 + for (; num > 0; num--) + { + CEvent* const cur = va_arg(ap, CEvent*); + if (cur) + events.push_back(cur); + } + va_end(ap); + + // we preping for a wait, so we need to set the group value on + // all of the CEvents. + for (std::vector::iterator iter = events.begin(); + iter != events.end(); ++iter) + (*iter)->addGroup(this); + } + + CEventGroup::CEventGroup(CEvent* v1, ...) : signaled(NULL), condVar(actualCv,signaled), numWaits(0) + { + va_list ap; + + va_start(ap, v1); + if (v1) + events.push_back(v1); + bool done = false; + while(!done) + { + CEvent* cur = va_arg(ap,CEvent*); + if (cur) + events.push_back(cur); + else + done = true; + } + va_end(ap); + + // we preping for a wait, so we need to set the group value on + // all of the CEvents. + for (std::vector::iterator iter = events.begin(); + iter != events.end(); ++iter) + (*iter)->addGroup(this); + } + + CEventGroup::~CEventGroup() + { + for (std::vector::iterator iter = events.begin(); + iter != events.end(); ++iter) + (*iter)->removeGroup(this); + } +} diff --git a/KodiThreads/threads/Event.h b/KodiThreads/threads/Event.h new file mode 100644 index 0000000..5888f07 --- /dev/null +++ b/KodiThreads/threads/Event.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include + +#include "threads/Condition.h" +#include "threads/SingleLock.h" + +// forward declare the CEventGroup +namespace XbmcThreads +{ + class CEventGroup; +} + + +/** + * This is an Event class built from a ConditionVariable. The Event adds the state + * that the condition is gating as well as the mutex/lock. + * + * This Event can be 'interruptible' (even though there is only a single place + * in the code that uses this behavior). + * + * This class manages 'spurious returns' from the condition variable. + */ +class CEvent : public XbmcThreads::NonCopyable +{ + bool manualReset; + volatile bool signaled; + unsigned int numWaits; + + CCriticalSection groupListMutex; // lock for the groups list + std::vector * groups; + + /** + * To satisfy the TightConditionVariable requirements and allow the + * predicate being monitored to include both the signaled and interrupted + * states. + */ + XbmcThreads::ConditionVariable actualCv; + XbmcThreads::TightConditionVariable condVar; + CCriticalSection mutex; + + friend class XbmcThreads::CEventGroup; + + void addGroup(XbmcThreads::CEventGroup* group); + void removeGroup(XbmcThreads::CEventGroup* group); + + // helper for the two wait methods + inline bool prepReturn() { bool ret = signaled; if (!manualReset && numWaits == 0) signaled = false; return ret; } + +public: + inline CEvent(bool manual = false, bool signaled_ = false) : + manualReset(manual), signaled(signaled_), numWaits(0), groups(NULL), condVar(actualCv,signaled) {} + + inline void Reset() { CSingleLock lock(mutex); signaled = false; } + void Set(); + + /** Returns true if Event has been triggered and not reset, false otherwise. */ + inline bool Signaled() { CSingleLock lock(mutex); return signaled; } + + /** + * This will wait up to 'milliSeconds' milliseconds for the Event + * to be triggered. The method will return 'true' if the Event + * was triggered. Otherwise it will return false. + */ + inline bool WaitMSec(unsigned int milliSeconds) + { CSingleLock lock(mutex); numWaits++; condVar.wait(mutex,milliSeconds); numWaits--; return prepReturn(); } + + /** + * This will wait for the Event to be triggered. The method will return + * 'true' if the Event was triggered. If it was either interrupted + * it will return false. Otherwise it will return false. + */ + inline bool Wait() + { CSingleLock lock(mutex); numWaits++; condVar.wait(mutex); numWaits--; return prepReturn(); } + + /** + * This is mostly for testing. It allows a thread to make sure there are + * the right amount of other threads waiting. + */ + inline int getNumWaits() { CSingleLock lock(mutex); return numWaits; } + +}; + +namespace XbmcThreads +{ + /** + * CEventGroup is a means of grouping CEvents to wait on them together. + * It is equivalent to WaitOnMultipleObject that returns when "any" Event + * in the group signaled. + */ + class CEventGroup : public NonCopyable + { + std::vector events; + CEvent* signaled; + XbmcThreads::ConditionVariable actualCv; + XbmcThreads::TightConditionVariable condVar; + CCriticalSection mutex; + + unsigned int numWaits; + + // This is ONLY called from CEvent::Set. + inline void Set(CEvent* child) { CSingleLock l(mutex); signaled = child; condVar.notifyAll(); } + + friend class ::CEvent; + + public: + + /** + * Create a CEventGroup from a number of CEvents. num is the number + * of Events that follow. E.g.: + * + * CEventGroup g(3, event1, event2, event3); + */ + CEventGroup(int num, CEvent* v1, ...); + + /** + * Create a CEventGroup from a number of CEvents. The parameters + * should form a NULL terminated list of CEvent*'s + * + * CEventGroup g(event1, event2, event3, NULL); + */ + CEventGroup(CEvent* v1, ...); + ~CEventGroup(); + + /** + * This will block until any one of the CEvents in the group are + * signaled at which point a pointer to that CEvents will be + * returned. + */ + CEvent* wait(); + + /** + * This will block until any one of the CEvents in the group are + * signaled or the timeout is reachec. If an event is signaled then + * it will return a pointer to that CEvent, otherwise it will return + * NULL. + */ + CEvent* wait(unsigned int milliseconds); + + /** + * This is mostly for testing. It allows a thread to make sure there are + * the right amount of other threads waiting. + */ + inline int getNumWaits() { CSingleLock lock(mutex); return numWaits; } + + }; +} diff --git a/KodiThreads/threads/Helpers.h b/KodiThreads/threads/Helpers.h new file mode 100644 index 0000000..2511086 --- /dev/null +++ b/KodiThreads/threads/Helpers.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +namespace XbmcThreads +{ + /** + * Any class that inherits from NonCopyable will ... not be copyable (Duh!) + */ + class NonCopyable + { + inline NonCopyable(const NonCopyable& ) {} + inline NonCopyable& operator=(const NonCopyable& ) { return *this; } + public: + inline NonCopyable() {} + }; + + /** + * This will create a new predicate from an old predicate P with + * inverse truth value. This predicate is safe to use in a + * TightConditionVariable

+ */ + template class InversePredicate + { + P predicate; + + public: + inline InversePredicate(P predicate_) : predicate(predicate_) {} + inline InversePredicate(const InversePredicate

& other) : predicate(other.predicate) {} + inline InversePredicate

& operator=(InversePredicate

& other) { predicate = other.predicate; } + + inline bool operator!() const { return !(!predicate); } + }; + +} + diff --git a/KodiThreads/threads/Lockables.h b/KodiThreads/threads/Lockables.h new file mode 100644 index 0000000..865a258 --- /dev/null +++ b/KodiThreads/threads/Lockables.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/Helpers.h" + +namespace XbmcThreads +{ + + /** + * This template will take any implementation of the "Lockable" concept + * and allow it to be used as an "Exitable Lockable." + * + * Something that implements the "Lockable concept" simply means that + * it has the three methods: + * + * lock(); + * try_lock(); + * unlock(); + * + * "Exitable" specifially means that, no matter how deep the recursion + * on the mutex/critical section, we can exit from it and then restore + * the state. + * + * This requires us to extend the Lockable so that we can keep track of the + * number of locks that have been recursively acquired so that we can + * undo it, and then restore that (See class CSingleExit). + * + * All xbmc code expects Lockables to be recursive. + */ + template class CountingLockable : public NonCopyable + { + friend class ConditionVariable; + protected: + L mutex; + unsigned int count; + + public: + inline CountingLockable() : count(0) {} + + // boost::thread Lockable concept + inline void lock() { mutex.lock(); count++; } + inline bool try_lock() { return mutex.try_lock() ? count++, true : false; } + inline void unlock() { count--; mutex.unlock(); } + + /** + * This implements the "exitable" behavior mentioned above. + * + * This can be used to ALMOST exit, but not quite, by passing + * the number of locks to leave. This is used in the windows + * ConditionVariable which requires that the lock be entered + * only once, and so it backs out ALMOST all the way, but + * leaves one still there. + */ + inline unsigned int exit(unsigned int leave = 0) + { + // it's possibe we don't actually own the lock + // so we will try it. + unsigned int ret = 0; + if (try_lock()) + { + if (leave < (count - 1)) + { + ret = count - 1 - leave; // The -1 is because we don't want + // to count the try_lock increment. + // We must NOT compare "count" in this loop since + // as soon as the last unlock is called another thread + // can modify it. + for (unsigned int i = 0; i < ret; i++) + unlock(); + } + unlock(); // undo the try_lock before returning + } + + return ret; + } + + /** + * Restore a previous exit to the provided level. + */ + inline void restore(unsigned int restoreCount) + { + for (unsigned int i = 0; i < restoreCount; i++) + lock(); + } + + /** + * Some implementations (see pthreads) require access to the underlying + * CCriticalSection, which is also implementation specific. This + * provides access to it through the same method on the guard classes + * UniqueLock, and SharedLock. + * + * There really should be no need for the users of the threading library + * to call this method. + */ + inline L& get_underlying() { return mutex; } + }; + + + /** + * This template can be used to define the base implementation for any UniqueLock + * (such as CSingleLock) that uses a Lockable as its mutex/critical section. + */ + template class UniqueLock : public NonCopyable + { + protected: + L& mutex; + bool owns; + inline UniqueLock(L& lockable) : mutex(lockable), owns(true) { mutex.lock(); } + inline UniqueLock(L& lockable, bool try_to_lock_discrim ) : mutex(lockable) { owns = mutex.try_lock(); } + inline ~UniqueLock() { if (owns) mutex.unlock(); } + + public: + + inline bool owns_lock() const { return owns; } + + //This also implements lockable + inline void lock() { mutex.lock(); owns=true; } + inline bool try_lock() { return (owns = mutex.try_lock()); } + inline void unlock() { if (owns) { mutex.unlock(); owns=false; } } + + /** + * See the note on the same method on CountingLockable + */ + inline L& get_underlying() { return mutex; } + }; + + /** + * This template can be used to define the base implementation for any SharedLock + * (such as CSharedLock) that uses a Shared Lockable as its mutex/critical section. + * + * Something that implements the "Shared Lockable" concept has all of the methods + * required by the Lockable concept and also: + * + * void lock_shared(); + * bool try_lock_shared(); + * void unlock_shared(); + */ + template class SharedLock : public NonCopyable + { + protected: + L& mutex; + bool owns; + inline SharedLock(L& lockable) : mutex(lockable), owns(true) { mutex.lock_shared(); } + inline ~SharedLock() { if (owns) mutex.unlock_shared(); } + + inline bool owns_lock() const { return owns; } + inline void lock() { mutex.lock_shared(); owns = true; } + inline bool try_lock() { return (owns = mutex.try_lock_shared()); } + inline void unlock() { if (owns) mutex.unlock_shared(); owns = false; } + + /** + * See the note on the same method on CountingLockable + */ + inline L& get_underlying() { return mutex; } + }; + + +} diff --git a/KodiThreads/threads/Makefile b/KodiThreads/threads/Makefile new file mode 100644 index 0000000..fc2c02d --- /dev/null +++ b/KodiThreads/threads/Makefile @@ -0,0 +1,11 @@ +SRCS=Atomics.cpp \ + Event.cpp \ + Thread.cpp \ + Timer.cpp \ + SystemClock.cpp \ + platform/Implementation.cpp + +LIB=threads.a + +include ../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/KodiThreads/threads/MipsAtomics.h b/KodiThreads/threads/MipsAtomics.h new file mode 100644 index 0000000..a2cf686 --- /dev/null +++ b/KodiThreads/threads/MipsAtomics.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + * Copyright (C) 2003, 06, 07 by Ralf Baechle (ralf@linux-mips.org) + * + * Most of this file was borrowed from the linux kernel. + */ + +#pragma once + +#include +#include + +extern pthread_mutex_t cmpxchg_mutex; + +static inline long cmpxchg32(volatile long *m, long oldval, long newval) +{ + long retval; + __asm__ __volatile__( \ + " .set push \n" \ + " .set noat \n" \ + " .set mips3 \n" \ + "1: ll %0, %2 # __cmpxchg_asm \n" \ + " bne %0, %z3, 2f \n" \ + " .set mips0 \n" \ + " move $1, %z4 \n" \ + " .set mips3 \n" \ + " sc $1, %1 \n" \ + " beqz $1, 3f \n" \ + "2: \n" \ + " .subsection 2 \n" \ + "3: b 1b \n" \ + " .previous \n" \ + " .set pop \n" \ + : "=&r" (retval), "=R" (*m) \ + : "R" (*m), "Jr" (oldval), "Jr" (newval) \ + : "memory"); \ + + return retval; +} + + +static inline long long cmpxchg64(volatile long long *ptr, + long long oldval, long long newval) +{ + long long prev; + + pthread_mutex_lock(&cmpxchg_mutex); + prev = *(long long *)ptr; + if (prev == oldval) + *(long long *)ptr = newval; + pthread_mutex_unlock(&cmpxchg_mutex); + return prev; +} + + +static __inline__ long atomic_add(int i, volatile long* v) +{ + long temp; + + __asm__ __volatile__( + " .set mips3 \n" + "1: ll %0, %1 # atomic_add \n" + " addu %0, %2 \n" + " sc %0, %1 \n" + " beqz %0, 2f \n" + " .subsection 2 \n" + "2: b 1b \n" + " .previous \n" + " .set mips0 \n" + : "=&r" (temp), "=m" (*v) + : "Ir" (i), "m" (*v)); + + return temp; +} + +static __inline__ long atomic_sub(int i, volatile long* v) +{ + long temp; + + __asm__ __volatile__( + " .set mips3 \n" + "1: ll %0, %1 # atomic_sub \n" + " subu %0, %2 \n" + " sc %0, %1 \n" + " beqz %0, 2f \n" + " .subsection 2 \n" + "2: b 1b \n" + " .previous \n" + " .set mips0 \n" + : "=&r" (temp), "=m" (*v) + : "Ir" (i), "m" (*v)); + + return temp; +} diff --git a/KodiThreads/threads/SharedSection.h b/KodiThreads/threads/SharedSection.h new file mode 100644 index 0000000..ca4a218 --- /dev/null +++ b/KodiThreads/threads/SharedSection.h @@ -0,0 +1,71 @@ +#pragma once + +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/Condition.h" +#include "threads/SingleLock.h" +#include "threads/Helpers.h" + +/** + * A CSharedSection is a mutex that satisfies the Shared Lockable concept (see Lockables.h). + */ +class CSharedSection +{ + CCriticalSection sec; + XbmcThreads::ConditionVariable actualCv; + XbmcThreads::TightConditionVariable > cond; + + unsigned int sharedCount; + +public: + inline CSharedSection() : cond(actualCv,XbmcThreads::InversePredicate(sharedCount)), sharedCount(0) {} + + inline void lock() { CSingleLock l(sec); while (sharedCount) cond.wait(l); sec.lock(); } + inline bool try_lock() { return (sec.try_lock() ? ((sharedCount == 0) ? true : (sec.unlock(), false)) : false); } + inline void unlock() { sec.unlock(); } + + inline void lock_shared() { CSingleLock l(sec); sharedCount++; } + inline bool try_lock_shared() { return (sec.try_lock() ? sharedCount++, sec.unlock(), true : false); } + inline void unlock_shared() { CSingleLock l(sec); sharedCount--; if (!sharedCount) { cond.notifyAll(); } } +}; + +class CSharedLock : public XbmcThreads::SharedLock +{ +public: + inline CSharedLock(CSharedSection& cs) : XbmcThreads::SharedLock(cs) {} + inline CSharedLock(const CSharedSection& cs) : XbmcThreads::SharedLock((CSharedSection&)cs) {} + + inline bool IsOwner() const { return owns_lock(); } + inline void Enter() { lock(); } + inline void Leave() { unlock(); } +}; + +class CExclusiveLock : public XbmcThreads::UniqueLock +{ +public: + inline CExclusiveLock(CSharedSection& cs) : XbmcThreads::UniqueLock(cs) {} + inline CExclusiveLock(const CSharedSection& cs) : XbmcThreads::UniqueLock ((CSharedSection&)cs) {} + + inline bool IsOwner() const { return owns_lock(); } + inline void Leave() { unlock(); } + inline void Enter() { lock(); } +}; + diff --git a/KodiThreads/threads/SingleLock.h b/KodiThreads/threads/SingleLock.h new file mode 100644 index 0000000..a4e945e --- /dev/null +++ b/KodiThreads/threads/SingleLock.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2002 Frodo + * Portions Copyright (c) by the authors of ffmpeg and xvid + * Copyright (C) 2002-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ +// SingleLock.h: interface for the CSingleLock class. +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/Lockables.h" + +/** + * This implements a "guard" pattern for a CCriticalSection that + * borrows most of it's functionality from boost's unique_lock. + */ +class CSingleLock : public XbmcThreads::UniqueLock +{ +public: + inline CSingleLock(CCriticalSection& cs) : XbmcThreads::UniqueLock(cs) {} + inline CSingleLock(const CCriticalSection& cs) : XbmcThreads::UniqueLock ((CCriticalSection&)cs) {} + + inline void Leave() { unlock(); } + inline void Enter() { lock(); } +protected: + inline CSingleLock(CCriticalSection& cs, bool dicrim) : XbmcThreads::UniqueLock(cs,true) {} +}; + +/** + * This implements a "guard" pattern for a CCriticalSection that + * works like a CSingleLock but only "try"s the lock and so + * it's possible it doesn't actually get it.. + */ +class CSingleTryLock : public CSingleLock +{ +public: + inline CSingleTryLock(CCriticalSection& cs) : CSingleLock(cs,true) {} + + inline bool IsOwner() const { return owns_lock(); } +}; + +/** + * This implements a "guard" pattern for exiting all locks + * currently being held by the current thread and restoring + * those locks on destruction. + * + * This class can be used on a CCriticalSection that isn't owned + * by this thread in which case it will do nothing. + */ +class CSingleExit +{ + CCriticalSection& sec; + unsigned int count; +public: + inline CSingleExit(CCriticalSection& cs) : sec(cs), count(cs.exit()) { } + inline ~CSingleExit() { sec.restore(count); } +}; + diff --git a/KodiThreads/threads/SystemClock.cpp b/KodiThreads/threads/SystemClock.cpp new file mode 100644 index 0000000..e7580ba --- /dev/null +++ b/KodiThreads/threads/SystemClock.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#if defined(TARGET_DARWIN) +#include +#include +#elif defined(TARGET_WINDOWS) +#include +#else +#include +#endif +#include "SystemClock.h" + +namespace XbmcThreads +{ + unsigned int SystemClockMillis() + { + uint64_t now_time; + static uint64_t start_time = 0; + static bool start_time_set = false; +#if defined(TARGET_DARWIN) + now_time = CVGetCurrentHostTime() * 1000 / CVGetHostClockFrequency(); +#elif defined(TARGET_WINDOWS) + now_time = (uint64_t)timeGetTime(); +#else + struct timespec ts = {}; +#ifdef CLOCK_MONOTONIC_RAW + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif // CLOCK_MONOTONIC_RAW + now_time = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); +#endif + if (!start_time_set) + { + start_time = now_time; + start_time_set = true; + } + return (unsigned int)(now_time - start_time); + } + const unsigned int EndTime::InfiniteValue = std::numeric_limits::max(); +} diff --git a/KodiThreads/threads/SystemClock.h b/KodiThreads/threads/SystemClock.h new file mode 100644 index 0000000..cea72ba --- /dev/null +++ b/KodiThreads/threads/SystemClock.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include + +namespace XbmcThreads +{ + /** + * This function returns the system clock's number of milliseconds but with + * an arbitrary reference point. It handles the wrapping of any underlying + * system clock by setting a starting point at the first call. It should + * only be used for measuring time durations. + * + * Of course, on windows it just calls timeGetTime, so you're on your own. + */ + unsigned int SystemClockMillis(); + + /** + * DO NOT compare the results from SystemClockMillis() to an expected end time + * that was calculated by adding a number of milliseconds to some start time. + * The reason is because the SystemClockMillis could wrap. Instead use this + * class which uses differences (which are safe accross a wrap). + */ + class EndTime + { + unsigned int startTime; + unsigned int totalWaitTime; + public: + static const unsigned int InfiniteValue; + inline EndTime() : startTime(0), totalWaitTime(0) {} + inline EndTime(unsigned int millisecondsIntoTheFuture) : startTime(SystemClockMillis()), totalWaitTime(millisecondsIntoTheFuture) {} + + inline void Set(unsigned int millisecondsIntoTheFuture) { startTime = SystemClockMillis(); totalWaitTime = millisecondsIntoTheFuture; } + + inline bool IsTimePast() const { return totalWaitTime == InfiniteValue ? false : (totalWaitTime == 0 ? true : (SystemClockMillis() - startTime) >= totalWaitTime); } + + inline unsigned int MillisLeft() const + { + if (totalWaitTime == InfiniteValue) + return InfiniteValue; + if (totalWaitTime == 0) + return 0; + unsigned int timeWaitedAlready = (SystemClockMillis() - startTime); + return (timeWaitedAlready >= totalWaitTime) ? 0 : (totalWaitTime - timeWaitedAlready); + } + + inline void SetExpired() { totalWaitTime = 0; } + inline void SetInfinite() { totalWaitTime = InfiniteValue; } + inline bool IsInfinite(void) const { return (totalWaitTime == InfiniteValue); } + inline unsigned int GetInitialTimeoutValue(void) const { return totalWaitTime; } + inline unsigned int GetStartTime(void) const { return startTime; } + }; +} diff --git a/KodiThreads/threads/Thread.cpp b/KodiThreads/threads/Thread.cpp new file mode 100644 index 0000000..bc9c026 --- /dev/null +++ b/KodiThreads/threads/Thread.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2002 Frodo + * Portions Copyright (c) by the authors of ffmpeg and xvid + * Copyright (C) 2002-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/SystemClock.h" +#include "Thread.h" +#include "threads/ThreadLocal.h" +#include "threads/SingleLock.h" +#include "commons/Exception.h" +#include + +#define __STDC_FORMAT_MACROS +#include + +static XbmcThreads::ThreadLocal currentThread; + +#include "threads/platform/ThreadImpl.cpp" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CThread::CThread(const char* ThreadName) +: m_StopEvent(true,true), m_TermEvent(true), m_StartEvent(true) +{ + m_bStop = false; + + m_bAutoDelete = false; + m_ThreadId = 0; + m_iLastTime = 0; + m_iLastUsage = 0; + m_fLastUsage = 0.0f; + + m_pRunnable=NULL; + + if (ThreadName) + m_ThreadName = ThreadName; +} + +CThread::CThread(IRunnable* pRunnable, const char* ThreadName) +: m_StopEvent(true,true), m_TermEvent(true), m_StartEvent(true) +{ + m_bStop = false; + + m_bAutoDelete = false; + m_ThreadId = 0; + m_iLastTime = 0; + m_iLastUsage = 0; + m_fLastUsage = 0.0f; + + m_pRunnable=pRunnable; + + if (ThreadName) + m_ThreadName = ThreadName; +} + +CThread::~CThread() +{ + StopThread(); +} + +void CThread::Create(bool bAutoDelete, unsigned stacksize) +{ + if (m_ThreadId != 0) + { + LOG(LOGERROR, "%s - fatal error creating thread- old thread id not null", __FUNCTION__); + exit(1); + } + m_iLastTime = XbmcThreads::SystemClockMillis() * 10000; + m_iLastUsage = 0; + m_fLastUsage = 0.0f; + m_bAutoDelete = bAutoDelete; + m_bStop = false; + m_StopEvent.Reset(); + m_TermEvent.Reset(); + m_StartEvent.Reset(); + + SpawnThread(stacksize); +} + +bool CThread::IsRunning() const +{ + return m_ThreadId ? true : false; +} + +THREADFUNC CThread::staticThread(void* data) +{ + CThread* pThread = (CThread*)(data); + std::string name; + ThreadIdentifier id; + bool autodelete; + + if (!pThread) { + LOG(LOGERROR,"%s, sanity failed. thread is NULL.",__FUNCTION__); + return 1; + } + + name = pThread->m_ThreadName; + id = pThread->m_ThreadId; + autodelete = pThread->m_bAutoDelete; + + pThread->SetThreadInfo(); + + LOG(LOGDEBUG,"Thread %s start, auto delete: %s", name.c_str(), (autodelete ? "true" : "false")); + + currentThread.set(pThread); + pThread->m_StartEvent.Set(); + + pThread->Action(); + + // lock during termination + CSingleLock lock(pThread->m_CriticalSection); + + pThread->m_ThreadId = 0; + pThread->m_TermEvent.Set(); + pThread->TermHandler(); + + lock.Leave(); + + if (autodelete) + { + LOG(LOGDEBUG,"Thread %s %" PRIu64" terminating (autodelete)", name.c_str(), (uint64_t)id); + delete pThread; + pThread = NULL; + } + else + LOG(LOGDEBUG,"Thread %s %" PRIu64" terminating", name.c_str(), (uint64_t)id); + + return 0; +} + +bool CThread::IsAutoDelete() const +{ + return m_bAutoDelete; +} + +void CThread::StopThread(bool bWait /*= true*/) +{ + m_bStop = true; + m_StopEvent.Set(); + CSingleLock lock(m_CriticalSection); + if (m_ThreadId && bWait) + { + lock.Leave(); + WaitForThreadExit(0xFFFFFFFF); + } +} + +ThreadIdentifier CThread::ThreadId() const +{ + return m_ThreadId; +} + +void CThread::Process() +{ + if(m_pRunnable) + m_pRunnable->Run(); +} + +bool CThread::IsCurrentThread() const +{ + return IsCurrentThread(ThreadId()); +} + +CThread* CThread::GetCurrentThread() +{ + return currentThread.get(); +} + +void CThread::Sleep(unsigned int milliseconds) +{ + if(milliseconds > 10 && IsCurrentThread()) + m_StopEvent.WaitMSec(milliseconds); + else + XbmcThreads::ThreadSleep(milliseconds); +} + +void CThread::Action() +{ + try + { + OnStartup(); + } + catch (const XbmcCommons::UncheckedException &e) + { + e.LogThrowMessage("OnStartup"); + if (IsAutoDelete()) + return; + } + catch (...) + { + LOG(LOGERROR, "%s - thread %s, Unhandled exception caught in thread startup, aborting. auto delete: %d", __FUNCTION__, m_ThreadName.c_str(), IsAutoDelete()); + if (IsAutoDelete()) + return; + } + + try + { + Process(); + } + catch (const XbmcCommons::UncheckedException &e) + { + e.LogThrowMessage("Process"); + } + catch (...) + { + LOG(LOGERROR, "%s - thread %s, Unhandled exception caught in thread process, aborting. auto delete: %d", __FUNCTION__, m_ThreadName.c_str(), IsAutoDelete()); + } + + try + { + OnExit(); + } + catch (const XbmcCommons::UncheckedException &e) + { + e.LogThrowMessage("OnExit"); + } + catch (...) + { + LOG(LOGERROR, "%s - thread %s, Unhandled exception caught in thread OnExit, aborting. auto delete: %d", __FUNCTION__, m_ThreadName.c_str(), IsAutoDelete()); + } +} + diff --git a/KodiThreads/threads/Thread.h b/KodiThreads/threads/Thread.h new file mode 100644 index 0000000..c2124e8 --- /dev/null +++ b/KodiThreads/threads/Thread.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +// Thread.h: interface for the CThread class. +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include "Event.h" +#include "threads/ThreadImpl.h" +#include "threads/ThreadLocal.h" + +#ifdef TARGET_DARWIN +#include +#endif + +class IRunnable +{ +public: + virtual void Run()=0; + virtual ~IRunnable() {} +}; + +// minimum as mandated by XTL +#define THREAD_MINSTACKSIZE 0x10000 + +namespace XbmcThreads { class ThreadSettings; } + +class CThread +{ +protected: + CThread(const char* ThreadName); + +public: + CThread(IRunnable* pRunnable, const char* ThreadName); + virtual ~CThread(); + void Create(bool bAutoDelete = false, unsigned stacksize = 0); + void Sleep(unsigned int milliseconds); + int GetSchedRRPriority(void); + bool SetPrioritySched_RR(int iPriority); + bool IsAutoDelete() const; + virtual void StopThread(bool bWait = true); + bool IsRunning() const; + + // ----------------------------------------------------------------------------------- + // These are platform specific and can be found in ./platform/[platform]/ThreadImpl.cpp + // ----------------------------------------------------------------------------------- + bool IsCurrentThread() const; + int GetMinPriority(void); + int GetMaxPriority(void); + int GetNormalPriority(void); + int GetPriority(void); + bool SetPriority(const int iPriority); + bool WaitForThreadExit(unsigned int milliseconds); + float GetRelativeUsage(); // returns the relative cpu usage of this thread since last call + int64_t GetAbsoluteUsage(); + // ----------------------------------------------------------------------------------- + + static bool IsCurrentThread(const ThreadIdentifier tid); + static ThreadIdentifier GetCurrentThreadId(); + static CThread* GetCurrentThread(); + + virtual void OnException(){} // signal termination handler +protected: + virtual void OnStartup(){}; + virtual void OnExit(){}; + virtual void Process(); + + volatile bool m_bStop; + + enum WaitResponse { WAIT_INTERRUPTED = -1, WAIT_SIGNALED = 0, WAIT_TIMEDOUT = 1 }; + + /** + * This call will wait on a CEvent in an interruptible way such that if + * stop is called on the thread the wait will return with a response + * indicating what happened. + */ + inline WaitResponse AbortableWait(CEvent& event, int timeoutMillis = -1 /* indicates wait forever*/) + { + XbmcThreads::CEventGroup group(&event, &m_StopEvent, NULL); + CEvent* result = timeoutMillis < 0 ? group.wait() : group.wait(timeoutMillis); + return result == &event ? WAIT_SIGNALED : + (result == NULL ? WAIT_TIMEDOUT : WAIT_INTERRUPTED); + } + +private: + static THREADFUNC staticThread(void *data); + void Action(); + + // ----------------------------------------------------------------------------------- + // These are platform specific and can be found in ./platform/[platform]/ThreadImpl.cpp + // ----------------------------------------------------------------------------------- + ThreadIdentifier ThreadId() const; + void SetThreadInfo(); + void TermHandler(); + void SetSignalHandlers(); + void SpawnThread(unsigned stacksize); + // ----------------------------------------------------------------------------------- + + ThreadIdentifier m_ThreadId; + ThreadOpaque m_ThreadOpaque; + bool m_bAutoDelete; + CEvent m_StopEvent; + CEvent m_TermEvent; + CEvent m_StartEvent; + CCriticalSection m_CriticalSection; + IRunnable* m_pRunnable; + uint64_t m_iLastUsage; + uint64_t m_iLastTime; + float m_fLastUsage; + + std::string m_ThreadName; +}; diff --git a/KodiThreads/threads/ThreadImpl.h b/KodiThreads/threads/ThreadImpl.h new file mode 100644 index 0000000..2764e36 --- /dev/null +++ b/KodiThreads/threads/ThreadImpl.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/platform/ThreadImpl.h" + diff --git a/KodiThreads/threads/ThreadLocal.h b/KodiThreads/threads/ThreadLocal.h new file mode 100644 index 0000000..ca71be1 --- /dev/null +++ b/KodiThreads/threads/ThreadLocal.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/platform/ThreadLocal.h" + diff --git a/KodiThreads/threads/Timer.cpp b/KodiThreads/threads/Timer.cpp new file mode 100644 index 0000000..de4db9c --- /dev/null +++ b/KodiThreads/threads/Timer.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "Timer.h" +#include "SystemClock.h" + +CTimer::CTimer(ITimerCallback *callback) + : CThread("Timer"), + m_callback(callback), + m_timeout(0), + m_interval(false), + m_endTime(0) +{ } + +CTimer::~CTimer() +{ + Stop(true); +} + +bool CTimer::Start(uint32_t timeout, bool interval /* = false */) +{ + if (m_callback == NULL || timeout == 0 || IsRunning()) + return false; + + m_timeout = timeout; + m_interval = interval; + + Create(); + return true; +} + +bool CTimer::Stop(bool wait /* = false */) +{ + if (!IsRunning()) + return false; + + m_bStop = true; + m_eventTimeout.Set(); + StopThread(wait); + + return true; +} + +void CTimer::RestartAsync(uint32_t timeout) +{ + m_timeout = timeout; + m_endTime = XbmcThreads::SystemClockMillis() + timeout; + m_eventTimeout.Set(); +} + +bool CTimer::Restart() +{ + if (!IsRunning()) + return false; + + Stop(true); + return Start(m_timeout, m_interval); +} + +float CTimer::GetElapsedSeconds() const +{ + return GetElapsedMilliseconds() / 1000.0f; +} + +float CTimer::GetElapsedMilliseconds() const +{ + if (!IsRunning()) + return 0.0f; + + return (float)(XbmcThreads::SystemClockMillis() - (m_endTime - m_timeout)); +} + +void CTimer::Process() +{ + while (!m_bStop) + { + uint32_t currentTime = XbmcThreads::SystemClockMillis(); + m_endTime = currentTime + m_timeout; + + // wait the necessary time + if (!m_eventTimeout.WaitMSec(m_endTime - currentTime)) + { + currentTime = XbmcThreads::SystemClockMillis(); + if (m_endTime <= currentTime) + { + // execute OnTimeout() callback + m_callback->OnTimeout(); + + // continue if this is an interval timer, or if it was restarted during callback + if (!m_interval && m_endTime <= currentTime) + break; + } + } + } +} \ No newline at end of file diff --git a/KodiThreads/threads/Timer.h b/KodiThreads/threads/Timer.h new file mode 100644 index 0000000..8c90853 --- /dev/null +++ b/KodiThreads/threads/Timer.h @@ -0,0 +1,58 @@ +#pragma once +/* + * Copyright (C) 2012-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "Event.h" +#include "Thread.h" + +class ITimerCallback +{ +public: + virtual ~ITimerCallback() { } + + virtual void OnTimeout() = 0; +}; + +class CTimer : protected CThread +{ +public: + CTimer(ITimerCallback *callback); + virtual ~CTimer(); + + bool Start(uint32_t timeout, bool interval = false); + bool Stop(bool wait = false); + bool Restart(); + void RestartAsync(uint32_t timeout); + + bool IsRunning() const { return CThread::IsRunning(); } + + float GetElapsedSeconds() const; + float GetElapsedMilliseconds() const; + +protected: + virtual void Process(); + +private: + ITimerCallback *m_callback; + uint32_t m_timeout; + bool m_interval; + uint32_t m_endTime; + CEvent m_eventTimeout; +}; diff --git a/KodiThreads/threads/platform/Condition.h b/KodiThreads/threads/platform/Condition.h new file mode 100644 index 0000000..75e9b19 --- /dev/null +++ b/KodiThreads/threads/platform/Condition.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/Condition.h" +#elif (defined TARGET_WINDOWS) +#include "threads/platform/win/Condition.h" +#endif + diff --git a/KodiThreads/threads/platform/CriticalSection.h b/KodiThreads/threads/platform/CriticalSection.h new file mode 100644 index 0000000..ad4e6fb --- /dev/null +++ b/KodiThreads/threads/platform/CriticalSection.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/CriticalSection.h" +#elif (defined TARGET_WINDOWS) +#include "threads/platform/win/CriticalSection.h" +#endif diff --git a/KodiThreads/threads/platform/Implementation.cpp b/KodiThreads/threads/platform/Implementation.cpp new file mode 100644 index 0000000..7363c75 --- /dev/null +++ b/KodiThreads/threads/platform/Implementation.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/Implementation.cpp" +#endif diff --git a/KodiThreads/threads/platform/README.platform b/KodiThreads/threads/platform/README.platform new file mode 100644 index 0000000..a85042f --- /dev/null +++ b/KodiThreads/threads/platform/README.platform @@ -0,0 +1,52 @@ + +In order to port xbmc to an unsupported platform, the threading model +needs to replace the following classes: + +------------------------------------------------------------------------- +CCriticalSection +------------------------------------------------------------------------- + +CCriticalSection - must implement the "CountingLockable" concept: + void lock(); + bool try_lock(); + void unlock(); + unsigned int exit(); + void restore(unsigned int); + +The "CountingLockable" concept implies a RECURSIVE Lockable. + +There is a "CountingLockable" template which can be used to facilitate this +implementation from something that implements the "Lockable" concept: + void lock(); + bool try_lock(); + void unlock(); + +using: + class CCriticalSection : public CountingLockable {}; + + +------------------------------------------------------------------------- +ThreadLocal +------------------------------------------------------------------------- + +ThreadLocal - must be a template that implements the "ThreadLocal" concept: + void set(T* val); + T* get(); + +currently there is no need for a facility to define a cleanup function and if +destructors are automatically called by the underlying implementation this +behavior must be blocked (such is the case with boost). + +------------------------------------------------------------------------- +ConditionVariable +------------------------------------------------------------------------- + +ConditionVariable - must be implemented to handle a ConditionVariable concept: + + tempalte WaitResponse wait(L& lock); + tempalte WaitResponse wait(L& lock, int milliseconds); + void notifyAll(); + void notify(); + +where L is a typename template variable that must satisfy Lockable concept +defined above. diff --git a/KodiThreads/threads/platform/ThreadImpl.cpp b/KodiThreads/threads/platform/ThreadImpl.cpp new file mode 100644 index 0000000..cfd2e52 --- /dev/null +++ b/KodiThreads/threads/platform/ThreadImpl.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/ThreadImpl.cpp" +#if defined(TARGET_DARWIN_IOS) +#include "threads/platform/darwin/ThreadSchedImpl.cpp" +#else +#include "threads/platform/linux/ThreadSchedImpl.cpp" +#endif +#elif (defined TARGET_WINDOWS) +#include "threads/platform/win/ThreadImpl.cpp" +#endif + diff --git a/KodiThreads/threads/platform/ThreadImpl.h b/KodiThreads/threads/platform/ThreadImpl.h new file mode 100644 index 0000000..beb02e3 --- /dev/null +++ b/KodiThreads/threads/platform/ThreadImpl.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/ThreadImpl.h" +#elif (defined TARGET_WINDOWS) +#include "threads/platform/win/ThreadImpl.h" +#endif diff --git a/KodiThreads/threads/platform/ThreadLocal.h b/KodiThreads/threads/platform/ThreadLocal.h new file mode 100644 index 0000000..943c5bb --- /dev/null +++ b/KodiThreads/threads/platform/ThreadLocal.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#if (defined TARGET_POSIX) +#include "threads/platform/pthreads/ThreadLocal.h" +#elif (defined TARGET_WINDOWS) +#include "threads/platform/win/ThreadLocal.h" +#endif diff --git a/KodiThreads/threads/platform/darwin/ThreadSchedImpl.cpp b/KodiThreads/threads/platform/darwin/ThreadSchedImpl.cpp new file mode 100644 index 0000000..e2ccbfb --- /dev/null +++ b/KodiThreads/threads/platform/darwin/ThreadSchedImpl.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +int CThread::GetSchedRRPriority(void) +{ + return 96; +} + +bool CThread::SetPrioritySched_RR(int iPriority) +{ + // Changing to SCHED_RR is safe under OSX, you don't need elevated privileges and the + // OSX scheduler will monitor SCHED_RR threads and drop to SCHED_OTHER if it detects + // the thread running away. OSX automatically does this with the CoreAudio audio + // device handler thread. + int32_t result; + thread_extended_policy_data_t theFixedPolicy; + + // make thread fixed, set to 'true' for a non-fixed thread + theFixedPolicy.timeshare = false; + result = thread_policy_set(pthread_mach_thread_np(ThreadId()), THREAD_EXTENDED_POLICY, + (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT); + + int policy; + struct sched_param param; + result = pthread_getschedparam(ThreadId(), &policy, ¶m ); + // change from default SCHED_OTHER to SCHED_RR + policy = SCHED_RR; + result = pthread_setschedparam(ThreadId(), policy, ¶m ); + return result == 0; +} diff --git a/KodiThreads/threads/platform/linux/ThreadSchedImpl.cpp b/KodiThreads/threads/platform/linux/ThreadSchedImpl.cpp new file mode 100644 index 0000000..6a17dd7 --- /dev/null +++ b/KodiThreads/threads/platform/linux/ThreadSchedImpl.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +int CThread::GetSchedRRPriority(void) +{ + return GetNormalPriority(); +} + +bool CThread::SetPrioritySched_RR(int iPriority) +{ + return false; +} diff --git a/KodiThreads/threads/platform/pthreads/Condition.h b/KodiThreads/threads/platform/pthreads/Condition.h new file mode 100644 index 0000000..3587f02 --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/Condition.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/SingleLock.h" +#include "threads/Helpers.h" + +#include + +#ifdef TARGET_DARWIN + #include //for gettimeofday +#else + #include //for clock_gettime +#endif + +namespace XbmcThreads +{ + + /** + * This is a thin wrapper around boost::condition_variable. It is subject + * to "spurious returns" as it is built on boost which is built on posix + * on many of our platforms. + */ + class ConditionVariable : public NonCopyable + { + private: + pthread_cond_t cond; + + public: + inline ConditionVariable() + { + pthread_cond_init(&cond,NULL); + } + + inline ~ConditionVariable() + { + pthread_cond_destroy(&cond); + } + + inline void wait(CCriticalSection& lock) + { + int count = lock.count; + lock.count = 0; + pthread_cond_wait(&cond,&lock.get_underlying().mutex); + lock.count = count; + } + + inline bool wait(CCriticalSection& lock, unsigned long milliseconds) + { + struct timespec ts; + +#ifdef TARGET_DARWIN + struct timeval tv; + gettimeofday(&tv, NULL); + ts.tv_nsec = tv.tv_usec * 1000; + ts.tv_sec = tv.tv_sec; +#else + clock_gettime(CLOCK_REALTIME, &ts); +#endif + + ts.tv_nsec += milliseconds % 1000 * 1000000; + ts.tv_sec += milliseconds / 1000 + ts.tv_nsec / 1000000000; + ts.tv_nsec %= 1000000000; + int count = lock.count; + lock.count = 0; + int res = pthread_cond_timedwait(&cond,&lock.get_underlying().mutex,&ts); + lock.count = count; + return res == 0; + } + + inline void wait(CSingleLock& lock) { wait(lock.get_underlying()); } + inline bool wait(CSingleLock& lock, unsigned long milliseconds) { return wait(lock.get_underlying(), milliseconds); } + + inline void notifyAll() + { + pthread_cond_broadcast(&cond); + } + + inline void notify() + { + pthread_cond_signal(&cond); + } + }; + +} diff --git a/KodiThreads/threads/platform/pthreads/CriticalSection.h b/KodiThreads/threads/platform/pthreads/CriticalSection.h new file mode 100644 index 0000000..8de96a1 --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/CriticalSection.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include +#include + +#include "threads/Lockables.h" +#include "threads/Helpers.h" + +namespace XbmcThreads +{ + + // forward declare in preparation for the friend declaration + class ConditionVariable; + + namespace pthreads + { + class RecursiveMutex + { + pthread_mutexattr_t* getRecursiveAttr(); + pthread_mutex_t mutex; + + // needs acces to 'mutex' + friend class XbmcThreads::ConditionVariable; + public: + inline RecursiveMutex() { pthread_mutex_init(&mutex,getRecursiveAttr()); } + + inline ~RecursiveMutex() { pthread_mutex_destroy(&mutex); } + + inline void lock() { pthread_mutex_lock(&mutex); } + + inline void unlock() { pthread_mutex_unlock(&mutex); } + + inline bool try_lock() { return (pthread_mutex_trylock(&mutex) == 0); } + }; + } +} + + +/** + * A CCriticalSection is a CountingLockable whose implementation is a + * native recursive mutex. + * + * This is not a typedef because of a number of "class CCriticalSection;" + * forward declarations in the code that break when it's done that way. + */ +class CCriticalSection : public XbmcThreads::CountingLockable {}; + diff --git a/KodiThreads/threads/platform/pthreads/Implementation.cpp b/KodiThreads/threads/platform/pthreads/Implementation.cpp new file mode 100644 index 0000000..76dd4ec --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/Implementation.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/Lockables.h" +#include "threads/platform/pthreads/CriticalSection.h" +#include "threads/Helpers.h" + +#include + +namespace XbmcThreads +{ + namespace pthreads + { + // ========================================================== + static pthread_mutexattr_t recursiveAttr; + + static bool setRecursiveAttr() + { + static bool alreadyCalled = false; // initialized to 0 in the data segment prior to startup init code running + if (!alreadyCalled) + { + pthread_mutexattr_init(&recursiveAttr); + pthread_mutexattr_settype(&recursiveAttr,PTHREAD_MUTEX_RECURSIVE); +#if !defined(__arm__) && !defined(TARGET_ANDROID) + pthread_mutexattr_setprotocol(&recursiveAttr,PTHREAD_PRIO_INHERIT); +#endif + alreadyCalled = true; + } + return true; // note, we never call destroy. + } + + static bool recursiveAttrSet = setRecursiveAttr(); + + pthread_mutexattr_t* RecursiveMutex::getRecursiveAttr() + { + if (!recursiveAttrSet) // this is only possible in the single threaded startup code + recursiveAttrSet = setRecursiveAttr(); + return &recursiveAttr; + } + // ========================================================== + } +} + diff --git a/KodiThreads/threads/platform/pthreads/ThreadImpl.cpp b/KodiThreads/threads/platform/pthreads/ThreadImpl.cpp new file mode 100644 index 0000000..fe9ee9a --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/ThreadImpl.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#if defined(HAVE_CONFIG_H) + #include "config.h" +#endif +#include +#if defined(TARGET_ANDROID) +#include +#else +#include +#endif +#include +#include +#ifdef TARGET_FREEBSD +#include +#if __FreeBSD_version < 900031 +#include +#else +#include +#endif +#endif + +#include + +void CThread::SpawnThread(unsigned stacksize) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); +#if !defined(TARGET_ANDROID) // http://code.google.com/p/android/issues/detail?id=7808 + if (stacksize > PTHREAD_STACK_MIN) + pthread_attr_setstacksize(&attr, stacksize); +#endif + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (pthread_create(&m_ThreadId, &attr, (void*(*)(void*))staticThread, this) != 0) + { + LOG(LOGNOTICE, "%s - fatal error creating thread",__FUNCTION__); + } + pthread_attr_destroy(&attr); +} + +void CThread::TermHandler() { } + +void CThread::SetThreadInfo() +{ +#ifdef TARGET_FREEBSD +#if __FreeBSD_version < 900031 + long lwpid; + thr_self(&lwpid); + m_ThreadOpaque.LwpId = lwpid; +#else + m_ThreadOpaque.LwpId = pthread_getthreadid_np(); +#endif +#elif defined(TARGET_ANDROID) + m_ThreadOpaque.LwpId = gettid(); +#else + m_ThreadOpaque.LwpId = syscall(SYS_gettid); +#endif + +#if defined(HAVE_PTHREAD_SETNAME_NP) +#ifdef TARGET_DARWIN +#if(__MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 30200) + pthread_setname_np(m_ThreadName.c_str()); +#endif +#else + pthread_setname_np(m_ThreadId, m_ThreadName.c_str()); +#endif +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(m_ThreadId, m_ThreadName.c_str()); +#endif + +#ifdef RLIMIT_NICE + // get user max prio + struct rlimit limit; + int userMaxPrio; + if (getrlimit(RLIMIT_NICE, &limit) == 0) + { + userMaxPrio = limit.rlim_cur - 20; + if (userMaxPrio < 0) + userMaxPrio = 0; + } + else + userMaxPrio = 0; + + if (geteuid() == 0) + userMaxPrio = GetMaxPriority(); + + // if the user does not have an entry in limits.conf the following + // call will fail + if (userMaxPrio > 0) + { + // start thread with nice level of appication + int appNice = getpriority(PRIO_PROCESS, getpid()); + if (setpriority(PRIO_PROCESS, m_ThreadOpaque.LwpId, appNice) != 0) + LOG(LOGERROR, "%s: error %s", __FUNCTION__, strerror(errno)); + } +#endif +} + +ThreadIdentifier CThread::GetCurrentThreadId() +{ + return pthread_self(); +} + +bool CThread::IsCurrentThread(const ThreadIdentifier tid) +{ + return pthread_equal(pthread_self(), tid); +} + +int CThread::GetMinPriority(void) +{ + // one level lower than application + return -1; +} + +int CThread::GetMaxPriority(void) +{ + // one level higher than application + return 1; +} + +int CThread::GetNormalPriority(void) +{ + // same level as application + return 0; +} + +bool CThread::SetPriority(const int iPriority) +{ + bool bReturn = false; + + // wait until thread is running, it needs to get its lwp id + m_StartEvent.Wait(); + + CSingleLock lock(m_CriticalSection); + + // get min prio for SCHED_RR + int minRR = GetMaxPriority() + 1; + + if (!m_ThreadId) + bReturn = false; + else if (iPriority >= minRR) + bReturn = SetPrioritySched_RR(iPriority); +#ifdef RLIMIT_NICE + else + { + // get user max prio + struct rlimit limit; + int userMaxPrio; + if (getrlimit(RLIMIT_NICE, &limit) == 0) + { + userMaxPrio = limit.rlim_cur - 20; + // is a user has no entry in limits.conf rlim_cur is zero + if (userMaxPrio < 0) + userMaxPrio = 0; + } + else + userMaxPrio = 0; + + if (geteuid() == 0) + userMaxPrio = GetMaxPriority(); + + // keep priority in bounds + int prio = iPriority; + if (prio >= GetMaxPriority()) + prio = std::min(GetMaxPriority(), userMaxPrio); + if (prio < GetMinPriority()) + prio = GetMinPriority(); + + // nice level of application + int appNice = getpriority(PRIO_PROCESS, getpid()); + if (prio) + prio = prio > 0 ? appNice-1 : appNice+1; + + if (setpriority(PRIO_PROCESS, m_ThreadOpaque.LwpId, prio) == 0) + bReturn = true; + else + LOG(LOGERROR, "%s: error %s", __FUNCTION__, strerror(errno)); + } +#endif + + return bReturn; +} + +int CThread::GetPriority() +{ + int iReturn; + + // lwp id is valid after start signel has fired + m_StartEvent.Wait(); + + CSingleLock lock(m_CriticalSection); + + int appNice = getpriority(PRIO_PROCESS, getpid()); + int prio = getpriority(PRIO_PROCESS, m_ThreadOpaque.LwpId); + iReturn = appNice - prio; + + return iReturn; +} + +bool CThread::WaitForThreadExit(unsigned int milliseconds) +{ + bool bReturn = m_TermEvent.WaitMSec(milliseconds); + + return bReturn; +} + +int64_t CThread::GetAbsoluteUsage() +{ + CSingleLock lock(m_CriticalSection); + + if (!m_ThreadId) + return 0; + + int64_t time = 0; +#ifdef TARGET_DARWIN + thread_basic_info threadInfo; + mach_msg_type_number_t threadInfoCount = THREAD_BASIC_INFO_COUNT; + + kern_return_t ret = thread_info(pthread_mach_thread_np(m_ThreadId), + THREAD_BASIC_INFO, (thread_info_t)&threadInfo, &threadInfoCount); + + if (ret == KERN_SUCCESS) + { + // User time. + time = ((int64_t)threadInfo.user_time.seconds * 10000000L) + threadInfo.user_time.microseconds*10L; + + // System time. + time += (((int64_t)threadInfo.system_time.seconds * 10000000L) + threadInfo.system_time.microseconds*10L); + } + +#else + clockid_t clock; + if (pthread_getcpuclockid(m_ThreadId, &clock) == 0) + { + struct timespec tp; + clock_gettime(clock, &tp); + time = (int64_t)tp.tv_sec * 10000000 + tp.tv_nsec/100; + } +#endif + + return time; +} + +float CThread::GetRelativeUsage() +{ + unsigned int iTime = XbmcThreads::SystemClockMillis(); + iTime *= 10000; // convert into 100ns tics + + // only update every 1 second + if( iTime < m_iLastTime + 1000*10000 ) return m_fLastUsage; + + int64_t iUsage = GetAbsoluteUsage(); + + if (m_iLastUsage > 0 && m_iLastTime > 0) + m_fLastUsage = (float)( iUsage - m_iLastUsage ) / (float)( iTime - m_iLastTime ); + + m_iLastUsage = iUsage; + m_iLastTime = iTime; + + return m_fLastUsage; +} + +void term_handler (int signum) +{ + LOG(LOGERROR,"thread 0x%lx (%lu) got signal %d. calling OnException and terminating thread abnormally.", (long unsigned int)pthread_self(), (long unsigned int)pthread_self(), signum); + CThread* curThread = CThread::GetCurrentThread(); + if (curThread) + { + curThread->StopThread(false); + curThread->OnException(); + if( curThread->IsAutoDelete() ) + delete curThread; + } + pthread_exit(NULL); +} + +void CThread::SetSignalHandlers() +{ + struct sigaction action; + action.sa_handler = term_handler; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + //sigaction (SIGABRT, &action, NULL); + //sigaction (SIGSEGV, &action, NULL); +} + diff --git a/KodiThreads/threads/platform/pthreads/ThreadImpl.h b/KodiThreads/threads/platform/pthreads/ThreadImpl.h new file mode 100644 index 0000000..7bab9da --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/ThreadImpl.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include +#include + +struct threadOpaque +{ + pid_t LwpId; +}; + +typedef pthread_t ThreadIdentifier; +typedef threadOpaque ThreadOpaque; +typedef int THREADFUNC; + +namespace XbmcThreads +{ + inline static void ThreadSleep(unsigned int millis) { usleep(millis*1000); } +} + diff --git a/KodiThreads/threads/platform/pthreads/ThreadLocal.h b/KodiThreads/threads/platform/pthreads/ThreadLocal.h new file mode 100644 index 0000000..1a7ef5e --- /dev/null +++ b/KodiThreads/threads/platform/pthreads/ThreadLocal.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include + +namespace XbmcThreads +{ + /** + * A thin wrapper around pthreads thread specific storage + * functionality. + */ + template class ThreadLocal + { + pthread_key_t key; + public: + inline ThreadLocal() : key(0) { pthread_key_create(&key,NULL); } + + inline ~ThreadLocal() { pthread_key_delete(key); } + + inline void set(T* val) { pthread_setspecific(key,(void*)val); } + + inline T* get() { return (T*)pthread_getspecific(key); } + }; +} + diff --git a/KodiThreads/threads/platform/win/CMakeLists.txt b/KodiThreads/threads/platform/win/CMakeLists.txt new file mode 100644 index 0000000..d66cd13 --- /dev/null +++ b/KodiThreads/threads/platform/win/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES Win32Exception.cpp) + +set(HEADERS Condition.h + CriticalSection.h + ThreadImpl.h + ThreadLocal.h + Win32Exception.h) + +core_add_library(threads_win) diff --git a/KodiThreads/threads/platform/win/Condition.h b/KodiThreads/threads/platform/win/Condition.h new file mode 100644 index 0000000..1900c1d --- /dev/null +++ b/KodiThreads/threads/platform/win/Condition.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/SingleLock.h" +#include "threads/Helpers.h" + +#include + +namespace XbmcThreads +{ + /** + * This is condition variable implementation that uses the underlying + * Windows mechanisms and assumes Vista (or later) + */ + class ConditionVariable : public NonCopyable + { + CONDITION_VARIABLE cond; + + // SleepConditionVarialbeCS requires the condition variable be entered + // only once. + struct AlmostExit + { + unsigned int count; + CCriticalSection& cc; + inline AlmostExit(CCriticalSection& pcc) : count(pcc.exit(1)), cc(pcc) { cc.count = 0; } + inline ~AlmostExit() { cc.count = 1; cc.restore(count); } + }; + + public: + inline ConditionVariable() { InitializeConditionVariable(&cond); } + + // apparently, windows condition variables do not need to be deleted + inline ~ConditionVariable() { } + + inline void wait(CCriticalSection& lock) + { + AlmostExit ae(lock); + // even the windows implementation is capable of spontaneous wakes + SleepConditionVariableCS(&cond,&lock.get_underlying().mutex,INFINITE); + } + + inline bool wait(CCriticalSection& lock, unsigned long milliseconds) + { + AlmostExit ae(lock); + return SleepConditionVariableCS(&cond,&lock.get_underlying().mutex,milliseconds) ? true : false; + } + + inline void wait(CSingleLock& lock) { wait(lock.get_underlying()); } + inline bool wait(CSingleLock& lock, unsigned long milliseconds) { return wait(lock.get_underlying(), milliseconds); } + inline void notifyAll() { WakeAllConditionVariable(&cond); } + inline void notify() { WakeConditionVariable(&cond); } + }; +} diff --git a/KodiThreads/threads/platform/win/CriticalSection.h b/KodiThreads/threads/platform/win/CriticalSection.h new file mode 100644 index 0000000..464a3a1 --- /dev/null +++ b/KodiThreads/threads/platform/win/CriticalSection.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include "threads/Lockables.h" + +#include + +namespace XbmcThreads +{ + + // forward declare in preparation for the friend declaration + class ConditionVariable; + + namespace windows + { + class RecursiveMutex + { + CRITICAL_SECTION mutex; + + // needs acces to 'mutex' + friend class XbmcThreads::ConditionVariable; + public: + inline RecursiveMutex() + { + InitializeCriticalSection(&mutex); + } + + inline ~RecursiveMutex() + { + DeleteCriticalSection(&mutex); + } + + inline void lock() + { + EnterCriticalSection(&mutex); + } + + inline void unlock() + { + LeaveCriticalSection(&mutex); + } + + inline bool try_lock() + { + return TryEnterCriticalSection(&mutex) ? true : false; + } + }; + } +} + + +/** + * A CCriticalSection is a CountingLockable whose implementation is a + * native recursive mutex. + * + * This is not a typedef because of a number of "class CCriticalSection;" + * forward declarations in the code that break when it's done that way. + */ +class CCriticalSection : public XbmcThreads::CountingLockable {}; + diff --git a/KodiThreads/threads/platform/win/ThreadImpl.cpp b/KodiThreads/threads/platform/win/ThreadImpl.cpp new file mode 100644 index 0000000..2d0d724 --- /dev/null +++ b/KodiThreads/threads/platform/win/ThreadImpl.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include +#include +#include "threads/platform/win/Win32Exception.h" +#include "system.h" +//#include "../../win32/WIN32Util.h" + +void CThread::SpawnThread(unsigned stacksize) +{ + // Create in the suspended state, so that no matter the thread priorities and scheduled order, the handle will be assigned + // before the new thread exits. + unsigned threadId; + m_ThreadOpaque.handle = (HANDLE)_beginthreadex(NULL, stacksize, &staticThread, this, CREATE_SUSPENDED, &threadId); + if (m_ThreadOpaque.handle == NULL) + { + LOG(LOGERROR, "%s - fatal error %d creating thread", __FUNCTION__, GetLastError()); + return; + } + m_ThreadId = threadId; + + if (ResumeThread(m_ThreadOpaque.handle) == -1) + LOG(LOGERROR, "%s - fatal error %d resuming thread", __FUNCTION__, GetLastError()); + +} + +void CThread::TermHandler() +{ + CloseHandle(m_ThreadOpaque.handle); + m_ThreadOpaque.handle = NULL; +} + +void CThread::SetThreadInfo() +{ + const unsigned int MS_VC_EXCEPTION = 0x406d1388; + +#pragma pack(push,8) + struct THREADNAME_INFO + { + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in same addr space) + DWORD dwThreadID; // thread ID (-1 caller thread) + DWORD dwFlags; // reserved for future use, most be zero + } info; +#pragma pack(pop) + + info.dwType = 0x1000; + info.szName = m_ThreadName.c_str(); + info.dwThreadID = m_ThreadId; + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + + //CWIN32Util::SetThreadLocalLocale(true); // avoid crashing with setlocale(), see https://connect.microsoft.com/VisualStudio/feedback/details/794122 + + win32_exception::install_handler(); +} + +ThreadIdentifier CThread::GetCurrentThreadId() +{ + return ::GetCurrentThreadId(); +} + +bool CThread::IsCurrentThread(const ThreadIdentifier tid) +{ + return (::GetCurrentThreadId() == tid); +} + +int CThread::GetMinPriority(void) +{ + return(THREAD_PRIORITY_IDLE); +} + +int CThread::GetMaxPriority(void) +{ + return(THREAD_PRIORITY_HIGHEST); +} + +int CThread::GetNormalPriority(void) +{ + return(THREAD_PRIORITY_NORMAL); +} + +int CThread::GetSchedRRPriority(void) +{ + return GetNormalPriority(); +} + +bool CThread::SetPriority(const int iPriority) +{ + bool bReturn = false; + + CSingleLock lock(m_CriticalSection); + if (m_ThreadOpaque.handle) + { + bReturn = SetThreadPriority(m_ThreadOpaque.handle, iPriority) == TRUE; + } + + return bReturn; +} + +int CThread::GetPriority() +{ + CSingleLock lock(m_CriticalSection); + + int iReturn = THREAD_PRIORITY_NORMAL; + if (m_ThreadOpaque.handle) + { + iReturn = GetThreadPriority(m_ThreadOpaque.handle); + } + return iReturn; +} + +bool CThread::WaitForThreadExit(unsigned int milliseconds) +{ + bool bReturn = true; + + CSingleLock lock(m_CriticalSection); + if (m_ThreadId && m_ThreadOpaque.handle != NULL) + { + // boost priority of thread we are waiting on to same as caller + int callee = GetThreadPriority(m_ThreadOpaque.handle); + int caller = GetThreadPriority(::GetCurrentThread()); + if(caller != THREAD_PRIORITY_ERROR_RETURN && caller > callee) + SetThreadPriority(m_ThreadOpaque.handle, caller); + + lock.Leave(); + bReturn = m_TermEvent.WaitMSec(milliseconds); + lock.Enter(); + + // restore thread priority if thread hasn't exited + if(callee != THREAD_PRIORITY_ERROR_RETURN && caller > callee && m_ThreadOpaque.handle) + SetThreadPriority(m_ThreadOpaque.handle, callee); + } + return bReturn; +} + +int64_t CThread::GetAbsoluteUsage() +{ + CSingleLock lock(m_CriticalSection); + + if (!m_ThreadOpaque.handle) + return 0; + + uint64_t time = 0; + FILETIME CreationTime, ExitTime, UserTime, KernelTime; + if( GetThreadTimes(m_ThreadOpaque.handle, &CreationTime, &ExitTime, &KernelTime, &UserTime ) ) + { + time = (((uint64_t)UserTime.dwHighDateTime) << 32) + ((uint64_t)UserTime.dwLowDateTime); + time += (((uint64_t)KernelTime.dwHighDateTime) << 32) + ((uint64_t)KernelTime.dwLowDateTime); + } + return time; +} + +float CThread::GetRelativeUsage() +{ + unsigned int iTime = XbmcThreads::SystemClockMillis(); + iTime *= 10000; // convert into 100ns tics + + // only update every 1 second + if( iTime < m_iLastTime + 1000*10000 ) return m_fLastUsage; + + int64_t iUsage = GetAbsoluteUsage(); + + if (m_iLastUsage > 0 && m_iLastTime > 0) + m_fLastUsage = (float)( iUsage - m_iLastUsage ) / (float)( iTime - m_iLastTime ); + + m_iLastUsage = iUsage; + m_iLastTime = iTime; + + return m_fLastUsage; +} + +void CThread::SetSignalHandlers() +{ + // install win32 exception translator + win32_exception::install_handler(); +} diff --git a/KodiThreads/threads/platform/win/ThreadImpl.h b/KodiThreads/threads/platform/win/ThreadImpl.h new file mode 100644 index 0000000..b670df3 --- /dev/null +++ b/KodiThreads/threads/platform/win/ThreadImpl.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include + + +struct threadOpaque +{ + HANDLE handle; +}; + +typedef DWORD ThreadIdentifier; +typedef threadOpaque ThreadOpaque; +#define THREADFUNC unsigned __stdcall + +namespace XbmcThreads +{ + inline static void ThreadSleep(unsigned int millis) { Sleep(millis); } +} + diff --git a/KodiThreads/threads/platform/win/ThreadLocal.h b/KodiThreads/threads/platform/win/ThreadLocal.h new file mode 100644 index 0000000..210fba9 --- /dev/null +++ b/KodiThreads/threads/platform/win/ThreadLocal.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#pragma once + +#include +#include "commons/Exception.h" + +namespace XbmcThreads +{ + /** + * A thin wrapper around windows thread specific storage + * functionality. + */ + template class ThreadLocal + { + DWORD key; + public: + inline ThreadLocal() + { + if ((key = TlsAlloc()) == TLS_OUT_OF_INDEXES) + throw XbmcCommons::UncheckedException("Ran out of Windows TLS Indexes. Windows Error Code %d",(int)GetLastError()); + } + + inline ~ThreadLocal() + { + if (!TlsFree(key)) + throw XbmcCommons::UncheckedException("Failed to free Tls %d, Windows Error Code %d",(int)key, (int)GetLastError()); + } + + inline void set(T* val) + { + if (!TlsSetValue(key,(LPVOID)val)) + throw XbmcCommons::UncheckedException("Failed to set Tls %d, Windows Error Code %d",(int)key, (int)GetLastError()); + } + + inline T* get() { return (T*)TlsGetValue(key); } + }; +} + diff --git a/KodiThreads/threads/platform/win/Win32Exception.cpp b/KodiThreads/threads/platform/win/Win32Exception.cpp new file mode 100644 index 0000000..c6d9b4e --- /dev/null +++ b/KodiThreads/threads/platform/win/Win32Exception.cpp @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "Win32Exception.h" +#include +#include +//#include "Util.h" +//#include "WIN32Util.h" +//#include "utils/StringUtils.h" +//#include "utils/CharsetConverter.h" +//#include "utils/URIUtils.h" +#include "system.h" + +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + +// StackWalk64() +typedef BOOL (__stdcall *tSW)( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); + +// SymInitialize() +typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); +// SymCleanup() +typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess ); +// SymGetSymFromAddr64() +typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol ); +// UnDecorateSymbolName() +typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags ); +// SymGetLineFromAddr64() +typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line ); +// SymGetModuleBase64() +typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr ); +// SymFunctionTableAccess64() +typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase ); +// SymGetOptions() +typedef DWORD (__stdcall *tSGO)( VOID ); +// SymSetOptions() +typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions ); + +std::string win32_exception::mVersion; + +void win32_exception::install_handler() +{ + _set_se_translator(win32_exception::translate); +} + +void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info) +{ + switch (code) + { + case EXCEPTION_ACCESS_VIOLATION: + //throw access_violation(info); // TODO: implement this + break; + default: + throw win32_exception(info); + } +} + +win32_exception::win32_exception(EXCEPTION_POINTERS* info, const char* classname) : + XbmcCommons::UncheckedException(classname ? classname : "win32_exception"), + mWhat("Win32 exception"), mWhere(info->ExceptionRecord->ExceptionAddress), mCode(info->ExceptionRecord->ExceptionCode), mExceptionPointers(info) +{ + // Windows guarantees that *(info->ExceptionRecord) is valid + switch (info->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION: + mWhat = "Access violation"; + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + mWhat = "Division by zero"; + break; + } +} + +void win32_exception::LogThrowMessage(const char *prefix) const +{ + if( prefix ) + LOG(LOGERROR, "Unhandled exception in %s : %s (code:0x%08x) at 0x%08x", prefix, (unsigned int) what(), code(), where()); + else + LOG(LOGERROR, "Unhandled exception in %s (code:0x%08x) at 0x%08x", what(), code(), where()); + + write_stacktrace(); + write_minidump(); +} + +bool win32_exception::write_minidump(EXCEPTION_POINTERS* pEp) +{ + // Create the dump file where the xbmc.exe resides + bool returncode = false; + std::string dumpFileName; + std::wstring dumpFileNameW; + SYSTEMTIME stLocalTime; + GetLocalTime(&stLocalTime); + + dumpFileName = StringUtils::Format("kodi_crashlog-%s-%04d%02d%02d-%02d%02d%02d.dmp", + mVersion.c_str(), + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond); + + // TODO: implement this + //dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(), CUtil::MakeLegalFileName(dumpFileName))); + + //g_charsetConverter.utf8ToW(dumpFileName, dumpFileNameW, false); + HANDLE hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + + if (hDumpFile == INVALID_HANDLE_VALUE) + { + LOG(LOGERROR, "CreateFile '%s' failed with error id %d", dumpFileName.c_str(), GetLastError()); + goto cleanup; + } + + // Load the DBGHELP DLL + HMODULE hDbgHelpDll = ::LoadLibrary("DBGHELP.DLL"); + if (!hDbgHelpDll) + { + LOG(LOGERROR, "LoadLibrary 'DBGHELP.DLL' failed with error id %d", GetLastError()); + goto cleanup; + } + + MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDbgHelpDll, "MiniDumpWriteDump"); + if (!pDump) + { + LOG(LOGERROR, "Failed to locate MiniDumpWriteDump with error id %d", GetLastError()); + goto cleanup; + } + + // Initialize minidump structure + MINIDUMP_EXCEPTION_INFORMATION mdei; + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = pEp; + mdei.ClientPointers = FALSE; + + // Call the minidump api with normal dumping + // We can get more detail information by using other minidump types but the dump file will be + // extremely large. + BOOL bMiniDumpSuccessful = pDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &mdei, 0, NULL); + if( !bMiniDumpSuccessful ) + { + LOG(LOGERROR, "MiniDumpWriteDump failed with error id %d", GetLastError()); + goto cleanup; + } + + returncode = true; + +cleanup: + + if (hDumpFile != INVALID_HANDLE_VALUE) + CloseHandle(hDumpFile); + + if (hDbgHelpDll) + FreeLibrary(hDbgHelpDll); + + return returncode; +} + +/* \brief Writes a simple stack trace to the XBMC user directory. + It needs a valid .pdb file to show the method, filename and + line number where the exception took place. +*/ +bool win32_exception::write_stacktrace(EXCEPTION_POINTERS* pEp) +{ + #define STACKWALK_MAX_NAMELEN 1024 + + std::string dumpFileName, strOutput; + std::wstring dumpFileNameW; + CHAR cTemp[STACKWALK_MAX_NAMELEN]; + DWORD dwBytes; + SYSTEMTIME stLocalTime; + GetLocalTime(&stLocalTime); + bool returncode = false; + STACKFRAME64 frame = { 0 }; + HANDLE hCurProc = GetCurrentProcess(); + IMAGEHLP_SYMBOL64* pSym = NULL; + HANDLE hDumpFile = INVALID_HANDLE_VALUE; + tSC pSC = NULL; + + HMODULE hDbgHelpDll = ::LoadLibrary("DBGHELP.DLL"); + if (!hDbgHelpDll) + { + LOG(LOGERROR, "LoadLibrary 'DBGHELP.DLL' failed with error id %d", GetLastError()); + goto cleanup; + } + + tSI pSI = (tSI) GetProcAddress(hDbgHelpDll, "SymInitialize" ); + tSGO pSGO = (tSGO) GetProcAddress(hDbgHelpDll, "SymGetOptions" ); + tSSO pSSO = (tSSO) GetProcAddress(hDbgHelpDll, "SymSetOptions" ); + pSC = (tSC) GetProcAddress(hDbgHelpDll, "SymCleanup" ); + tSW pSW = (tSW) GetProcAddress(hDbgHelpDll, "StackWalk64" ); + tSGSFA pSGSFA = (tSGSFA) GetProcAddress(hDbgHelpDll, "SymGetSymFromAddr64" ); + tUDSN pUDSN = (tUDSN) GetProcAddress(hDbgHelpDll, "UnDecorateSymbolName" ); + tSGLFA pSGLFA = (tSGLFA) GetProcAddress(hDbgHelpDll, "SymGetLineFromAddr64" ); + tSFTA pSFTA = (tSFTA) GetProcAddress(hDbgHelpDll, "SymFunctionTableAccess64" ); + tSGMB pSGMB = (tSGMB) GetProcAddress(hDbgHelpDll, "SymGetModuleBase64" ); + + if(pSI == NULL || pSGO == NULL || pSSO == NULL || pSC == NULL || pSW == NULL || pSGSFA == NULL || pUDSN == NULL || pSGLFA == NULL || + pSFTA == NULL || pSGMB == NULL) + goto cleanup; + + dumpFileName = StringUtils::Format("kodi_stacktrace-%s-%04d%02d%02d-%02d%02d%02d.txt", + mVersion.c_str(), + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond); + + //dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(), CUtil::MakeLegalFileName(dumpFileName))); // TODO: implement this + + //g_charsetConverter.utf8ToW(dumpFileName, dumpFileNameW, false); // TODO: implement this + hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + + if (hDumpFile == INVALID_HANDLE_VALUE) + { + LOG(LOGERROR, "CreateFile '%s' failed with error id %d", dumpFileName.c_str(), GetLastError()); + goto cleanup; + } + + frame.AddrPC.Offset = pEp->ContextRecord->Eip; // Current location in program + frame.AddrPC.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing + frame.AddrStack.Offset = pEp->ContextRecord->Esp; // Stack pointers current value + frame.AddrStack.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing + frame.AddrFrame.Offset = pEp->ContextRecord->Ebp; // Value of register used to access local function variables. + frame.AddrFrame.Mode = AddrModeFlat; // Address mode for this pointer: flat 32 bit addressing + + if(pSI(hCurProc, NULL, TRUE) == FALSE) + goto cleanup; + + DWORD symOptions = pSGO(); + symOptions |= SYMOPT_LOAD_LINES; + symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; + symOptions &= ~SYMOPT_UNDNAME; + symOptions &= ~SYMOPT_DEFERRED_LOADS; + symOptions = pSSO(symOptions); + + pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + if (!pSym) + goto cleanup; + memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; + + IMAGEHLP_LINE64 Line; + memset(&Line, 0, sizeof(Line)); + Line.SizeOfStruct = sizeof(Line); + + IMAGEHLP_MODULE64 Module; + memset(&Module, 0, sizeof(Module)); + Module.SizeOfStruct = sizeof(Module); + int seq=0; + + strOutput = StringUtils::Format("Thread %d (process %d)\r\n", GetCurrentThreadId(), GetCurrentProcessId()); + WriteFile(hDumpFile, strOutput.c_str(), strOutput.size(), &dwBytes, NULL); + + while(pSW(IMAGE_FILE_MACHINE_I386, hCurProc, GetCurrentThread(), &frame, pEp->ContextRecord, NULL, pSFTA, pSGMB, NULL)) + { + if(frame.AddrPC.Offset != 0) + { + DWORD64 symoffset=0; + DWORD lineoffset=0; + strOutput = StringUtils::Format("#%2d", seq++); + + if(pSGSFA(hCurProc, frame.AddrPC.Offset, &symoffset, pSym)) + { + if(pUDSN(pSym->Name, cTemp, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE)>0) + strOutput.append(StringUtils::Format(" %s", cTemp)); + } + if(pSGLFA(hCurProc, frame.AddrPC.Offset, &lineoffset, &Line)) + strOutput.append(StringUtils::Format(" at %s:%d", Line.FileName, Line.LineNumber)); + + strOutput.append("\r\n"); + WriteFile(hDumpFile, strOutput.c_str(), strOutput.size(), &dwBytes, NULL); + } + } + returncode = true; + +cleanup: + if (pSym) + free( pSym ); + + if (hDumpFile != INVALID_HANDLE_VALUE) + CloseHandle(hDumpFile); + + if(pSC) + pSC(hCurProc); + + if (hDbgHelpDll) + FreeLibrary(hDbgHelpDll); + + return returncode; +} + +access_violation::access_violation(EXCEPTION_POINTERS* info) : + win32_exception(info,"access_violation"), mAccessType(Invalid), mBadAddress(0) +{ + switch(info->ExceptionRecord->ExceptionInformation[0]) + { + case 0: + mAccessType = Read; + break; + case 1: + mAccessType = Write; + break; + case 8: + mAccessType = DEP; + break; + } + mBadAddress = reinterpret_cast(info->ExceptionRecord->ExceptionInformation[1]); +} + +void access_violation::LogThrowMessage(const char *prefix) const +{ + if( prefix ) + if( mAccessType == Write) + LOG(LOGERROR, "Unhandled exception in %s : %s at 0x%08x: Writing location 0x%08x", prefix, what(), where(), address()); + else if( mAccessType == Read) + LOG(LOGERROR, "Unhandled exception in %s : %s at 0x%08x: Reading location 0x%08x", prefix, what(), where(), address()); + else if( mAccessType == DEP) + LOG(LOGERROR, "Unhandled exception in %s : %s at 0x%08x: DEP violation, location 0x%08x", prefix, what(), where(), address()); + else + LOG(LOGERROR, "Unhandled exception in %s : %s at 0x%08x: unknown access type, location 0x%08x", prefix, what(), where(), address()); + else + if( mAccessType == Write) + LOG(LOGERROR, "Unhandled exception in %s at 0x%08x: Writing location 0x%08x", what(), where(), address()); + else if( mAccessType == Read) + LOG(LOGERROR, "Unhandled exception in %s at 0x%08x: Reading location 0x%08x", what(), where(), address()); + else if( mAccessType == DEP) + LOG(LOGERROR, "Unhandled exception in %s at 0x%08x: DEP violation, location 0x%08x", what(), where(), address()); + else + LOG(LOGERROR, "Unhandled exception in %s at 0x%08x: unknown access type, location 0x%08x", what(), where(), address()); + + write_stacktrace(); + write_minidump(); +} diff --git a/KodiThreads/threads/platform/win/Win32Exception.h b/KodiThreads/threads/platform/win/Win32Exception.h new file mode 100644 index 0000000..f36c8c4 --- /dev/null +++ b/KodiThreads/threads/platform/win/Win32Exception.h @@ -0,0 +1,73 @@ +#pragma once + +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include +#include +#include "commons/Exception.h" + +class win32_exception: public XbmcCommons::UncheckedException +{ +public: + typedef const void* Address; // OK on Win32 platform + + static void install_handler(); + static void set_version(std::string version) { mVersion = version; }; + virtual const char* what() const { return mWhat; }; + Address where() const { return mWhere; }; + unsigned code() const { return mCode; }; + virtual void LogThrowMessage(const char *prefix) const; + static bool write_minidump(EXCEPTION_POINTERS* pEp); + static bool write_stacktrace(EXCEPTION_POINTERS* pEp); +protected: + win32_exception(EXCEPTION_POINTERS*, const char* classname = NULL); + static void translate(unsigned code, EXCEPTION_POINTERS* info); + + inline bool write_minidump() const { return write_minidump(mExceptionPointers); }; + inline bool write_stacktrace() const { return write_stacktrace(mExceptionPointers); }; +private: + const char* mWhat; + Address mWhere; + unsigned mCode; + EXCEPTION_POINTERS *mExceptionPointers; + static std::string mVersion; +}; + +class access_violation: public win32_exception +{ + enum access_type + { + Invalid, + Read, + Write, + DEP + }; + +public: + Address address() const { return mBadAddress; }; + virtual void LogThrowMessage(const char *prefix) const; +protected: + friend void win32_exception::translate(unsigned code, EXCEPTION_POINTERS* info); +private: + access_type mAccessType; + Address mBadAddress; + access_violation(EXCEPTION_POINTERS* info); +}; diff --git a/KodiThreads/threads/test/CMakeLists.txt b/KodiThreads/threads/test/CMakeLists.txt new file mode 100644 index 0000000..67bbe76 --- /dev/null +++ b/KodiThreads/threads/test/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES TestEvent.cpp + TestSharedSection.cpp + TestAtomics.cpp + TestThreadLocal.cpp) + +set(HEADERS TestHelpers.h) + +core_add_test_library(threads_test) diff --git a/KodiThreads/threads/test/Makefile b/KodiThreads/threads/test/Makefile new file mode 100644 index 0000000..d41a2ab --- /dev/null +++ b/KodiThreads/threads/test/Makefile @@ -0,0 +1,12 @@ +SRCS= \ + TestEvent.cpp \ + TestSharedSection.cpp \ + TestAtomics.cpp \ + TestThreadLocal.cpp + +LIB=threadTest.a + +INCLUDES += -I../../../lib/gtest/include + +include ../../../Makefile.include +-include $(patsubst %.cpp,%.P,$(patsubst %.c,%.P,$(SRCS))) diff --git a/KodiThreads/threads/test/TestAtomics.cpp b/KodiThreads/threads/test/TestAtomics.cpp new file mode 100644 index 0000000..79bab2c --- /dev/null +++ b/KodiThreads/threads/test/TestAtomics.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "TestHelpers.h" +#include "threads/Atomics.h" + +#include +#include + +#define TESTNUM 100000l +#define NUMTHREADS 10l + +class DoIncrement : public IRunnable +{ + long* number; +public: + inline DoIncrement(long* num) : number(num) {} + + virtual void Run() + { + for (long i = 0; i> t(NUMTHREADS); + for (size_t i=0;ijoin(); + + EXPECT_EQ((NUMTHREADS * TESTNUM), lNumber); + } + +TEST(TestMassAtomic, Decrement) +{ + long lNumber = (NUMTHREADS * TESTNUM); + DoDecrement dd(&lNumber); + + std::vector> t(NUMTHREADS); + for (size_t i=0;ijoin(); + + EXPECT_EQ(0, lNumber); + } + +TEST(TestMassAtomic, Add) +{ + long lNumber = 0; + long toAdd = 10; + DoAdd da(&lNumber,toAdd); + + std::vector> t(NUMTHREADS); + for(size_t i=0; ijoin(); + + EXPECT_EQ((NUMTHREADS * TESTNUM) * toAdd, lNumber); + } + +TEST(TestMassAtomic, Subtract) +{ + long toSubtract = 10; + long lNumber = (NUMTHREADS * TESTNUM) * toSubtract; + DoSubtract ds(&lNumber,toSubtract); + + std::vector> t(NUMTHREADS); + for(size_t i=0; ijoin(); + + EXPECT_EQ(0, lNumber); + } + +#define STARTVAL 767856l + +TEST(TestAtomic, Increment) +{ + long check = STARTVAL; + EXPECT_EQ(STARTVAL + 1l, AtomicIncrement(&check)); + EXPECT_EQ(STARTVAL + 1l,check); +} + +TEST(TestAtomic, Decrement) +{ + long check = STARTVAL; + EXPECT_EQ(STARTVAL - 1l, AtomicDecrement(&check)); + EXPECT_EQ(STARTVAL - 1l,check); +} + +TEST(TestAtomic, Add) +{ + long check = STARTVAL; + EXPECT_EQ(STARTVAL + 123l, AtomicAdd(&check,123l)); + EXPECT_EQ(STARTVAL + 123l,check); +} + +TEST(TestAtomic, Subtract) +{ + long check = STARTVAL; + EXPECT_EQ(STARTVAL - 123l, AtomicSubtract(&check,123l)); + EXPECT_EQ(STARTVAL - 123l, check); +} + diff --git a/KodiThreads/threads/test/TestEvent.cpp b/KodiThreads/threads/test/TestEvent.cpp new file mode 100644 index 0000000..b552af2 --- /dev/null +++ b/KodiThreads/threads/test/TestEvent.cpp @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/Event.h" +#include "threads/Atomics.h" + +#include "threads/test/TestHelpers.h" + +#include +#include + +using namespace XbmcThreads; + +//============================================================================= +// Helper classes +//============================================================================= + +class waiter : public IRunnable +{ + CEvent& event; +public: + bool& result; + + volatile bool waiting; + + waiter(CEvent& o, bool& flag) : event(o), result(flag), waiting(false) {} + + void Run() + { + waiting = true; + result = event.Wait(); + waiting = false; + } +}; + +class timed_waiter : public IRunnable +{ + CEvent& event; + unsigned int waitTime; +public: + int& result; + + volatile bool waiting; + + timed_waiter(CEvent& o, int& flag, int waitTimeMillis) : event(o), waitTime(waitTimeMillis), result(flag), waiting(false) {} + + void Run() + { + waiting = true; + result = 0; + result = event.WaitMSec(waitTime) ? 1 : -1; + waiting = false; + } +}; + +class group_wait : public IRunnable +{ + CEventGroup& event; + int timeout; +public: + CEvent* result; + bool waiting; + + group_wait(CEventGroup& o) : event(o), timeout(-1), result(NULL), waiting(false) {} + group_wait(CEventGroup& o, int timeout_) : event(o), timeout(timeout_), result(NULL), waiting(false) {} + + void Run() + { + waiting = true; + if (timeout == -1) + result = event.wait(); + else + result = event.wait((unsigned int)timeout); + waiting = false; + } +}; + +//============================================================================= + +TEST(TestEvent, General) +{ + CEvent event; + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event,1,10000)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(10000)); + + EXPECT_TRUE(result); +} + +TEST(TestEvent, TwoWaits) +{ + CEvent event; + bool result1 = false; + bool result2 = false; + waiter w1(event,result1); + waiter w2(event,result2); + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event,2,10000)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); + + EXPECT_TRUE(result1); + EXPECT_TRUE(result2); + +} + +TEST(TestEvent, TimedWaits) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event,result1,100); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event,1,10000)); + + EXPECT_TRUE(result1 == 0); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + + EXPECT_TRUE(result1 == 1); +} + +TEST(TestEvent, TimedWaitsTimeout) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event,result1,50); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event,1,100)); + + EXPECT_TRUE(result1 == 0); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + + EXPECT_TRUE(result1 == -1); +} + +TEST(TestEvent, Group) +{ + CEvent event1; + CEvent event2; + + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1,1,10000)); + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + EXPECT_TRUE(waitForWaiters(group,1,10000)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); + +} + +/* Test disabled for now, because it deadlocks +TEST(TestEvent, GroupLimitedGroupScope) +{ + CEvent event1; + CEvent event2; + + { + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1,1,10000)); + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + EXPECT_TRUE(waitForWaiters(group,1,10000)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + } + + event2.Set(); + + SleepMillis(50); // give thread 2 a chance to exit +}*/ + +TEST(TestEvent, TwoGroups) +{ + CEvent event1; + CEvent event2; + + CEventGroup group1(2, &event1,&event2); + CEventGroup group2(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group1); + group_wait w4(group2); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(event1,1,10000)); + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + EXPECT_TRUE(waitForWaiters(group1,1,10000)); + EXPECT_TRUE(waitForWaiters(group2,1,10000)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_EQ(w3.result,(void*)NULL); + EXPECT_TRUE(w4.waiting); + EXPECT_EQ(w4.result,(void*)NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread4.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); +} + +TEST(TestEvent, AutoResetBehavior) +{ + CEvent event; + + EXPECT_TRUE(!event.WaitMSec(1)); + + event.Set(); // event will remain signaled if there are no waits + + EXPECT_TRUE(event.WaitMSec(1)); +} + +TEST(TestEvent, ManualReset) +{ + CEvent event(true); + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event,1,10000)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(MILLIS(10000))); + + EXPECT_TRUE(result); + + // with manual reset, the state should remain signaled + EXPECT_TRUE(event.WaitMSec(1)); + + event.Reset(); + + EXPECT_TRUE(!event.WaitMSec(1)); +} + +TEST(TestEvent, InitVal) +{ + CEvent event(false,true); + EXPECT_TRUE(event.WaitMSec(50)); +} + +TEST(TestEvent, SimpleTimeout) +{ + CEvent event; + EXPECT_TRUE(!event.WaitMSec(50)); +} + +TEST(TestEvent, GroupChildSet) +{ + CEvent event1(true); + CEvent event2; + + event1.Set(); + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); +} + +TEST(TestEvent, GroupChildSet2) +{ + CEvent event1(true,true); + CEvent event2; + + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); +} + +TEST(TestEvent, GroupWaitResetsChild) +{ + CEvent event1; + CEvent event2; + + CEventGroup group(&event1,&event2,NULL); + + group_wait w3(group); + + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group,1,10000)); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event2.Set(); + + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event2); + // event2 should have been reset. + EXPECT_TRUE(event2.WaitMSec(1) == false); +} + +TEST(TestEvent, GroupTimedWait) +{ + CEvent event1; + CEvent event2; + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event1,1,10000)); + EXPECT_TRUE(waitForWaiters(event2,1,10000)); + + EXPECT_TRUE(group.wait(20) == NULL); // waited ... got nothing + + group_wait w3(group,50); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group,1,10000)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + // this should end given the wait is for only 50 millis + EXPECT_TRUE(waitThread3.timed_join(MILLIS(100))); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + group_wait w4(group,50); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(group,1,10000)); + + EXPECT_TRUE(w4.waiting); + EXPECT_TRUE(w4.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread4.timed_join(MILLIS(10000))); + SleepMillis(10); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); +} + +#define TESTNUM 100000l +#define NUMTHREADS 100l + +CEvent* g_event = NULL; +volatile long g_mutex; + +class mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting; + + mass_waiter() : event(*g_event), waiting(false) {} + + void Run() + { + waiting = true; + AtomicGuard g(&g_mutex); + result = event.Wait(); + waiting = false; + } +}; + +class poll_mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting; + + poll_mass_waiter() : event(*g_event), waiting(false) {} + + void Run() + { + waiting = true; + AtomicGuard g(&g_mutex); + while ((result = event.WaitMSec(0)) == false); + waiting = false; + } +}; + +template void RunMassEventTest(std::vector>& m, bool canWaitOnEvent) +{ + std::vector> t(NUMTHREADS); + for(size_t i=0; iwaiting); + } + + g_event->Set(); + + for(size_t i=0; itimed_join(MILLIS(10000))); + } + + for(size_t i=0; iwaiting); + EXPECT_TRUE(m[i]->result); + } +} + + +TEST(TestMassEvent, General) +{ + g_event = new CEvent(); + + std::vector> m(NUMTHREADS); + for(size_t i=0; i> m(NUMTHREADS); + for(size_t i=0; i. + * + */ + +#pragma once + +#include "gtest/gtest.h" + +#include "threads/Thread.h" +#include "threads/Atomics.h" + +#define MILLIS(x) x + +inline static void SleepMillis(unsigned int millis) { XbmcThreads::ThreadSleep(millis); } + +template inline static bool waitForWaiters(E& event, int numWaiters, int milliseconds) +{ + for( int i = 0; i < milliseconds; i++) + { + if (event.getNumWaits() == numWaiters) + return true; + SleepMillis(1); + } + return false; +} + +inline static bool waitForThread(volatile long& mutex, int numWaiters, int milliseconds) +{ + CCriticalSection sec; + for( int i = 0; i < milliseconds; i++) + { + if (mutex == (long)numWaiters) + return true; + + { + CSingleLock tmplock(sec); // kick any memory syncs + } + SleepMillis(1); + } + return false; +} + +class AtomicGuard +{ + volatile long* val; +public: + inline AtomicGuard(volatile long* val_) : val(val_) { if (val) AtomicIncrement(val); } + inline ~AtomicGuard() { if (val) AtomicDecrement(val); } +}; + +class thread +{ + IRunnable* f; + CThread* cthread; + +// inline thread(const thread& other) { } +public: + inline explicit thread(IRunnable& runnable) : + f(&runnable), cthread(new CThread(f, "DumbThread")) + { + cthread->Create(); + } + + inline thread() : f(NULL), cthread(NULL) {} + ~thread() + { + delete cthread; + } + + /** + * Gcc-4.2 requires this to be 'const' to find the right constructor. + * It really shouldn't be since it modifies the parameter thread + * to ensure only one thread instance has control of the + * Runnable.a + */ + inline thread(const thread& other) : f(other.f), cthread(other.cthread) { ((thread&)other).f = NULL; ((thread&)other).cthread = NULL; } + inline thread& operator=(const thread& other) { f = other.f; ((thread&)other).f = NULL; cthread = other.cthread; ((thread&)other).cthread = NULL; return *this; } + + void join() + { + cthread->WaitForThreadExit((unsigned int)-1); + } + + bool timed_join(unsigned int millis) + { + return cthread->WaitForThreadExit(millis); + } +}; + diff --git a/KodiThreads/threads/test/TestSharedSection.cpp b/KodiThreads/threads/test/TestSharedSection.cpp new file mode 100644 index 0000000..13b1e2c --- /dev/null +++ b/KodiThreads/threads/test/TestSharedSection.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/SharedSection.h" +#include "threads/SingleLock.h" +#include "threads/Event.h" +#include "threads/Atomics.h" +#include "threads/test/TestHelpers.h" + +#include + +//============================================================================= +// Helper classes +//============================================================================= + +template +class locker : public IRunnable +{ + CSharedSection& sec; + CEvent* wait; + + volatile long* mutex; +public: + volatile bool haslock; + volatile bool obtainedlock; + + inline locker(CSharedSection& o, volatile long* mutex_ = NULL, CEvent* wait_ = NULL) : + sec(o), wait(wait_), mutex(mutex_), haslock(false), obtainedlock(false) {} + + inline locker(CSharedSection& o, CEvent* wait_ = NULL) : + sec(o), wait(wait_), mutex(NULL), haslock(false), obtainedlock(false) {} + + void Run() + { + AtomicGuard g(mutex); + L lock(sec); + haslock = true; + obtainedlock = true; + if (wait) + wait->Wait(); + haslock = false; + } +}; + +TEST(TestCritSection, General) +{ + CCriticalSection sec; + + CSingleLock l1(sec); + CSingleLock l2(sec); +} + +TEST(TestSharedSection, General) +{ + CSharedSection sec; + + CSharedLock l1(sec); + CSharedLock l2(sec); +} + +TEST(TestSharedSection, GetSharedLockWhileTryingExclusiveLock) +{ + volatile long mutex = 0; + CEvent event; + + CSharedSection sec; + + CSharedLock l1(sec); // get a shared lock + + locker l2(sec,&mutex); + thread waitThread1(l2); // try to get an exclusive lock + + EXPECT_TRUE(waitForThread(mutex,1,10000)); + SleepMillis(10); // still need to give it a chance to move ahead + + EXPECT_TRUE(!l2.haslock); // this thread is waiting ... + EXPECT_TRUE(!l2.obtainedlock); // this thread is waiting ... + + // now try and get a SharedLock + locker l3(sec,&mutex,&event); + thread waitThread3(l3); // try to get a shared lock + EXPECT_TRUE(waitForThread(mutex,2,10000)); + SleepMillis(10); + EXPECT_TRUE(l3.haslock); + + event.Set(); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + + // l3 should have released. + EXPECT_TRUE(!l3.haslock); + + // but the exclusive lock should still not have happened + EXPECT_TRUE(!l2.haslock); // this thread is waiting ... + EXPECT_TRUE(!l2.obtainedlock); // this thread is waiting ... + + // let it go + l1.Leave(); // the last shared lock leaves. + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + + EXPECT_TRUE(l2.obtainedlock); // the exclusive lock was captured + EXPECT_TRUE(!l2.haslock); // ... but it doesn't have it anymore +} + +TEST(TestSharedSection, TwoCase) +{ + CSharedSection sec; + + CEvent event; + volatile long mutex = 0; + + locker l1(sec,&mutex,&event); + + { + CSharedLock lock(sec); + thread waitThread1(l1); + + EXPECT_TRUE(waitForWaiters(event,1,10000)); + EXPECT_TRUE(l1.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + } + + locker l2(sec,&mutex,&event); + { + CExclusiveLock lock(sec); // get exclusive lock + thread waitThread2(l2); // thread should block + + EXPECT_TRUE(waitForThread(mutex,1,10000)); + SleepMillis(10); + + EXPECT_TRUE(!l2.haslock); + + lock.Leave(); + + EXPECT_TRUE(waitForWaiters(event,1,10000)); + SleepMillis(10); + EXPECT_TRUE(l2.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); + } +} + +TEST(TestMultipleSharedSection, General) +{ + CSharedSection sec; + + CEvent event; + volatile long mutex = 0; + + locker l1(sec,&mutex, &event); + + { + CSharedLock lock(sec); + thread waitThread1(l1); + + EXPECT_TRUE(waitForThread(mutex,1,10000)); + SleepMillis(10); + + EXPECT_TRUE(l1.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + } + + locker l2(sec,&mutex,&event); + locker l3(sec,&mutex,&event); + locker l4(sec,&mutex,&event); + locker l5(sec,&mutex,&event); + { + CExclusiveLock lock(sec); + thread waitThread1(l2); + thread waitThread2(l3); + thread waitThread3(l4); + thread waitThread4(l5); + + EXPECT_TRUE(waitForThread(mutex,4,10000)); + SleepMillis(10); + + EXPECT_TRUE(!l2.haslock); + EXPECT_TRUE(!l3.haslock); + EXPECT_TRUE(!l4.haslock); + EXPECT_TRUE(!l5.haslock); + + lock.Leave(); + + EXPECT_TRUE(waitForWaiters(event,4,10000)); + + EXPECT_TRUE(l2.haslock); + EXPECT_TRUE(l3.haslock); + EXPECT_TRUE(l4.haslock); + EXPECT_TRUE(l5.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread2.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread3.timed_join(MILLIS(10000))); + EXPECT_TRUE(waitThread4.timed_join(MILLIS(10000))); + } +} + diff --git a/KodiThreads/threads/test/TestThreadLocal.cpp b/KodiThreads/threads/test/TestThreadLocal.cpp new file mode 100644 index 0000000..2667fc7 --- /dev/null +++ b/KodiThreads/threads/test/TestThreadLocal.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "threads/ThreadLocal.h" + +#include "threads/Event.h" +#include "TestHelpers.h" + +using namespace XbmcThreads; + +bool destructorCalled = false; + +class Thinggy +{ +public: + inline ~Thinggy() { destructorCalled = true; } +}; + +Thinggy* staticThinggy = NULL; +CEvent gate; +ThreadLocal staticThreadLocal; + +void cleanup() +{ + if (destructorCalled) + staticThinggy = NULL; + destructorCalled = false; +} + +CEvent waiter; +class Runnable : public IRunnable +{ +public: + bool waiting; + bool threadLocalHadValue; + ThreadLocal& threadLocal; + + inline Runnable(ThreadLocal& tl) : waiting(false), threadLocal(tl) {} + inline void Run() + { + staticThinggy = new Thinggy; + staticThreadLocal.set(staticThinggy); + waiting = true; + gate.Set(); + waiter.Wait(); + waiting = false; + + threadLocalHadValue = staticThreadLocal.get() != NULL; + gate.Set(); + } +}; + +class GlobalThreadLocal : public Runnable +{ +public: + GlobalThreadLocal() : Runnable(staticThreadLocal) {} +}; + +class StackThreadLocal : public Runnable +{ +public: + ThreadLocal threadLocal; + inline StackThreadLocal() : Runnable(threadLocal) {} +}; + +class HeapThreadLocal : public Runnable +{ +public: + ThreadLocal* hthreadLocal; + inline HeapThreadLocal() : Runnable(*(new ThreadLocal)) { hthreadLocal = &threadLocal; } + inline ~HeapThreadLocal() { delete hthreadLocal; } +}; + + +TEST(TestThreadLocal, Simple) +{ + GlobalThreadLocal runnable; + thread t(runnable); + + gate.Wait(); + EXPECT_TRUE(runnable.waiting); + EXPECT_TRUE(staticThinggy != NULL); + EXPECT_TRUE(staticThreadLocal.get() == NULL); + waiter.Set(); + gate.Wait(); + EXPECT_TRUE(runnable.threadLocalHadValue); + EXPECT_TRUE(!destructorCalled); + delete staticThinggy; + EXPECT_TRUE(destructorCalled); + cleanup(); +} + +TEST(TestThreadLocal, Stack) +{ + StackThreadLocal runnable; + thread t(runnable); + + gate.Wait(); + EXPECT_TRUE(runnable.waiting); + EXPECT_TRUE(staticThinggy != NULL); + EXPECT_TRUE(runnable.threadLocal.get() == NULL); + waiter.Set(); + gate.Wait(); + EXPECT_TRUE(runnable.threadLocalHadValue); + EXPECT_TRUE(!destructorCalled); + delete staticThinggy; + EXPECT_TRUE(destructorCalled); + cleanup(); +} + +TEST(TestThreadLocal, Heap) +{ + HeapThreadLocal runnable; + thread t(runnable); + + gate.Wait(); + EXPECT_TRUE(runnable.waiting); + EXPECT_TRUE(staticThinggy != NULL); + EXPECT_TRUE(runnable.threadLocal.get() == NULL); + waiter.Set(); + gate.Wait(); + EXPECT_TRUE(runnable.threadLocalHadValue); + EXPECT_TRUE(!destructorCalled); + delete staticThinggy; + EXPECT_TRUE(destructorCalled); + cleanup(); +} + +TEST(TestThreadLocal, HeapDestroyed) +{ + { + HeapThreadLocal runnable; + thread t(runnable); + + gate.Wait(); + EXPECT_TRUE(runnable.waiting); + EXPECT_TRUE(staticThinggy != NULL); + EXPECT_TRUE(runnable.threadLocal.get() == NULL); + waiter.Set(); + gate.Wait(); + EXPECT_TRUE(runnable.threadLocalHadValue); + EXPECT_TRUE(!destructorCalled); + } // runnable goes out of scope + + // even though the threadlocal is gone ... + EXPECT_TRUE(!destructorCalled); + delete staticThinggy; + EXPECT_TRUE(destructorCalled); + cleanup(); +} + diff --git a/KodiThreads/utils/win32/WIN32Util.cpp b/KodiThreads/utils/win32/WIN32Util.cpp new file mode 100644 index 0000000..4ff0125 --- /dev/null +++ b/KodiThreads/utils/win32/WIN32Util.cpp @@ -0,0 +1,1635 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "WIN32Util.h" +#include "Util.h" +#include "utils/URIUtils.h" +#include "storage/cdioSupport.h" +#include "PowrProf.h" +#include "WindowHelper.h" +#include "Application.h" +#include +#include "filesystem/SpecialProtocol.h" +#include "my_ntddscsi.h" +#include "Setupapi.h" +#include "storage/MediaManager.h" +#include "windowing/WindowingFactory.h" +#include "guilib/LocalizeStrings.h" +#include "utils/CharsetConverter.h" +#include "utils/log.h" +#include "powermanagement\PowerManager.h" +#include "utils/SystemInfo.h" +#include "utils/Environment.h" +#include "utils/StringUtils.h" +#include "win32/crts_caller.h" + +#include + + +#include + +extern HWND g_hWnd; + +using namespace MEDIA_DETECT; + +CWIN32Util::CWIN32Util(void) +{ +} + +CWIN32Util::~CWIN32Util(void) +{ +} + +int CWIN32Util::GetDriveStatus(const std::string &strPath, bool bStatusEx) +{ + HANDLE hDevice; // handle to the drive to be examined + int iResult; // results flag + ULONG ulChanges=0; + DWORD dwBytesReturned; + T_SPDT_SBUF sptd_sb; //SCSI Pass Through Direct variable. + byte DataBuf[8]; //Buffer for holding data to/from drive. + + CLog::Log(LOGDEBUG, __FUNCTION__": Requesting status for drive %s.", strPath.c_str()); + + hDevice = CreateFile( strPath.c_str(), // drive + 0, // no access to the drive + FILE_SHARE_READ, // share mode + NULL, // default security attributes + OPEN_EXISTING, // disposition + FILE_ATTRIBUTE_READONLY, // file attributes + NULL); + + if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive + { + CLog::Log(LOGERROR, __FUNCTION__": Failed to CreateFile for %s.", strPath.c_str()); + return -1; + } + + CLog::Log(LOGDEBUG, __FUNCTION__": Requesting media status for drive %s.", strPath.c_str()); + iResult = DeviceIoControl((HANDLE) hDevice, // handle to device + IOCTL_STORAGE_CHECK_VERIFY2, // dwIoControlCode + NULL, // lpInBuffer + 0, // nInBufferSize + &ulChanges, // lpOutBuffer + sizeof(ULONG), // nOutBufferSize + &dwBytesReturned , // number of bytes returned + NULL ); // OVERLAPPED structure + + CloseHandle(hDevice); + + if(iResult == 1) + return 2; + + // don't request the tray status as we often doesn't need it + if(!bStatusEx) + return 0; + + hDevice = CreateFile( strPath.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + + if (hDevice == INVALID_HANDLE_VALUE) + { + CLog::Log(LOGERROR, __FUNCTION__": Failed to CreateFile2 for %s.", strPath.c_str()); + return -1; + } + + sptd_sb.sptd.Length=sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd_sb.sptd.PathId=0; + sptd_sb.sptd.TargetId=0; + sptd_sb.sptd.Lun=0; + sptd_sb.sptd.CdbLength=10; + sptd_sb.sptd.SenseInfoLength=MAX_SENSE_LEN; + sptd_sb.sptd.DataIn=SCSI_IOCTL_DATA_IN; + sptd_sb.sptd.DataTransferLength=sizeof(DataBuf); + sptd_sb.sptd.TimeOutValue=2; + sptd_sb.sptd.DataBuffer=(PVOID)&(DataBuf); + sptd_sb.sptd.SenseInfoOffset=sizeof(SCSI_PASS_THROUGH_DIRECT); + + sptd_sb.sptd.Cdb[0]=0x4a; + sptd_sb.sptd.Cdb[1]=1; + sptd_sb.sptd.Cdb[2]=0; + sptd_sb.sptd.Cdb[3]=0; + sptd_sb.sptd.Cdb[4]=0x10; + sptd_sb.sptd.Cdb[5]=0; + sptd_sb.sptd.Cdb[6]=0; + sptd_sb.sptd.Cdb[7]=0; + sptd_sb.sptd.Cdb[8]=8; + sptd_sb.sptd.Cdb[9]=0; + sptd_sb.sptd.Cdb[10]=0; + sptd_sb.sptd.Cdb[11]=0; + sptd_sb.sptd.Cdb[12]=0; + sptd_sb.sptd.Cdb[13]=0; + sptd_sb.sptd.Cdb[14]=0; + sptd_sb.sptd.Cdb[15]=0; + + ZeroMemory(DataBuf, 8); + ZeroMemory(sptd_sb.SenseBuf, MAX_SENSE_LEN); + + //Send the command to drive + CLog::Log(LOGDEBUG, __FUNCTION__": Requesting tray status for drive %s.", strPath.c_str()); + iResult = DeviceIoControl((HANDLE) hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + (PVOID)&sptd_sb, (DWORD)sizeof(sptd_sb), + (PVOID)&sptd_sb, (DWORD)sizeof(sptd_sb), + &dwBytesReturned, + NULL); + + CloseHandle(hDevice); + + if(iResult) + { + + if(DataBuf[5] == 0) // tray close + return 0; + else if(DataBuf[5] == 1) // tray open + return 1; + else + return 2; // tray closed, media present + } + CLog::Log(LOGERROR, __FUNCTION__": Could not determine tray status %d", GetLastError()); + return -1; +} + +char CWIN32Util::FirstDriveFromMask (ULONG unitmask) +{ + char i; + for (i = 0; i < 26; ++i) + { + if (unitmask & 0x1) break; + unitmask = unitmask >> 1; + } + return (i + 'A'); +} + +bool CWIN32Util::PowerManagement(PowerState State) +{ + static bool gotShutdownPrivileges = false; + if (!gotShutdownPrivileges) + { + HANDLE hToken; + // Get a token for this process. + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + // Get the LUID for the shutdown privilege. + TOKEN_PRIVILEGES tkp = {}; + if (LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid)) + { + tkp.PrivilegeCount = 1; // one privilege to set + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + // Get the shutdown privilege for this process. + if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0)) + gotShutdownPrivileges = true; + } + CloseHandle(hToken); + } + + if (!gotShutdownPrivileges) + return false; + } + + switch (State) + { + case POWERSTATE_HIBERNATE: + CLog::Log(LOGINFO, "Asking Windows to hibernate..."); + return SetSuspendState(true, true, false) == TRUE; + break; + case POWERSTATE_SUSPEND: + CLog::Log(LOGINFO, "Asking Windows to suspend..."); + return SetSuspendState(false, true, false) == TRUE; + break; + case POWERSTATE_SHUTDOWN: + CLog::Log(LOGINFO, "Shutdown Windows..."); + if (g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8)) + return InitiateShutdownW(NULL, NULL, 0, SHUTDOWN_HYBRID | SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_POWEROFF, + SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED) == ERROR_SUCCESS; + return InitiateShutdownW(NULL, NULL, 0, SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_POWEROFF, + SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED) == ERROR_SUCCESS; + break; + case POWERSTATE_REBOOT: + CLog::Log(LOGINFO, "Rebooting Windows..."); + if (g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8)) + return InitiateShutdownW(NULL, NULL, 0, SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_RESTART, + SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED) == ERROR_SUCCESS; + return InitiateShutdownW(NULL, NULL, 0, SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_RESTART, + SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED) == ERROR_SUCCESS; + break; + default: + CLog::Log(LOGERROR, "Unknown PowerState called."); + return false; + break; + } +} + +int CWIN32Util::BatteryLevel() +{ + SYSTEM_POWER_STATUS SystemPowerStatus; + + if (GetSystemPowerStatus(&SystemPowerStatus) && SystemPowerStatus.BatteryLifePercent != 255) + return SystemPowerStatus.BatteryLifePercent; + + return 0; +} + +bool CWIN32Util::XBMCShellExecute(const std::string &strPath, bool bWaitForScriptExit) +{ + std::string strCommand = strPath; + std::string strExe = strPath; + std::string strParams; + std::string strWorkingDir; + + StringUtils::Trim(strCommand); + if (strCommand.empty()) + { + return false; + } + size_t iIndex = std::string::npos; + char split = ' '; + if (strCommand[0] == '\"') + { + split = '\"'; + } + iIndex = strCommand.find(split, 1); + if (iIndex != std::string::npos) + { + strExe = strCommand.substr(0, iIndex + 1); + strParams = strCommand.substr(iIndex + 1); + } + + StringUtils::Replace(strExe, "\"", ""); + + strWorkingDir = strExe; + iIndex = strWorkingDir.rfind('\\'); + if(iIndex != std::string::npos) + { + strWorkingDir[iIndex+1] = '\0'; + } + + std::wstring WstrExe, WstrParams, WstrWorkingDir; + g_charsetConverter.utf8ToW(strExe, WstrExe); + g_charsetConverter.utf8ToW(strParams, WstrParams); + g_charsetConverter.utf8ToW(strWorkingDir, WstrWorkingDir); + + bool ret; + SHELLEXECUTEINFOW ShExecInfo = {0}; + ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ShExecInfo.hwnd = NULL; + ShExecInfo.lpVerb = NULL; + ShExecInfo.lpFile = WstrExe.c_str(); + ShExecInfo.lpParameters = WstrParams.c_str(); + ShExecInfo.lpDirectory = WstrWorkingDir.c_str(); + ShExecInfo.nShow = SW_SHOW; + ShExecInfo.hInstApp = NULL; + + g_windowHelper.StopThread(); + + LockSetForegroundWindow(LSFW_UNLOCK); + ShowWindow(g_hWnd,SW_MINIMIZE); + ret = ShellExecuteExW(&ShExecInfo) == TRUE; + g_windowHelper.SetHANDLE(ShExecInfo.hProcess); + + // ShellExecute doesn't return the window of the started process + // we need to gather it from somewhere to allow switch back to XBMC + // when a program is minimized instead of stopped. + //g_windowHelper.SetHWND(ShExecInfo.hwnd); + g_windowHelper.Create(); + + if(bWaitForScriptExit) + { + //! @todo Pause music and video playback + WaitForSingleObject(ShExecInfo.hProcess,INFINITE); + } + + return ret; +} + +std::vector CWIN32Util::GetDiskUsage() +{ + std::vector result; + ULARGE_INTEGER ULTotal= { { 0 } }; + ULARGE_INTEGER ULTotalFree= { { 0 } }; + + char* pcBuffer= NULL; + DWORD dwStrLength= GetLogicalDriveStrings( 0, pcBuffer ); + if( dwStrLength != 0 ) + { + std::string strRet; + + dwStrLength+= 1; + pcBuffer= new char [dwStrLength]; + GetLogicalDriveStrings( dwStrLength, pcBuffer ); + int iPos= 0; + do + { + std::string strDrive = pcBuffer + iPos; + if( DRIVE_FIXED == GetDriveType( strDrive.c_str() ) && + GetDiskFreeSpaceEx( ( strDrive.c_str() ), NULL, &ULTotal, &ULTotalFree ) ) + { + strRet = StringUtils::Format("%s %d MB %s",strDrive.c_str(), int(ULTotalFree.QuadPart/(1024*1024)),g_localizeStrings.Get(160).c_str()); + result.push_back(strRet); + } + iPos += (strlen( pcBuffer + iPos) + 1 ); + }while( strlen( pcBuffer + iPos ) > 0 ); + } + delete[] pcBuffer; + return result; +} + +std::string CWIN32Util::GetResInfoString() +{ + DEVMODE devmode; + ZeroMemory(&devmode, sizeof(devmode)); + devmode.dmSize = sizeof(devmode); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode); + return StringUtils::Format("Desktop Resolution: %dx%d %dBit at %dHz",devmode.dmPelsWidth,devmode.dmPelsHeight,devmode.dmBitsPerPel,devmode.dmDisplayFrequency); +} + +int CWIN32Util::GetDesktopColorDepth() +{ + DEVMODE devmode; + ZeroMemory(&devmode, sizeof(devmode)); + devmode.dmSize = sizeof(devmode); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode); + return (int)devmode.dmBitsPerPel; +} + +std::string CWIN32Util::GetSpecialFolder(int csidl) +{ + std::string strProfilePath; + static const int bufSize = MAX_PATH; + WCHAR* buf = new WCHAR[bufSize]; + + if(SUCCEEDED(SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, buf))) + { + buf[bufSize-1] = 0; + g_charsetConverter.wToUTF8(buf, strProfilePath); + strProfilePath = UncToSmb(strProfilePath); + } + else + strProfilePath = ""; + + delete[] buf; + return strProfilePath; +} + +std::string CWIN32Util::GetSystemPath() +{ + return GetSpecialFolder(CSIDL_SYSTEM); +} + +std::string CWIN32Util::GetProfilePath() +{ + std::string strProfilePath; + std::string strHomePath = CUtil::GetHomePath(); + + if(g_application.PlatformDirectoriesEnabled()) + strProfilePath = URIUtils::AddFileToFolder(GetSpecialFolder(CSIDL_APPDATA|CSIDL_FLAG_CREATE), "Kodi"); + else + strProfilePath = URIUtils::AddFileToFolder(strHomePath , "portable_data"); + + if (strProfilePath.length() == 0) + strProfilePath = strHomePath; + + URIUtils::AddSlashAtEnd(strProfilePath); + + return strProfilePath; +} + +std::string CWIN32Util::UncToSmb(const std::string &strPath) +{ + std::string strRetPath(strPath); + if(StringUtils::StartsWith(strRetPath, "\\\\")) + { + strRetPath = "smb:" + strPath; + StringUtils::Replace(strRetPath, '\\', '/'); + } + return strRetPath; +} + +std::string CWIN32Util::SmbToUnc(const std::string &strPath) +{ + std::string strRetPath(strPath); + if(StringUtils::StartsWithNoCase(strRetPath, "smb://")) + { + StringUtils::Replace(strRetPath, "smb://", "\\\\"); + StringUtils::Replace(strRetPath, '/', '\\'); + } + return strRetPath; +} + +bool CWIN32Util::AddExtraLongPathPrefix(std::wstring& path) +{ + const wchar_t* const str = path.c_str(); + if (path.length() < 4 || str[0] != L'\\' || str[1] != L'\\' || str[3] != L'\\' || str[2] != L'?') + { + path.insert(0, L"\\\\?\\"); + return true; + } + return false; +} + +bool CWIN32Util::RemoveExtraLongPathPrefix(std::wstring& path) +{ + const wchar_t* const str = path.c_str(); + if (path.length() >= 4 && str[0] == L'\\' && str[1] == L'\\' && str[3] == L'\\' && str[2] == L'?') + { + path.erase(0, 4); + return true; + } + return false; +} + +std::wstring CWIN32Util::ConvertPathToWin32Form(const std::string& pathUtf8) +{ + std::wstring result; + if (pathUtf8.empty()) + return result; + + bool convertResult; + + if (pathUtf8.compare(0, 2, "\\\\", 2) != 0) // pathUtf8 don't start from "\\" + { // assume local file path in form 'C:\Folder\File.ext' + std::string formedPath("\\\\?\\"); // insert "\\?\" prefix + formedPath += URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(pathUtf8, '\\'), '\\'); // fix duplicated and forward slashes, resolve relative path + convertResult = g_charsetConverter.utf8ToW(formedPath, result, false, false, true); + } + else if (pathUtf8.compare(0, 8, "\\\\?\\UNC\\", 8) == 0) // pathUtf8 starts from "\\?\UNC\" + { + std::string formedPath("\\\\?\\UNC"); // start from "\\?\UNC" prefix + formedPath += URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(pathUtf8.substr(7), '\\'), '\\'); // fix duplicated and forward slashes, resolve relative path, don't touch "\\?\UNC" prefix, + convertResult = g_charsetConverter.utf8ToW(formedPath, result, false, false, true); + } + else if (pathUtf8.compare(0, 4, "\\\\?\\", 4) == 0) // pathUtf8 starts from "\\?\", but it's not UNC path + { + std::string formedPath("\\\\?"); // start from "\\?" prefix + formedPath += URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(pathUtf8.substr(3), '\\'), '\\'); // fix duplicated and forward slashes, resolve relative path, don't touch "\\?" prefix, + convertResult = g_charsetConverter.utf8ToW(formedPath, result, false, false, true); + } + else // pathUtf8 starts from "\\", but not from "\\?\UNC\" + { // assume UNC path in form '\\server\share\folder\file.ext' + std::string formedPath("\\\\?\\UNC"); // append "\\?\UNC" prefix + formedPath += URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(pathUtf8), '\\'); // fix duplicated and forward slashes, resolve relative path, transform "\\" prefix to single "\" + convertResult = g_charsetConverter.utf8ToW(formedPath, result, false, false, true); + } + + if (!convertResult) + { + CLog::Log(LOGERROR, "Error converting path \"%s\" to Win32 wide string!", pathUtf8.c_str()); + return L""; + } + + return result; +} + +std::wstring CWIN32Util::ConvertPathToWin32Form(const CURL& url) +{ + assert(url.GetProtocol().empty() || url.IsProtocol("smb")); + + if (url.GetFileName().empty()) + return std::wstring(); // empty string + + if (url.GetProtocol().empty()) + { + std::wstring result; + if (g_charsetConverter.utf8ToW("\\\\?\\" + + URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(url.GetFileName(), '\\'), '\\'), result, false, false, true)) + return result; + } + else if (url.IsProtocol("smb")) + { + if (url.GetHostName().empty()) + return std::wstring(); // empty string + + std::wstring result; + if (g_charsetConverter.utf8ToW("\\\\?\\UNC\\" + + URIUtils::CanonicalizePath(URIUtils::FixSlashesAndDups(url.GetHostName() + '\\' + url.GetFileName(), '\\'), '\\'), + result, false, false, true)) + return result; + } + else + return std::wstring(); // unsupported protocol, return empty string + + CLog::Log(LOGERROR, "%s: Error converting path \"%s\" to Win32 form", __FUNCTION__, url.Get().c_str()); + return std::wstring(); // empty string +} + +__time64_t CWIN32Util::fileTimeToTimeT(const FILETIME& ftimeft) +{ + if (!ftimeft.dwHighDateTime && !ftimeft.dwLowDateTime) + return 0; + + return fileTimeToTimeT((__int64(ftimeft.dwHighDateTime) << 32) + __int64(ftimeft.dwLowDateTime)); +} + +__time64_t CWIN32Util::fileTimeToTimeT(const LARGE_INTEGER& ftimeli) +{ + if (ftimeli.QuadPart == 0) + return 0; + + return fileTimeToTimeT(__int64(ftimeli.QuadPart)); +} + + +HRESULT CWIN32Util::ToggleTray(const char cDriveLetter) +{ + BOOL bRet= FALSE; + DWORD dwReq = 0; + char cDL = cDriveLetter; + if( !cDL ) + { + std::string dvdDevice = g_mediaManager.TranslateDevicePath(""); + if(dvdDevice == "") + return S_FALSE; + cDL = dvdDevice[0]; + } + + std::string strVolFormat = StringUtils::Format("\\\\.\\%c:", cDL); + HANDLE hDrive= CreateFile( strVolFormat.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + std::string strRootFormat = StringUtils::Format("%c:\\", cDL); + if( ( hDrive != INVALID_HANDLE_VALUE || GetLastError() == NO_ERROR) && + ( GetDriveType( strRootFormat.c_str() ) == DRIVE_CDROM ) ) + { + DWORD dwDummy; + dwReq = (GetDriveStatus(strVolFormat, true) == 1) ? IOCTL_STORAGE_LOAD_MEDIA : IOCTL_STORAGE_EJECT_MEDIA; + bRet = DeviceIoControl( hDrive, dwReq, NULL, 0, NULL, 0, &dwDummy, NULL); + } + // Windows doesn't seem to send always DBT_DEVICEREMOVECOMPLETE + // unmount it here too as it won't hurt + if(dwReq == IOCTL_STORAGE_EJECT_MEDIA && bRet == 1) + { + strRootFormat = StringUtils::Format("%c:", cDL); + CMediaSource share; + share.strPath = strRootFormat; + share.strName = share.strPath; + g_mediaManager.RemoveAutoSource(share); + } + CloseHandle(hDrive); + return bRet? S_OK : S_FALSE; +} + +HRESULT CWIN32Util::EjectTray(const char cDriveLetter) +{ + char cDL = cDriveLetter; + if( !cDL ) + { + std::string dvdDevice = g_mediaManager.TranslateDevicePath(""); + if(dvdDevice.empty()) + return S_FALSE; + cDL = dvdDevice[0]; + } + + std::string strVolFormat = StringUtils::Format("\\\\.\\%c:", cDL); + + if(GetDriveStatus(strVolFormat, true) != 1) + return ToggleTray(cDL); + else + return S_OK; +} + +HRESULT CWIN32Util::CloseTray(const char cDriveLetter) +{ + char cDL = cDriveLetter; + if( !cDL ) + { + std::string dvdDevice = g_mediaManager.TranslateDevicePath(""); + if(dvdDevice.empty()) + return S_FALSE; + cDL = dvdDevice[0]; + } + + std::string strVolFormat = StringUtils::Format( "\\\\.\\%c:", cDL); + + if(GetDriveStatus(strVolFormat, true) == 1) + return ToggleTray(cDL); + else + return S_OK; +} + +// safe removal of USB drives: +// http://www.codeproject.com/KB/system/RemoveDriveByLetter.aspx +// http://www.techtalkz.com/microsoft-device-drivers/250734-remove-usb-device-c-3.html + +DEVINST CWIN32Util::GetDrivesDevInstByDiskNumber(long DiskNumber) +{ + + GUID* guid = (GUID*)(void*)&GUID_DEVINTERFACE_DISK; + + // Get device interface info set handle for all devices attached to system + HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (hDevInfo == INVALID_HANDLE_VALUE) + return 0; + + // Retrieve a context structure for a device interface of a device + // information set. + DWORD dwIndex = 0; + SP_DEVICE_INTERFACE_DATA devInterfaceData = {0}; + devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + BOOL bRet = FALSE; + + PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd; + SP_DEVICE_INTERFACE_DATA spdid; + SP_DEVINFO_DATA spdd; + DWORD dwSize; + + spdid.cbSize = sizeof(spdid); + + while ( true ) + { + bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &devInterfaceData); + if (!bRet) + break; + + SetupDiEnumInterfaceDevice(hDevInfo, NULL, guid, dwIndex, &spdid); + + dwSize = 0; + SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); + + if ( dwSize ) + { + pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize); + if ( pspdidd == NULL ) + continue; + + pspdidd->cbSize = sizeof(*pspdidd); + ZeroMemory((PVOID)&spdd, sizeof(spdd)); + spdd.cbSize = sizeof(spdd); + + long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, + pspdidd, dwSize, &dwSize, &spdd); + if ( res ) + { + HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if ( hDrive != INVALID_HANDLE_VALUE ) + { + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned = 0; + res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); + if ( res ) + { + if ( DiskNumber == (long)sdn.DeviceNumber ) + { + CloseHandle(hDrive); + SetupDiDestroyDeviceInfoList(hDevInfo); + return spdd.DevInst; + } + } + CloseHandle(hDrive); + } + } + HeapFree(GetProcessHeap(), 0, pspdidd); + } + dwIndex++; + } + SetupDiDestroyDeviceInfoList(hDevInfo); + return 0; +} + +bool CWIN32Util::EjectDrive(const char cDriveLetter) +{ + if( !cDriveLetter ) + return false; + + std::string strVolFormat = StringUtils::Format("\\\\.\\%c:", cDriveLetter); + + long DiskNumber = -1; + + HANDLE hVolume = CreateFile(strVolFormat.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (hVolume == INVALID_HANDLE_VALUE) + return false; + + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned = 0; + long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER,NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); + CloseHandle(hVolume); + if ( res ) + DiskNumber = sdn.DeviceNumber; + else + return false; + + DEVINST DevInst = GetDrivesDevInstByDiskNumber(DiskNumber); + + if ( DevInst == 0 ) + return false; + + ULONG Status = 0; + ULONG ProblemNumber = 0; + PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown; + char VetoName[MAX_PATH]; + bool bSuccess = false; + + CM_Get_Parent(&DevInst, DevInst, 0); // disk's parent, e.g. the USB bridge, the SATA controller.... + CM_Get_DevNode_Status(&Status, &ProblemNumber, DevInst, 0); + + for(int i=0;i<3;i++) + { + res = CM_Request_Device_Eject(DevInst, &VetoType, VetoName, MAX_PATH, 0); + bSuccess = (res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown); + if ( bSuccess ) + break; + } + + return bSuccess; +} + +#ifdef HAS_GL +void CWIN32Util::CheckGLVersion() +{ + if(CWIN32Util::HasGLDefaultDrivers()) + { + MessageBox(NULL, "MS default OpenGL drivers detected. Please get OpenGL drivers from your video card vendor", "XBMC: Fatal Error", MB_OK|MB_ICONERROR); + exit(1); + } + + if(!CWIN32Util::HasReqGLVersion()) + { + if(MessageBox(NULL, "Your OpenGL version doesn't meet the XBMC requirements", "XBMC: Warning", MB_OKCANCEL|MB_ICONWARNING) == IDCANCEL) + { + exit(1); + } + } +} + +bool CWIN32Util::HasGLDefaultDrivers() +{ + unsigned int a=0,b=0; + + std::string strVendor = g_Windowing.GetRenderVendor(); + g_Windowing.GetRenderVersion(a, b); + + if(strVendor.find("Microsoft")!=strVendor.npos && a==1 && b==1) + return true; + else + return false; +} + +bool CWIN32Util::HasReqGLVersion() +{ + unsigned int a=0,b=0; + + g_Windowing.GetRenderVersion(a, b); + if((a>=2) || (a == 1 && b >= 3)) + return true; + else + return false; +} +#endif + +BOOL CWIN32Util::IsCurrentUserLocalAdministrator() +{ + BOOL b; + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + PSID AdministratorsGroup; + b = AllocateAndInitializeSid( + &NtAuthority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &AdministratorsGroup); + if(b) + { + if (!CheckTokenMembership( NULL, AdministratorsGroup, &b)) + { + b = FALSE; + } + FreeSid(AdministratorsGroup); + } + + return(b); +} + +void CWIN32Util::GetDrivesByType(VECSOURCES &localDrives, Drive_Types eDriveType, bool bonlywithmedia) +{ + WCHAR* pcBuffer= NULL; + DWORD dwStrLength= GetLogicalDriveStringsW( 0, pcBuffer ); + if( dwStrLength != 0 ) + { + CMediaSource share; + + dwStrLength+= 1; + pcBuffer= new WCHAR [dwStrLength]; + GetLogicalDriveStringsW( dwStrLength, pcBuffer ); + + int iPos= 0; + WCHAR cVolumeName[100]; + do{ + int nResult = 0; + cVolumeName[0]= L'\0'; + + std::wstring strWdrive = pcBuffer + iPos; + + UINT uDriveType= GetDriveTypeW( strWdrive.c_str() ); + // don't use GetVolumeInformation on fdd's as the floppy controller may be enabled in Bios but + // no floppy HW is attached which causes huge delays. + if(strWdrive.size() >= 2 && strWdrive.substr(0,2) != L"A:" && strWdrive.substr(0,2) != L"B:") + nResult= GetVolumeInformationW( strWdrive.c_str() , cVolumeName, 100, 0, 0, 0, NULL, 25); + if(nResult == 0 && bonlywithmedia) + { + iPos += (wcslen( pcBuffer + iPos) + 1 ); + continue; + } + + // usb hard drives are reported as DRIVE_FIXED and won't be returned by queries with REMOVABLE_DRIVES set + // so test for usb hard drives + /*if(uDriveType == DRIVE_FIXED) + { + if(IsUsbDevice(strWdrive)) + uDriveType = DRIVE_REMOVABLE; + }*/ + + share.strPath= share.strName= ""; + + bool bUseDCD= false; + if( uDriveType > DRIVE_UNKNOWN && + (( eDriveType == ALL_DRIVES && (uDriveType == DRIVE_FIXED || uDriveType == DRIVE_REMOTE || uDriveType == DRIVE_CDROM || uDriveType == DRIVE_REMOVABLE )) || + ( eDriveType == LOCAL_DRIVES && (uDriveType == DRIVE_FIXED || uDriveType == DRIVE_REMOTE)) || + ( eDriveType == REMOVABLE_DRIVES && ( uDriveType == DRIVE_REMOVABLE )) || + ( eDriveType == DVD_DRIVES && ( uDriveType == DRIVE_CDROM )))) + { + //share.strPath = strWdrive; + g_charsetConverter.wToUTF8(strWdrive, share.strPath); + if( cVolumeName[0] != L'\0' ) + g_charsetConverter.wToUTF8(cVolumeName, share.strName); + if( uDriveType == DRIVE_CDROM && nResult) + { + // Has to be the same as auto mounted devices + share.strStatus = share.strName; + share.strName = share.strPath; + share.m_iDriveType= CMediaSource::SOURCE_TYPE_LOCAL; + bUseDCD= true; + } + else + { + // Lets show it, like Windows explorer do... + //! @todo Sorting should depend on driver letter + switch(uDriveType) + { + case DRIVE_CDROM: + share.strName = StringUtils::Format( "%s (%s)", share.strPath.c_str(), g_localizeStrings.Get(218).c_str()); + break; + case DRIVE_REMOVABLE: + if(share.strName.empty()) + share.strName = StringUtils::Format( "%s (%s)", g_localizeStrings.Get(437).c_str(), share.strPath.c_str()); + break; + case DRIVE_UNKNOWN: + share.strName = StringUtils::Format( "%s (%s)", share.strPath.c_str(), g_localizeStrings.Get(13205).c_str()); + break; + default: + if(share.strName.empty()) + share.strName = share.strPath; + else + share.strName = StringUtils::Format( "%s (%s)", share.strPath.c_str(), share.strName.c_str()); + break; + } + } + StringUtils::Replace(share.strName, ":\\", ":"); + StringUtils::Replace(share.strPath, ":\\", ":"); + share.m_ignore= true; + if( !bUseDCD ) + { + share.m_iDriveType= ( + ( uDriveType == DRIVE_FIXED ) ? CMediaSource::SOURCE_TYPE_LOCAL : + ( uDriveType == DRIVE_REMOTE ) ? CMediaSource::SOURCE_TYPE_REMOTE : + ( uDriveType == DRIVE_CDROM ) ? CMediaSource::SOURCE_TYPE_DVD : + ( uDriveType == DRIVE_REMOVABLE ) ? CMediaSource::SOURCE_TYPE_REMOVABLE : + CMediaSource::SOURCE_TYPE_UNKNOWN ); + } + + AddOrReplace(localDrives, share); + } + iPos += (wcslen( pcBuffer + iPos) + 1 ); + } while( wcslen( pcBuffer + iPos ) > 0 ); + delete[] pcBuffer; + } +} + +std::string CWIN32Util::GetFirstOpticalDrive() +{ + VECSOURCES vShare; + std::string strdevice = "\\\\.\\"; + CWIN32Util::GetDrivesByType(vShare, DVD_DRIVES); + if(!vShare.empty()) + return strdevice.append(vShare.front().strPath); + else + return ""; +} + +extern "C" +{ + FILE *fopen_utf8(const char *_Filename, const char *_Mode) + { + std::string modetmp = _Mode; + std::wstring wfilename, wmode(modetmp.begin(), modetmp.end()); + g_charsetConverter.utf8ToW(_Filename, wfilename, false); + return _wfopen(wfilename.c_str(), wmode.c_str()); + } +} + +extern "C" { + /* + * Ported from NetBSD to Windows by Ron Koenderink, 2007 + */ + + /* $NetBSD: strptime.c,v 1.25 2005/11/29 03:12:00 christos Exp $ */ + + /*- + * Copyright (c) 1997, 1998, 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code was contributed to The NetBSD Foundation by Klaus Klein. + * Heavily optimised by David Laight + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + #if !defined(TARGET_WINDOWS) + #include + #endif + + #if defined(LIBC_SCCS) && !defined(lint) + __RCSID("$NetBSD: strptime.c,v 1.25 2005/11/29 03:12:00 christos Exp $"); + #endif + + #if !defined(TARGET_WINDOWS) + #include "namespace.h" + #include + #else + typedef unsigned char u_char; + typedef unsigned int uint; + #endif + #include + #include + #include + #include + #if !defined(TARGET_WINDOWS) + #include + #endif + + #ifdef __weak_alias + __weak_alias(strptime,_strptime) + #endif + + #if !defined(TARGET_WINDOWS) + #define _ctloc(x) (_CurrentTimeLocale->x) + #else + #define _ctloc(x) (x) + const char *abday[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" + }; + const char *day[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }; + const char *abmon[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + const char *mon[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }; + const char *am_pm[] = { + "AM", "PM" + }; + char *d_t_fmt = "%a %Ef %T %Y"; + char *t_fmt_ampm = "%I:%M:%S %p"; + char *t_fmt = "%H:%M:%S"; + char *d_fmt = "%m/%d/%y"; + #define TM_YEAR_BASE 1900 + #define __UNCONST(x) ((char *)(((const char *)(x) - (const char *)0) + (char *)0)) + + #endif + /* + * We do not implement alternate representations. However, we always + * check whether a given modifier is allowed for a certain conversion. + */ + #define ALT_E 0x01 + #define ALT_O 0x02 + #define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; } + + + static const u_char *conv_num(const unsigned char *, int *, uint, uint); + static const u_char *find_string(const u_char *, int *, const char * const *, + const char * const *, int); + + + char * + strptime(const char *buf, const char *fmt, struct tm *tm) + { + unsigned char c; + const unsigned char *bp; + int alt_format, i, split_year = 0; + const char *new_fmt; + + bp = (const u_char *)buf; + + while (bp != NULL && (c = *fmt++) != '\0') { + /* Clear `alternate' modifier prior to new conversion. */ + alt_format = 0; + i = 0; + + /* Eat up white-space. */ + if (isspace(c)) { + while (isspace(*bp)) + bp++; + continue; + } + + if (c != '%') + goto literal; + + + again: switch (c = *fmt++) { + case '%': /* "%%" is converted to "%". */ + literal: + if (c != *bp++) + return NULL; + LEGAL_ALT(0); + continue; + + /* + * "Alternative" modifiers. Just set the appropriate flag + * and start over again. + */ + case 'E': /* "%E?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_E; + goto again; + + case 'O': /* "%O?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_O; + goto again; + + /* + * "Complex" conversion rules, implemented through recursion. + */ + case 'c': /* Date and time, using the locale's format. */ + new_fmt = _ctloc(d_t_fmt); + goto recurse; + + case 'D': /* The date as "%m/%d/%y". */ + new_fmt = "%m/%d/%y"; + LEGAL_ALT(0); + goto recurse; + + case 'R': /* The time as "%H:%M". */ + new_fmt = "%H:%M"; + LEGAL_ALT(0); + goto recurse; + + case 'r': /* The time in 12-hour clock representation. */ + new_fmt =_ctloc(t_fmt_ampm); + LEGAL_ALT(0); + goto recurse; + + case 'T': /* The time as "%H:%M:%S". */ + new_fmt = "%H:%M:%S"; + LEGAL_ALT(0); + goto recurse; + + case 'X': /* The time, using the locale's format. */ + new_fmt =_ctloc(t_fmt); + goto recurse; + + case 'x': /* The date, using the locale's format. */ + new_fmt =_ctloc(d_fmt); + recurse: + bp = (const u_char *)strptime((const char *)bp, + new_fmt, tm); + LEGAL_ALT(ALT_E); + continue; + + /* + * "Elementary" conversion rules. + */ + case 'A': /* The day of week, using the locale's form. */ + case 'a': + bp = find_string(bp, &tm->tm_wday, _ctloc(day), + _ctloc(abday), 7); + LEGAL_ALT(0); + continue; + + case 'B': /* The month, using the locale's form. */ + case 'b': + case 'h': + bp = find_string(bp, &tm->tm_mon, _ctloc(mon), + _ctloc(abmon), 12); + LEGAL_ALT(0); + continue; + + case 'C': /* The century number. */ + i = 20; + bp = conv_num(bp, &i, 0, 99); + + i = i * 100 - TM_YEAR_BASE; + if (split_year) + i += tm->tm_year % 100; + split_year = 1; + tm->tm_year = i; + LEGAL_ALT(ALT_E); + continue; + + case 'd': /* The day of month. */ + case 'e': + bp = conv_num(bp, &tm->tm_mday, 1, 31); + LEGAL_ALT(ALT_O); + continue; + + case 'k': /* The hour (24-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'H': + bp = conv_num(bp, &tm->tm_hour, 0, 23); + LEGAL_ALT(ALT_O); + continue; + + case 'l': /* The hour (12-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'I': + bp = conv_num(bp, &tm->tm_hour, 1, 12); + if (tm->tm_hour == 12) + tm->tm_hour = 0; + LEGAL_ALT(ALT_O); + continue; + + case 'j': /* The day of year. */ + i = 1; + bp = conv_num(bp, &i, 1, 366); + tm->tm_yday = i - 1; + LEGAL_ALT(0); + continue; + + case 'M': /* The minute. */ + bp = conv_num(bp, &tm->tm_min, 0, 59); + LEGAL_ALT(ALT_O); + continue; + + case 'm': /* The month. */ + i = 1; + bp = conv_num(bp, &i, 1, 12); + tm->tm_mon = i - 1; + LEGAL_ALT(ALT_O); + continue; + + case 'p': /* The locale's equivalent of AM/PM. */ + bp = find_string(bp, &i, _ctloc(am_pm), NULL, 2); + if (tm->tm_hour > 11) + return NULL; + tm->tm_hour += i * 12; + LEGAL_ALT(0); + continue; + + case 'S': /* The seconds. */ + bp = conv_num(bp, &tm->tm_sec, 0, 61); + LEGAL_ALT(ALT_O); + continue; + + case 'U': /* The week of year, beginning on sunday. */ + case 'W': /* The week of year, beginning on monday. */ + /* + * XXX This is bogus, as we can not assume any valid + * information present in the tm structure at this + * point to calculate a real value, so just check the + * range for now. + */ + bp = conv_num(bp, &i, 0, 53); + LEGAL_ALT(ALT_O); + continue; + + case 'w': /* The day of week, beginning on sunday. */ + bp = conv_num(bp, &tm->tm_wday, 0, 6); + LEGAL_ALT(ALT_O); + continue; + + case 'Y': /* The year. */ + i = TM_YEAR_BASE; /* just for data sanity... */ + bp = conv_num(bp, &i, 0, 9999); + tm->tm_year = i - TM_YEAR_BASE; + LEGAL_ALT(ALT_E); + continue; + + case 'y': /* The year within 100 years of the epoch. */ + /* LEGAL_ALT(ALT_E | ALT_O); */ + bp = conv_num(bp, &i, 0, 99); + + if (split_year) + /* preserve century */ + i += (tm->tm_year / 100) * 100; + else { + split_year = 1; + if (i <= 68) + i = i + 2000 - TM_YEAR_BASE; + else + i = i + 1900 - TM_YEAR_BASE; + } + tm->tm_year = i; + continue; + + /* + * Miscellaneous conversions. + */ + case 'n': /* Any kind of white-space. */ + case 't': + while (isspace(*bp)) + bp++; + LEGAL_ALT(0); + continue; + + + default: /* Unknown/unsupported conversion. */ + return NULL; + } + } + + return __UNCONST(bp); + } + + + static const u_char * + conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim) + { + uint result = 0; + unsigned char ch; + + /* The limit also determines the number of valid digits. */ + uint rulim = ulim; + + ch = *buf; + if (ch < '0' || ch > '9') + return NULL; + + do { + result *= 10; + result += ch - '0'; + rulim /= 10; + ch = *++buf; + } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9'); + + if (result < llim || result > ulim) + return NULL; + + *dest = result; + return buf; + } + + static const u_char * + find_string(const u_char *bp, int *tgt, const char * const *n1, + const char * const *n2, int c) + { + int i; + unsigned int len; + + /* check full name - then abbreviated ones */ + for (; n1 != NULL; n1 = n2, n2 = NULL) { + for (i = 0; i < c; i++, n1++) { + len = strlen(*n1); + if (strnicmp(*n1, (const char *)bp, len) == 0) { + *tgt = i; + return bp + len; + } + } + } + + /* Nothing matched */ + return NULL; + } +} + + +LONG CWIN32Util::UtilRegGetValue( const HKEY hKey, const char *const pcKey, DWORD *const pdwType, char **const ppcBuffer, DWORD *const pdwSizeBuff, const DWORD dwSizeAdd ) +{ + DWORD dwSize; + LONG lRet= RegQueryValueEx(hKey, pcKey, NULL, pdwType, NULL, &dwSize ); + if (lRet == ERROR_SUCCESS) + { + if (ppcBuffer) + { + char *pcValue=*ppcBuffer, *pcValueTmp; + if (!pcValue || !pdwSizeBuff || dwSize +dwSizeAdd > *pdwSizeBuff) { + pcValueTmp = (char*)realloc(pcValue, dwSize +dwSizeAdd); + if(pcValueTmp != NULL) + { + pcValue = pcValueTmp; + } + } + lRet= RegQueryValueEx(hKey,pcKey,NULL,NULL,(LPBYTE)pcValue,&dwSize); + + if ( lRet == ERROR_SUCCESS || *ppcBuffer ) *ppcBuffer= pcValue; + else free( pcValue ); + } + if (pdwSizeBuff) *pdwSizeBuff= dwSize +dwSizeAdd; + } + return lRet; +} + +bool CWIN32Util::UtilRegOpenKeyEx( const HKEY hKeyParent, const char *const pcKey, const REGSAM rsAccessRights, HKEY *hKey, const bool bReadX64 ) +{ + const REGSAM rsAccessRightsTmp= ( CSysInfo::GetKernelBitness() == 64 ? rsAccessRights | ( bReadX64 ? KEY_WOW64_64KEY : KEY_WOW64_32KEY ) : rsAccessRights ); + bool bRet= ( ERROR_SUCCESS == RegOpenKeyEx(hKeyParent, pcKey, 0, rsAccessRightsTmp, hKey)); + return bRet; +} + +// Retrieve the filename of the process that currently has the focus. +// Typically this will be some process using the system tray grabbing +// the focus and causing XBMC to minimise. Logging the offending +// process name can help the user fix the problem. +bool CWIN32Util::GetFocussedProcess(std::string &strProcessFile) +{ + strProcessFile = ""; + + // Get the window that has the focus + HWND hfocus = GetForegroundWindow(); + if (!hfocus) + return false; + + // Get the process ID from the window handle + DWORD pid = 0; + GetWindowThreadProcessId(hfocus, &pid); + + // Use OpenProcess to get the process handle from the process ID + HANDLE hproc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid); + if (!hproc) + return false; + + // Load QueryFullProcessImageName dynamically because it isn't available + // in all versions of Windows. + char procfile[MAX_PATH+1]; + DWORD procfilelen = MAX_PATH; + + HINSTANCE hkernel32 = LoadLibrary("kernel32.dll"); + if (hkernel32) + { + DWORD (WINAPI *pQueryFullProcessImageNameA)(HANDLE,DWORD,LPTSTR,PDWORD); + pQueryFullProcessImageNameA = (DWORD (WINAPI *)(HANDLE,DWORD,LPTSTR,PDWORD)) GetProcAddress(hkernel32, "QueryFullProcessImageNameA"); + if (pQueryFullProcessImageNameA) + if (pQueryFullProcessImageNameA(hproc, 0, procfile, &procfilelen)) + strProcessFile = procfile; + FreeLibrary(hkernel32); + } + + // If QueryFullProcessImageName failed fall back to GetModuleFileNameEx. + // Note this does not work across x86-x64 boundaries. + if (strProcessFile == "") + { + HINSTANCE hpsapi = LoadLibrary("psapi.dll"); + if (hpsapi) + { + DWORD (WINAPI *pGetModuleFileNameExA)(HANDLE,HMODULE,LPTSTR,DWORD); + pGetModuleFileNameExA = (DWORD (WINAPI*)(HANDLE,HMODULE,LPTSTR,DWORD)) GetProcAddress(hpsapi, "GetModuleFileNameExA"); + if (pGetModuleFileNameExA) + if (pGetModuleFileNameExA(hproc, NULL, procfile, MAX_PATH)) + strProcessFile = procfile; + FreeLibrary(hpsapi); + } + } + + CloseHandle(hproc); + + return true; +} + +// Adjust the src rectangle so that the dst is always contained in the target rectangle. +void CWIN32Util::CropSource(CRect& src, CRect& dst, CRect target, UINT rotation /* = 0 */) +{ + float s_width = src.Width(), s_height = src.Height(); + float d_width = dst.Width(), d_height = dst.Height(); + + if (dst.x1 < target.x1) + { + switch (rotation) + { + case 90: + src.y1 -= (dst.x1 - target.x1) * s_height / d_width; + break; + case 180: + src.x2 += (dst.x1 - target.x1) * s_width / d_width; + break; + case 270: + src.y2 += (dst.x1 - target.x1) * s_height / d_width; + break; + default: + src.x1 -= (dst.x1 - target.x1) * s_width / d_width; + break; + } + dst.x1 = target.x1; + } + if(dst.y1 < target.y1) + { + switch (rotation) + { + case 90: + src.x1 -= (dst.y1 - target.y1) * s_width / d_height; + break; + case 180: + src.y2 += (dst.y1 - target.y1) * s_height / d_height; + break; + case 270: + src.x2 += (dst.y1 - target.y1) * s_width / d_height; + break; + default: + src.y1 -= (dst.y1 - target.y1) * s_height / d_height; + break; + } + dst.y1 = target.y1; + } + if(dst.x2 > target.x2) + { + switch (rotation) + { + case 90: + src.y2 -= (dst.x2 - target.x2) * s_height / d_width; + break; + case 180: + src.x1 += (dst.x2 - target.x2) * s_width / d_width; + break; + case 270: + src.y1 += (dst.x2 - target.x2) * s_height / d_width; + break; + default: + src.x2 -= (dst.x2 - target.x2) * s_width / d_width; + break; + } + dst.x2 = target.x2; + } + if(dst.y2 > target.y2) + { + switch (rotation) + { + case 90: + src.x2 -= (dst.y2 - target.y2) * s_width / d_height; + break; + case 180: + src.y1 += (dst.y2 - target.y2) * s_height / d_height; + break; + case 270: + src.x1 += (dst.y2 - target.y2) * s_width / d_height; + break; + default: + src.y2 -= (dst.y2 - target.y2) * s_height / d_height; + break; + } + dst.y2 = target.y2; + } + // Callers expect integer coordinates. + src.x1 = floor(src.x1); + src.y1 = floor(src.y1); + src.x2 = ceil(src.x2); + src.y2 = ceil(src.y2); + dst.x1 = floor(dst.x1); + dst.y1 = floor(dst.y1); + dst.x2 = ceil(dst.x2); + dst.y2 = ceil(dst.y2); +} + +void CWinIdleTimer::StartZero() +{ + if (!g_application.IsDPMSActive()) + SetThreadExecutionState(ES_SYSTEM_REQUIRED|ES_DISPLAY_REQUIRED); + CStopWatch::StartZero(); +} + +extern "C" +{ + /* case-independent string matching, similar to strstr but + * matching */ + char * strcasestr(const char* haystack, const char* needle) + { + int i; + int nlength = (int) strlen (needle); + int hlength = (int) strlen (haystack); + + if (nlength > hlength) return NULL; + if (hlength <= 0) return NULL; + if (nlength <= 0) return (char *)haystack; + /* hlength and nlength > 0, nlength <= hlength */ + for (i = 0; i <= (hlength - nlength); i++) + { + if (strncasecmp (haystack + i, needle, nlength) == 0) + { + return (char *)haystack + i; + } + } + /* substring not found */ + return NULL; + } +} + +// detect if a drive is a usb device +// code taken from http://banderlogi.blogspot.com/2011/06/enum-drive-letters-attached-for-usb.html + +bool CWIN32Util::IsUsbDevice(const std::wstring &strWdrive) +{ + if (strWdrive.size() < 2) + return false; + + std::wstring strWDevicePath = StringUtils::Format(L"\\\\.\\%s",strWdrive.substr(0, 2).c_str()); + + HANDLE deviceHandle = CreateFileW( + strWDevicePath.c_str(), + 0, // no access to the drive + FILE_SHARE_READ | // share mode + FILE_SHARE_WRITE, + NULL, // default security attributes + OPEN_EXISTING, // disposition + 0, // file attributes + NULL); // do not copy file attributes + + if(deviceHandle == INVALID_HANDLE_VALUE) + return false; + + // setup query + STORAGE_PROPERTY_QUERY query; + memset(&query, 0, sizeof(query)); + query.PropertyId = StorageDeviceProperty; + query.QueryType = PropertyStandardQuery; + + // issue query + DWORD bytes; + STORAGE_DEVICE_DESCRIPTOR devd; + STORAGE_BUS_TYPE busType = BusTypeUnknown; + + if (DeviceIoControl(deviceHandle, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, sizeof(query), + &devd, sizeof(devd), + &bytes, NULL)) + { + busType = devd.BusType; + } + + CloseHandle(deviceHandle); + + return BusTypeUsb == busType; + } + +std::string CWIN32Util::WUSysMsg(DWORD dwError) +{ + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + SS_DEFLANGID, szBuf, 511, NULL) ) + return StringUtils::Format("%s (0x%X)", szBuf, dwError); + else + return StringUtils::Format("Unknown error (0x%X)", dwError); +} + +bool CWIN32Util::SetThreadLocalLocale(bool enable /* = true */) +{ + const int param = enable ? _ENABLE_PER_THREAD_LOCALE : _DISABLE_PER_THREAD_LOCALE; + return CALL_IN_CRTS(_configthreadlocale, param) != -1; +} + diff --git a/KodiThreads/utils/win32/WIN32Util.h b/KodiThreads/utils/win32/WIN32Util.h new file mode 100644 index 0000000..23cbb59 --- /dev/null +++ b/KodiThreads/utils/win32/WIN32Util.h @@ -0,0 +1,109 @@ +#pragma once + +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "URL.h" +#include "Cfgmgr32.h" +#include "MediaSource.h" +#include "guilib/Geometry.h" +#include "powermanagement/PowerManager.h" +#include "utils/Stopwatch.h" + +enum Drive_Types +{ + ALL_DRIVES = 0, + LOCAL_DRIVES, + REMOVABLE_DRIVES, + DVD_DRIVES +}; + +#define BONJOUR_EVENT ( WM_USER + 0x100 ) // Message sent to the Window when a Bonjour event occurs. +#define BONJOUR_BROWSER_EVENT ( WM_USER + 0x110 ) + +class CURL; // forward declaration + +class CWIN32Util +{ +public: + CWIN32Util(void); + virtual ~CWIN32Util(void); + + static char FirstDriveFromMask (ULONG unitmask); + static int GetDriveStatus(const std::string &strPath, bool bStatusEx=false); + static bool PowerManagement(PowerState State); + static int BatteryLevel(); + static bool XBMCShellExecute(const std::string &strPath, bool bWaitForScriptExit=false); + static std::vector GetDiskUsage(); + static std::string GetResInfoString(); + static int GetDesktopColorDepth(); + static std::string GetSpecialFolder(int csidl); + static std::string GetSystemPath(); + static std::string GetProfilePath(); + static std::string UncToSmb(const std::string &strPath); + static std::string SmbToUnc(const std::string &strPath); + static bool AddExtraLongPathPrefix(std::wstring& path); + static bool RemoveExtraLongPathPrefix(std::wstring& path); + static std::wstring ConvertPathToWin32Form(const std::string& pathUtf8); + static std::wstring ConvertPathToWin32Form(const CURL& url); + static inline __time64_t fileTimeToTimeT(const __int64 ftimei64) + { + // FILETIME is 100-nanoseconds from 00:00:00 UTC 01 Jan 1601 + // __time64_t is seconds from 00:00:00 UTC 01 Jan 1970 + return (ftimei64 - 116444736000000000) / 10000000; + } + static __time64_t fileTimeToTimeT(const FILETIME& ftimeft); + static __time64_t fileTimeToTimeT(const LARGE_INTEGER& ftimeli); + static HRESULT ToggleTray(const char cDriveLetter='\0'); + static HRESULT EjectTray(const char cDriveLetter='\0'); + static HRESULT CloseTray(const char cDriveLetter='\0'); + static bool EjectDrive(const char cDriveLetter='\0'); +#ifdef HAS_GL + static void CheckGLVersion(); + static bool HasGLDefaultDrivers(); + static bool HasReqGLVersion(); +#endif + static BOOL IsCurrentUserLocalAdministrator(); + static void GetDrivesByType(VECSOURCES &localDrives, Drive_Types eDriveType=ALL_DRIVES, bool bonlywithmedia=false); + static std::string GetFirstOpticalDrive(); + + static LONG UtilRegGetValue( const HKEY hKey, const char *const pcKey, DWORD *const pdwType, char **const ppcBuffer, DWORD *const pdwSizeBuff, const DWORD dwSizeAdd ); + static bool UtilRegOpenKeyEx( const HKEY hKeyParent, const char *const pcKey, const REGSAM rsAccessRights, HKEY *hKey, const bool bReadX64= false ); + + static bool GetFocussedProcess(std::string &strProcessFile); + static void CropSource(CRect& src, CRect& dst, CRect target, UINT rotation = 0); + + static bool IsUsbDevice(const std::wstring &strWdrive); + + static std::string WUSysMsg(DWORD dwError); + + static bool SetThreadLocalLocale(bool enable = true); +private: + static DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber); +}; + + +class CWinIdleTimer : public CStopWatch +{ +public: + void StartZero(); +};