@@ -48,7 +48,7 @@ RtList: RtList.cpp ../utils/RtList.hpp ../modules/rtmempool.a | |||||
# valgrind ./RtList | # valgrind ./RtList | ||||
Utils: Utils.cpp | Utils: Utils.cpp | ||||
$(CXX) $^ $(BUILD_CXX_FLAGS) $(ANSI_CXX_FLAGS) -std=c++11 -Wzero-as-null-pointer-constant $(LINK_FLAGS) -ldl -o $@ | |||||
$(CXX) $^ $(BUILD_CXX_FLAGS) $(ANSI_CXX_FLAGS) -std=c++11 -Wzero-as-null-pointer-constant $(LINK_FLAGS) -ldl -lpthread -o $@ | |||||
# valgrind ./Utils | # valgrind ./Utils | ||||
# -------------------------------------------------------------- | # -------------------------------------------------------------- | ||||
@@ -19,7 +19,9 @@ | |||||
#include "CarlaLibUtils.hpp" | #include "CarlaLibUtils.hpp" | ||||
#include "CarlaJuceUtils.hpp" | #include "CarlaJuceUtils.hpp" | ||||
#include "CarlaMutex.hpp" | |||||
#include "CarlaString.hpp" | #include "CarlaString.hpp" | ||||
#include "CarlaThread.hpp" | |||||
#include <cassert> | #include <cassert> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
@@ -45,6 +47,41 @@ private: | |||||
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyLeakCheckedClass) | CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyLeakCheckedClass) | ||||
}; | }; | ||||
class MyThread : public CarlaThread | |||||
{ | |||||
public: | |||||
MyThread(CarlaMutex* const m) | |||||
: CarlaThread("myThread"), | |||||
fMu(m) | |||||
{ | |||||
} | |||||
protected: | |||||
void run() override | |||||
{ | |||||
carla_stderr("Thread started"); | |||||
carla_stderr("Thread lock"); | |||||
fMu->lock(); | |||||
carla_stderr("Thread sleeping"); | |||||
carla_sleep(3); | |||||
carla_stderr("Thread unlock"); | |||||
fMu->unlock(); | |||||
carla_stderr("Thread sleep-waiting for stop"); | |||||
while (! shouldExit()) | |||||
carla_sleep(1); | |||||
carla_stderr("Thread finished"); | |||||
} | |||||
private: | |||||
CarlaMutex* const fMu; | |||||
}; | |||||
int main() | int main() | ||||
{ | { | ||||
// misc functions | // misc functions | ||||
@@ -185,8 +222,44 @@ int main() | |||||
delete b; | delete b; | ||||
} | } | ||||
// Mutex | |||||
{ | |||||
CarlaMutex m; | |||||
assert(! m.wasTryLockCalled()); | |||||
assert(m.tryLock()); | |||||
assert(m.wasTryLockCalled()); | |||||
assert(! m.wasTryLockCalled()); // once | |||||
m.unlock(); | |||||
m.lock(); | |||||
assert(! m.wasTryLockCalled()); | |||||
{ CarlaMutex::ScopedUnlocker su(m); } | |||||
assert(! m.wasTryLockCalled()); | |||||
m.unlock(); | |||||
{ CarlaMutex::ScopedLocker sl(m); } | |||||
} | |||||
// String | // String | ||||
{ | { | ||||
CarlaString a, b(2), c("haha"), d((uint)0x999, true), e(0.7f), f(0.9), g('u'); | |||||
assert(g == "u"); | |||||
} | |||||
// Thread | |||||
{ | |||||
CarlaMutex m; | |||||
MyThread t(&m); | |||||
carla_stdout("Thread init started"); | |||||
t.start(); | |||||
carla_stdout("Thread init finished, lock waiting..."); | |||||
m.lock(); | |||||
carla_stdout("Thread lock wait done"); | |||||
m.unlock(); | |||||
t.stop(-1); | |||||
} | } | ||||
return 0; | return 0; | ||||
@@ -24,25 +24,25 @@ | |||||
// library related calls | // library related calls | ||||
/* | /* | ||||
* Open 'filename' library (must not be NULL). | |||||
* May return NULL, in which case "lib_error" has the error. | |||||
* Open 'filename' library (must not be null). | |||||
* May return null, in which case "lib_error" has the error. | |||||
*/ | */ | ||||
void* lib_open(const char* const filename); | void* lib_open(const char* const filename); | ||||
/* | /* | ||||
* Close a previously opened library (must not be NULL). | |||||
* Close a previously opened library (must not be null). | |||||
* If false is returned,"lib_error" has the error. | * If false is returned,"lib_error" has the error. | ||||
*/ | */ | ||||
bool lib_close(void* const lib); | bool lib_close(void* const lib); | ||||
/* | /* | ||||
* Get a library symbol (must not be NULL). | |||||
* May return NULL if the symbol is not found. | |||||
* Get a library symbol (must not be null). | |||||
* May return null if the symbol is not found. | |||||
*/ | */ | ||||
void* lib_symbol(void* const lib, const char* const symbol); | void* lib_symbol(void* const lib, const char* const symbol); | ||||
/* | /* | ||||
* Return the last operation error ('filename' must not be NULL). | |||||
* Return the last operation error ('filename' must not be null). | |||||
*/ | */ | ||||
const char* lib_error(const char* const filename); | const char* lib_error(const char* const filename); | ||||
@@ -28,54 +28,81 @@ | |||||
class CarlaMutex | class CarlaMutex | ||||
{ | { | ||||
public: | public: | ||||
CarlaMutex() | |||||
/* | |||||
* Constructor. | |||||
*/ | |||||
CarlaMutex() noexcept | |||||
: fTryLockWasCalled(false) | : fTryLockWasCalled(false) | ||||
{ | { | ||||
pthread_mutex_init(&fMutex, nullptr); | |||||
pthread_mutexattr_t atts; | |||||
pthread_mutexattr_init(&atts); | |||||
pthread_mutexattr_settype(&atts, PTHREAD_MUTEX_RECURSIVE); | |||||
pthread_mutex_init(&fMutex, &atts); | |||||
pthread_mutexattr_destroy (&atts); | |||||
} | } | ||||
~CarlaMutex() | |||||
/* | |||||
* Destructor. | |||||
*/ | |||||
~CarlaMutex() noexcept | |||||
{ | { | ||||
pthread_mutex_destroy(&fMutex); | pthread_mutex_destroy(&fMutex); | ||||
} | } | ||||
void lock() | |||||
/* | |||||
* Check if "tryLock()" was called before. | |||||
*/ | |||||
bool wasTryLockCalled() const noexcept | |||||
{ | |||||
const bool ret(fTryLockWasCalled); | |||||
fTryLockWasCalled = false; | |||||
return ret; | |||||
} | |||||
/* | |||||
* Lock the mutex. | |||||
*/ | |||||
void lock() const noexcept | |||||
{ | { | ||||
pthread_mutex_lock(&fMutex); | pthread_mutex_lock(&fMutex); | ||||
} | } | ||||
bool tryLock() | |||||
/* | |||||
* Try to lock the mutex. | |||||
* Returns true if successful. | |||||
*/ | |||||
bool tryLock() const noexcept | |||||
{ | { | ||||
fTryLockWasCalled = true; | fTryLockWasCalled = true; | ||||
return (pthread_mutex_trylock(&fMutex) == 0); | return (pthread_mutex_trylock(&fMutex) == 0); | ||||
} | } | ||||
void unlock(/*const bool resetTryLock*/) | |||||
/* | |||||
* Unlock the mutex, optionally resetting the tryLock check. | |||||
*/ | |||||
void unlock(const bool resetTryLock = false) const noexcept | |||||
{ | { | ||||
//if (resetTryLock) | |||||
// fTryLockWasCalled = false; | |||||
if (resetTryLock) | |||||
fTryLockWasCalled = false; | |||||
pthread_mutex_unlock(&fMutex); | pthread_mutex_unlock(&fMutex); | ||||
} | } | ||||
bool wasTryLockCalled() | |||||
{ | |||||
const bool ret(fTryLockWasCalled); | |||||
fTryLockWasCalled = false; | |||||
return ret; | |||||
} | |||||
/* | |||||
* Helper class to lock&unlock a mutex during a function scope. | |||||
*/ | |||||
class ScopedLocker | class ScopedLocker | ||||
{ | { | ||||
public: | public: | ||||
ScopedLocker(CarlaMutex& mutex) | |||||
ScopedLocker(CarlaMutex& mutex) noexcept | |||||
: fMutex(mutex) | : fMutex(mutex) | ||||
{ | { | ||||
fMutex.lock(); | fMutex.lock(); | ||||
} | } | ||||
~ScopedLocker() | |||||
~ScopedLocker() noexcept | |||||
{ | { | ||||
fMutex.unlock(); | fMutex.unlock(); | ||||
} | } | ||||
@@ -87,9 +114,33 @@ public: | |||||
CARLA_DECLARE_NON_COPY_CLASS(ScopedLocker) | CARLA_DECLARE_NON_COPY_CLASS(ScopedLocker) | ||||
}; | }; | ||||
/* | |||||
* Helper class to unlock&lock a mutex during a function scope. | |||||
*/ | |||||
class ScopedUnlocker | |||||
{ | |||||
public: | |||||
ScopedUnlocker(CarlaMutex& mutex) noexcept | |||||
: fMutex(mutex) | |||||
{ | |||||
fMutex.unlock(); | |||||
} | |||||
~ScopedUnlocker() noexcept | |||||
{ | |||||
fMutex.lock(); | |||||
} | |||||
private: | |||||
CarlaMutex& fMutex; | |||||
CARLA_PREVENT_HEAP_ALLOCATION | |||||
CARLA_DECLARE_NON_COPY_CLASS(ScopedUnlocker) | |||||
}; | |||||
private: | private: | ||||
pthread_mutex_t fMutex; | |||||
volatile bool fTryLockWasCalled; | |||||
mutable pthread_mutex_t fMutex; // The mutex | |||||
mutable volatile bool fTryLockWasCalled; // true if "tryLock()" was called at least once | |||||
CARLA_PREVENT_HEAP_ALLOCATION | CARLA_PREVENT_HEAP_ALLOCATION | ||||
CARLA_DECLARE_NON_COPY_CLASS(CarlaMutex) | CARLA_DECLARE_NON_COPY_CLASS(CarlaMutex) | ||||
@@ -40,6 +40,19 @@ public: | |||||
_dup(nullptr); | _dup(nullptr); | ||||
} | } | ||||
/* | |||||
* Simple character. | |||||
*/ | |||||
explicit CarlaString(const char c) | |||||
{ | |||||
char ch[2]; | |||||
ch[0] = c; | |||||
ch[1] = '\0'; | |||||
_init(); | |||||
_dup(ch); | |||||
} | |||||
/* | /* | ||||
* Simple char string. | * Simple char string. | ||||
*/ | */ | ||||
@@ -618,7 +631,7 @@ private: | |||||
* Called whenever the string needs to be allocated. | * Called whenever the string needs to be allocated. | ||||
* | * | ||||
* Notes: | * Notes: | ||||
* - Allocates string only if first initiated, or 'strBuf' is not null and new string contents are different | |||||
* - Allocates string only if first initiated, or if 'strBuf' is not null and new string contents are different | |||||
* - If 'strBuf' is null 'size' must be 0 | * - If 'strBuf' is null 'size' must be 0 | ||||
*/ | */ | ||||
void _dup(const char* const strBuf, const size_t size = 0) | void _dup(const char* const strBuf, const size_t size = 0) | ||||
@@ -0,0 +1,198 @@ | |||||
/* | |||||
* Carla Thread | |||||
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com> | |||||
* | |||||
* 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 of | |||||
* the License, or 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. | |||||
* | |||||
* For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
*/ | |||||
#ifndef CARLA_THREAD_HPP_INCLUDED | |||||
#define CARLA_THREAD_HPP_INCLUDED | |||||
#include "CarlaMutex.hpp" | |||||
#include "CarlaString.hpp" | |||||
// ----------------------------------------------------------------------- | |||||
// CarlaThread class | |||||
class CarlaThread | |||||
{ | |||||
protected: | |||||
/* | |||||
* Constructor. | |||||
*/ | |||||
CarlaThread(const char* const threadName = nullptr) | |||||
: fName(threadName), | |||||
fShouldExit(false) | |||||
{ | |||||
_init(); | |||||
} | |||||
/* | |||||
* Destructor. | |||||
*/ | |||||
~CarlaThread() | |||||
{ | |||||
CARLA_SAFE_ASSERT(! isRunning()); | |||||
stop(-1); | |||||
} | |||||
/* | |||||
* Virtual function to be implemented by the subclass. | |||||
*/ | |||||
virtual void run() = 0; | |||||
public: | |||||
/* | |||||
* Check if the thread is running. | |||||
*/ | |||||
bool isRunning() const noexcept | |||||
{ | |||||
#ifdef CARLA_OS_WIN32 | |||||
return (fHandle.p != nullptr); | |||||
#else | |||||
return (fHandle != 0); | |||||
#endif | |||||
} | |||||
/* | |||||
* Check if the thread should exit. | |||||
*/ | |||||
bool shouldExit() const noexcept | |||||
{ | |||||
return fShouldExit; | |||||
} | |||||
/* | |||||
* Start the thread. | |||||
*/ | |||||
void start() | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(! isRunning(),); | |||||
const CarlaMutex::ScopedLocker sl(fLock); | |||||
fShouldExit = false; | |||||
if (pthread_create(const_cast<pthread_t*>(&fHandle), nullptr, _threadEntryPoint, this) == 0) | |||||
{ | |||||
#if (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 | |||||
if (fName.isNotEmpty()) | |||||
pthread_setname_np(fHandle, fName.getBuffer()); | |||||
#endif | |||||
pthread_detach(fHandle); | |||||
// wait for thread to start | |||||
fLock.lock(); | |||||
} | |||||
} | |||||
/* | |||||
* Stop the thread. | |||||
* In the 'timeOutMilliseconds': | |||||
* =0 -> no wait | |||||
* >0 -> wait timeout value | |||||
* <0 -> wait forever | |||||
*/ | |||||
bool stop(const int timeOutMilliseconds) | |||||
{ | |||||
const CarlaMutex::ScopedLocker sl(fLock); | |||||
if (isRunning()) | |||||
{ | |||||
signalShouldExit(); | |||||
if (timeOutMilliseconds != 0) | |||||
{ | |||||
// Wait for the thread to stop | |||||
int timeOutCheck = (timeOutMilliseconds == 1 || timeOutMilliseconds == -1) ? timeOutMilliseconds : timeOutMilliseconds/2; | |||||
while (isRunning()) | |||||
{ | |||||
carla_msleep(2); | |||||
if (timeOutCheck < 0) | |||||
continue; | |||||
if (timeOutCheck > 0) | |||||
timeOutCheck -= 1; | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
if (isRunning()) | |||||
{ | |||||
// should never happen! | |||||
carla_stderr2("Carla assertion failure: \"! isRunning()\" in file %s, line %i", __FILE__, __LINE__); | |||||
pthread_cancel(fHandle); | |||||
_init(); | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/* | |||||
* Tell the thread to stop as soon as possible. | |||||
*/ | |||||
void signalShouldExit() noexcept | |||||
{ | |||||
fShouldExit = true; | |||||
} | |||||
private: | |||||
CarlaMutex fLock; // Thread lock | |||||
const CarlaString fName; // Thread name | |||||
volatile pthread_t fHandle; // Handle for this thread | |||||
volatile bool fShouldExit; // ... | |||||
void _init() noexcept | |||||
{ | |||||
#ifdef CARLA_OS_WIN32 | |||||
fHandle.p = nullptr; | |||||
fHandle.x = 0; | |||||
#else | |||||
fHandle = 0; | |||||
#endif | |||||
} | |||||
static void* _threadEntryPoint(void* userData) | |||||
{ | |||||
static_cast<CarlaThread*>(userData)->threadEntryPoint(); | |||||
return nullptr; | |||||
} | |||||
void threadEntryPoint() | |||||
{ | |||||
//if (fName.isNotEmpty()) | |||||
// setCurrentThreadName(threadName); | |||||
// tell dad we're ready | |||||
fLock.unlock(); | |||||
run(); | |||||
// done | |||||
_init(); | |||||
} | |||||
CARLA_PREVENT_HEAP_ALLOCATION | |||||
CARLA_DECLARE_NON_COPY_CLASS(CarlaThread) | |||||
}; | |||||
// ----------------------------------------------------------------------- | |||||
#endif // CARLA_THREAD_HPP_INCLUDED |