fix semaphore

This commit is contained in:
Martin Baliet 2024-04-11 17:22:36 +02:00
parent 9e3dab85c3
commit 3007e81a1e
11 changed files with 660 additions and 254 deletions

View File

@ -4,7 +4,7 @@ add_library(kernel OBJECT
errors.cpp
filesystem.cpp
pthread.cpp
semaphore.cpp
semaphore_fifo.cpp
)
add_dependencies(kernel third_party boost)

View File

@ -1,199 +0,0 @@
#include "semaphore.h"
#include "logging.h"
#include "modules_include/common.h"
#include <queue>
#include <boost/chrono.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
LOG_DEFINE_MODULE(Semaphore)
namespace {
size_t getUniqueId() {
static size_t count = 0;
return count++;
}
} // namespace
class Semaphore: public ISemaphore {
enum class Status { Set, Canceled, Deleted };
boost::mutex m_mutex;
boost::condition_variable m_condState;
std::queue<std::shared_ptr<boost::condition_variable>> m_condQueue; // todo: optimze
std::string m_name;
size_t m_waitCounter = 0;
size_t const m_id = getUniqueId();
int m_countThreads = 0;
Status m_state = Status::Set;
bool m_fifo;
int m_count;
int m_max_count;
public:
Semaphore(const std::string& name, bool fifo, int init_count, int max_count): m_name(name), m_fifo(fifo), m_count(init_count), m_max_count(max_count) {
if (!fifo) m_condQueue.push(std::make_shared<boost::condition_variable>());
};
virtual ~Semaphore();
int cancel(int setCount, int* numWaitingThreads) final;
int signal(int signalCount) final;
int wait(int needcount, uint32_t* pMicros) final;
int try_wait(int needcount, uint32_t* pMicros) final;
std::string_view const getName() const final { return m_name; }
size_t getId() const final { return m_id; }
private:
int wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock);
};
std::unique_ptr<ISemaphore> createSemaphore(const char* name, bool fifo, int initCount, int maxCount) {
return std::make_unique<Semaphore>(name == nullptr ? "" : name, fifo, initCount, maxCount);
}
Semaphore::~Semaphore() {
boost::unique_lock lock(m_mutex);
m_state = Status::Deleted;
while (!m_condQueue.empty()) {
m_condQueue.front()->notify_all();
m_condQueue.pop();
}
// Wait for Threads to leave wait
m_condState.wait(lock, [this] { return m_countThreads == 0; });
}
int Semaphore::cancel(int setCount, int* numWaitingThreads) {
boost::unique_lock lock(m_mutex);
m_state = Status::Canceled;
if (numWaitingThreads != nullptr) *numWaitingThreads = m_countThreads;
if (m_fifo) {
while (!m_condQueue.empty()) {
m_condQueue.front()->notify_one();
m_condQueue.pop();
}
} else {
m_condQueue.front()->notify_all();
}
// Wait for Threads to leave wait
m_condState.wait(lock, [this] { return m_countThreads == 0; });
m_state = Status::Set;
return Ok;
}
int Semaphore::signal(int signalCount) {
LOG_USE_MODULE(Semaphore);
boost::unique_lock lock(m_mutex);
LOG_TRACE(L"KernelSema(%llu) name:%S signal:%d count:%d", m_id, m_name.c_str(), signalCount, m_count);
m_condState.wait(lock, [this] { return m_state != Status::Canceled; });
if (m_state == Status::Deleted) {
return getErr(ErrCode::_EACCES);
}
if (m_count + signalCount > m_max_count) {
return getErr(ErrCode::_EINVAL);
}
if (signalCount > 0) {
m_count += signalCount;
if (!m_condQueue.empty()) m_condQueue.front()->notify_one();
}
return Ok;
}
int Semaphore::wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock) {
LOG_USE_MODULE(Semaphore);
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
uint32_t const micros = pMicros != nullptr ? *pMicros : 0;
size_t waitCount = 0;
{
if (m_state == Status::Deleted) return getErr(ErrCode::_EACCES);
std::shared_ptr<boost::condition_variable> condVar;
if (m_fifo) {
condVar = std::make_shared<boost::condition_variable>();
m_condQueue.push(condVar);
} else {
condVar = m_condQueue.front();
}
waitCount = m_waitCounter++;
LOG_TRACE(L"-> KernelSema(%llu) name:%S waitCount:%llu need:%d count:%d time:%u us", m_id, m_name.c_str(), waitCount, needCount, m_count, micros);
m_countThreads++;
if (pMicros != nullptr) {
if (*pMicros == 0) {
if (m_count < needCount) {
*pMicros = 0;
m_countThreads--;
m_condState.notify_all();
return getErr(ErrCode::_ETIMEDOUT);
}
} else {
if (!condVar->wait_for(lock, boost::chrono::microseconds(micros),
[this, condVar, needCount] { return m_state != Status::Set || (m_condQueue.front() == condVar && m_count >= needCount); })) {
LOG_WARN(L"<- KernelSema(%llu) name:%S waitCount:%llu timeout", m_id, m_name.c_str(), waitCount);
*pMicros = 0;
m_countThreads--;
m_condState.notify_all();
return getErr(ErrCode::_ETIMEDOUT);
}
}
} else {
condVar->wait(lock, [this, condVar, needCount] { return m_state != Status::Set || (m_condQueue.front() == condVar && m_count >= needCount); });
}
if (m_fifo) m_condQueue.pop();
m_count -= needCount;
m_countThreads--;
m_condState.notify_all();
if (m_count >= 0 && !m_condQueue.empty()) m_condQueue.front()->notify_one(); // notify next in queue if count is != 0
}
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start).count();
LOG_TRACE(L"<- KernelSema(%llu) name:%S waitCount:%llu count:%d waiting:%llu", m_id, m_name.c_str(), waitCount, m_count, m_condQueue.size());
if (pMicros != nullptr) {
*pMicros = (elapsed >= micros ? 0 : micros - elapsed);
}
if (m_state == Status::Canceled) {
return getErr(ErrCode::_ECANCELED);
}
if (m_state == Status::Deleted) {
return getErr(ErrCode::_EACCES);
}
return Ok;
}
int Semaphore::wait(int needCount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutex);
return wait_internal(needCount, pMicros, lock);
}
int Semaphore::try_wait(int needCount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutex);
if (m_countThreads > 0) return getErr(ErrCode::_EAGAIN);
return wait_internal(needCount, pMicros, lock);
}

View File

@ -13,19 +13,17 @@ class ISemaphore {
public:
virtual ~ISemaphore() = default;
virtual int cancel(int setCount, int* numWaitingThreads) = 0;
virtual int signal(int signalCount) = 0;
virtual int wait(int needcount, uint32_t* pMicros) = 0;
virtual int try_wait(int needcount, uint32_t* pMicros) = 0;
int poll(int needCount) {
uint32_t micros = 0;
return wait(needCount, &micros);
}
virtual int cancel(int setCount, int* numCanceled) = 0;
virtual int signal(int signalCount) = 0;
virtual int wait(int needcount, uint32_t* pMicros) = 0;
virtual int try_wait(int needcount, uint32_t* pMicros) = 0;
virtual int poll(int needCount) = 0;
virtual std::string_view const getName() const = 0;
virtual size_t getId() const = 0;
virtual size_t getSignalCounter() const = 0;
};
#if defined(__APICALL_EXTERN)
@ -36,6 +34,7 @@ class ISemaphore {
#define __APICALL
#endif
__APICALL std::unique_ptr<ISemaphore> createSemaphore(const char* name, bool fifo, int initCount, int maxCount);
__APICALL std::unique_ptr<ISemaphore> createSemaphore_fifo(const char* name, int initCount, int maxCount);
__APICALL std::unique_ptr<ISemaphore> createSemaphore_prio(const char* name, int initCount, int maxCount);
#undef __APICALL

View File

@ -0,0 +1,309 @@
#include "logging.h"
#include "modules_include/common.h"
#include "pthread.h"
#include "semaphore.h"
#include <queue>
#include <boost/chrono.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <map>
LOG_DEFINE_MODULE(Semaphore)
namespace {
enum class SemState { idle, signaled, waiting, canceled };
struct SemData {
size_t index = 0;
uint32_t needs = 0;
SemState state = SemState::idle;
boost::condition_variable m_condVar;
SemData* parent = nullptr;
SemData* child = nullptr;
};
} // namespace
class Semaphore: public ISemaphore {
boost::mutex m_mutexInt;
boost::condition_variable m_condState;
size_t const m_id = [] {
static size_t count = 0;
return count++;
}();
std::string const m_name;
int const m_maxCount;
uint32_t m_signalValue = 0;
bool m_isStop = false;
std::map<size_t, std::shared_ptr<SemData>> m_semData; // todo use pthread specific
SemData* m_curBack = nullptr;
SemData* m_curStart = nullptr;
size_t m_numWaiters = 0;
size_t m_countWaits = 0;
public:
Semaphore(const std::string& name, int initCount, int maxCount): m_name(name), m_maxCount(maxCount) { m_signalValue = initCount; };
virtual ~Semaphore() {
m_isStop = true;
boost::unique_lock lock(m_mutexInt);
while (m_curStart != nullptr) {
m_curStart->m_condVar.notify_one();
m_curStart = m_curStart->child;
}
m_condState.wait(lock, [this] { return m_numWaiters == 0; });
}
// ### Interface
std::string_view const getName() const final { return m_name; }
size_t getSignalCounter() const final { return m_signalValue; }
size_t getId() const final { return m_id; }
int cancel(int setCount, int* numCanceled) final;
int signal(int signalCount) final;
int wait(int needcount, uint32_t* pMicros) final;
int try_wait(int needcount, uint32_t* pMicros) final;
int poll(int needCount) final;
private:
int wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock);
int poll_internal(int needCount, boost::unique_lock<boost::mutex>& lock);
};
std::unique_ptr<ISemaphore> createSemaphore_fifo(const char* name, int initCount, int maxCount) {
return std::make_unique<Semaphore>(name == nullptr ? "" : name, initCount, maxCount);
}
int Semaphore::cancel(int setCount, int* numCanceled) {
LOG_USE_MODULE(Semaphore);
if (setCount <= 0 || setCount >= m_maxCount) return getErr(ErrCode::_EINVAL);
boost::unique_lock lock(m_mutexInt);
*numCanceled = 0;
auto start = m_curStart;
while (start != nullptr) {
if (start->needs > setCount) break;
++*numCanceled;
setCount -= start->needs;
start->state = SemState::canceled;
start = start->child; // next
}
m_signalValue = 0;
// Notify back
if (*numCanceled > 0) {
lock.unlock();
start->m_condVar.notify_one();
}
// -
return Ok;
}
int Semaphore::signal(int signalCount) {
LOG_USE_MODULE(Semaphore);
boost::unique_lock lock(m_mutexInt);
m_signalValue += signalCount;
// Notify back
if (m_curStart != nullptr && m_curStart->needs <= m_signalValue) {
m_curStart->state = SemState::signaled;
LOG_TRACE(L"KernelSema(%llu) name:%S notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index);
auto& cond = m_curStart->m_condVar;
lock.unlock();
cond.notify_one(); // race condition if m_curStart is used
} else {
LOG_TRACE(L"KernelSema(%llu) name:%S signal| count:%d", m_id, m_name.c_str(), m_signalValue);
}
// -
return Ok;
}
int Semaphore::wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock) {
LOG_USE_MODULE(Semaphore);
if (poll_internal(needCount, lock) == Ok) {
return Ok;
}
auto itThread = m_semData.find(pthread::getThreadId());
if (itThread == m_semData.end()) {
itThread = m_semData.emplace(std::make_pair(pthread::getThreadId(), std::make_shared<SemData>())).first;
}
++m_numWaiters;
auto ownData = itThread->second;
ownData->index = ++m_countWaits;
ownData->needs = needCount;
// enque in list
if (m_curBack != nullptr) {
m_curBack->child = ownData.get();
}
if (m_curStart == nullptr) {
m_curStart = ownData.get();
}
ownData->parent = m_curBack;
m_curBack = ownData.get();
// - list
int ret = Ok;
ownData->state = SemState::waiting;
LOG_TRACE(L"-> KernelSema(%llu) name:%S wait| count:%d needs:%d index:%llu state:%d", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index,
ownData->state);
if (pMicros == nullptr) {
ownData->m_condVar.wait(lock, [this, ownData] { return m_isStop || ownData->state != SemState::waiting; });
} else {
std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
if (!ownData->m_condVar.wait_for(lock, boost::chrono::microseconds(*pMicros),
[this, ownData] { return m_isStop || ownData->state != SemState::waiting; })) {
// timeout
ret = getErr(ErrCode::_ETIMEDOUT);
// remove from list
if (ownData->parent != nullptr) {
ownData->parent->child = ownData->child;
}
if (ownData->child != nullptr) {
ownData->child->parent = ownData->parent;
}
LOG_TRACE(L"<- KernelSema(%llu) name:%S timeout| count:%d needs:%d index:%llu", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index);
// Special: first in list -> notify next
if (ownData->parent == nullptr) {
m_curStart = ownData->child;
if (m_curStart != nullptr && (m_isStop || m_curStart->needs <= m_signalValue)) {
m_curStart->state = SemState::signaled;
LOG_TRACE(L"KernelSema(%llu) name:%S timeout notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index);
auto& cond = m_curStart->m_condVar;
lock.unlock();
cond.notify_one(); // race condition if m_curStart is used
}
}
// - special
// Reset ownData
ownData->child = nullptr;
ownData->parent = nullptr;
ownData->state = SemState::idle;
*pMicros = 0;
if (--m_numWaiters == 0 && m_isStop) {
lock.unlock();
m_condState.notify_one();
}
return ret;
}
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - startTime).count();
if (pMicros != nullptr) {
*pMicros = (elapsed >= *pMicros ? 0 : *pMicros - elapsed);
}
}
if (m_isStop || ownData->state == SemState::canceled) {
ret = getErr(ErrCode::_ECANCELED);
}
if (ret == Ok) {
m_signalValue -= ownData->needs;
}
// Set list start/end
if (ownData->child == nullptr) {
// Reached end -> reset back
m_curBack = nullptr;
m_countWaits = 0;
}
m_curStart = ownData->child;
// -
LOG_TRACE(L"<- KernelSema(%llu) name:%S wait| count:%d needs:%d index:%llu state:%d", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index,
ownData->state);
// Reset ownData
ownData->child = nullptr;
ownData->parent = nullptr;
ownData->state = SemState::idle;
// -
// notify next
if (m_curStart != nullptr && (m_isStop || m_curStart->needs <= m_signalValue)) {
m_curStart->state = SemState::signaled;
LOG_TRACE(L"KernelSema(%llu) name:%S notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index);
auto& cond = m_curStart->m_condVar;
lock.unlock();
cond.notify_one(); // race condition if m_curStart is used
}
//
if (--m_numWaiters == 0 && m_isStop) {
lock.unlock();
m_condState.notify_one();
}
return ret;
}
int Semaphore::wait(int needcount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutexInt);
return wait_internal(needcount, pMicros, lock);
}
int Semaphore::try_wait(int needcount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutexInt);
if (m_curBack != nullptr) {
return getErr(ErrCode::_EBUSY);
}
return wait_internal(needcount, pMicros, lock);
}
int Semaphore::poll_internal(int needCount, boost::unique_lock<boost::mutex>& lock) {
if (m_curBack == 0 && (needCount <= m_signalValue)) {
m_signalValue -= needCount;
return Ok;
}
return getErr(ErrCode::_EAGAIN); // Waiters in queue, todo need enqueue?
}
int Semaphore::poll(int needCount) {
boost::unique_lock lock(m_mutexInt);
return poll_internal(needCount, lock);
}

View File

@ -434,6 +434,10 @@ void* SYSV_ABI sceGnmGetTheTessellationFactorRingBufferBaseAddress() {
return (void*)0xff0000000; // Maps/allocates it afterwards anyway
}
int SYSV_ABI sceGnmValidateCommandBuffers() {
return Err::VALIDATION_NOT_ENABLED;
}
// #### Only used for tracing ########
// #####################################

View File

@ -1,3 +1,5 @@
#include "boost/date_time/posix_time/posix_time_types.hpp"
#include "boost/interprocess/sync/interprocess_semaphore.hpp"
#include "common.h"
#include "core/kernel/errors.h"
#include "core/kernel/semaphore.h"
@ -7,6 +9,7 @@
#include <boost/chrono.hpp>
#include <boost/thread.hpp>
LOG_DEFINE_MODULE(libScePosix);
using ScePthreadSem_t = ISemaphore*;
@ -15,12 +18,12 @@ extern "C" {
EXPORT const char* MODULE_NAME = "libkernel";
EXPORT SYSV_ABI int __NID(sem_init)(ScePthreadSem_t* sem, int pshared, unsigned int value) {
(*sem) = createSemaphore(nullptr, false, 0, std::numeric_limits<int>::max()).release();
EXPORT SYSV_ABI int __NID(sem_init)(boost::interprocess::interprocess_semaphore** sem, int pshared, unsigned int value) {
*sem = new boost::interprocess::interprocess_semaphore(value);
return Ok;
}
EXPORT SYSV_ABI int __NID(sem_destroy)(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int __NID(sem_destroy)(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
@ -29,27 +32,29 @@ EXPORT SYSV_ABI int __NID(sem_destroy)(ScePthreadSem_t* sem) {
return Ok;
}
EXPORT SYSV_ABI int __NID(sem_post)(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int __NID(sem_post)(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->signal(1));
(*sem)->post();
return Ok;
}
// EXPORT SYSV_ABI int sem_reltimedwait_np(ScePthreadSem_t*sem, useconds_t);
EXPORT SYSV_ABI int __NID(sem_trywait)(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int __NID(sem_trywait)(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->try_wait(1, nullptr));
return (*sem)->try_wait() ? Ok : POSIX_SET(ErrCode::_EBUSY);
}
// EXPORT SYSV_ABI int sem_unlink(const char* semName){}
EXPORT SYSV_ABI int __NID(sem_wait)(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int __NID(sem_wait)(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->wait(1, nullptr));
(*sem)->wait();
return Ok;
}
EXPORT SYSV_ABI unsigned int __NID(sleep)(unsigned int seconds) {

View File

@ -1,5 +1,7 @@
#include "core/kernel/semaphore.h"
#include "boost/date_time/posix_time/posix_time_types.hpp"
#include "boost/interprocess/sync/interprocess_semaphore.hpp"
#include "common.h"
#include "core/kernel/errors.h"
#include "logging.h"
@ -7,8 +9,8 @@
using ScePthreadSem_t = ISemaphore*;
extern "C" {
EXPORT SYSV_ABI int scePthreadSemInit(ScePthreadSem_t* sem, int pshared, unsigned int initCount, const char* name) {
(*sem) = createSemaphore(name, false, 0, std::numeric_limits<int>::max()).release();
EXPORT SYSV_ABI int scePthreadSemInit(boost::interprocess::interprocess_semaphore** sem, int pshared, unsigned int initCount, const char* name) {
*sem = new boost::interprocess::interprocess_semaphore(initCount);
return Ok;
}
@ -21,44 +23,47 @@ EXPORT SYSV_ABI int scePthreadSemDestroy(ScePthreadSem_t* sem) {
return Ok;
}
EXPORT SYSV_ABI int scePthreadSemTimedwait(ScePthreadSem_t* sem, SceKernelUseconds usec) {
EXPORT SYSV_ABI int scePthreadSemTimedwait(boost::interprocess::interprocess_semaphore** sem, SceKernelUseconds usec) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->wait(1, &usec));
auto deadline = boost::posix_time::microsec_clock::universal_time() + boost::posix_time::microseconds(usec);
return (*sem)->timed_wait(deadline) ? Ok : POSIX_SET(ErrCode::_ETIMEDOUT);
}
EXPORT SYSV_ABI int scePthreadSemPost(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int scePthreadSemPost(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->signal(1));
(*sem)->post();
return Ok;
}
EXPORT SYSV_ABI int scePthreadSemTrywait(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int scePthreadSemTrywait(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->try_wait(1, nullptr));
return (*sem)->try_wait() ? Ok : POSIX_SET(ErrCode::_EBUSY);
}
EXPORT SYSV_ABI int scePthreadSemWait(ScePthreadSem_t* sem) {
EXPORT SYSV_ABI int scePthreadSemWait(boost::interprocess::interprocess_semaphore** sem) {
if (sem == nullptr || *sem == nullptr) {
return POSIX_SET(ErrCode::_ESRCH);
}
return POSIX_CALL((*sem)->wait(1, nullptr));
(*sem)->wait();
return Ok;
}
}
EXPORT SYSV_ABI int sceKernelCreateSema(ScePthreadSem_t* sem, const char* name, uint32_t attr, int init, int max, const void* opt) {
if (name == nullptr || init < 0 || init > max) {
if (sem == nullptr) {
return getErr(ErrCode::_EINVAL);
}
bool fifo = attr == 1 ? true : false;
(*sem) = createSemaphore(name, fifo, init, max).release();
(*sem) = createSemaphore_fifo(name, init, max).release();
return Ok;
}

View File

@ -1,5 +1,9 @@
enable_testing()
add_compile_definitions(
BOOST_ALL_NO_LIB
)
link_libraries(gtest_main gmock gmock_main)
add_link_options(/DEBUG)
link_directories(
@ -13,9 +17,8 @@ include_directories(
${CMAKE_BINARY_DIR}/third_party/install/include
${Vulkan_INCLUDE_DIRS}
${PRJ_SRC_DIR}/public_repo
${PRJ_SRC_DIR}/public_repo/third_party
${PRJ_SRC_DIR}/public_repo/tools/logging
${PRJ_SRC_DIR}/third_party
${PRJ_SRC_DIR}/tools/logging
)
add_subdirectory(core)

View File

@ -1,17 +1 @@
enable_testing()
add_executable(core_test
semaphore/entry.cpp
)
add_test(NAME core_test COMMAND core_test)
add_dependencies(core_test third_party psOff_utility logging_stub)
set_target_properties(core_test
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
)
add_subdirectory(semaphore)

View File

@ -0,0 +1,25 @@
enable_testing()
add_executable(semaphore_test
entry.cpp
${PRJ_SRC_DIR}/core/kernel/semaphore_fifo.cpp
)
add_test(NAME semaphore_test COMMAND semaphore_test)
add_dependencies(semaphore_test third_party psOff_utility logging_stub boost)
target_link_libraries(semaphore_test PRIVATE
logging_stub.lib
libboost_thread
libboost_chrono
)
set_target_properties(semaphore_test
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/."
)

View File

@ -1,4 +1,275 @@
#include "core/kernel/semaphore.h"
#include "modules_include/common.h"
#include <fff/fff.h>
#include <future>
#include <gtest/gtest.h>
TEST(core_semaphore, init) {}
DEFINE_FFF_GLOBALS;
namespace pthread {
FAKE_VALUE_FUNC(int, getThreadId);
}
TEST(core_semaphore, init) {
RESET_FAKE(pthread::getThreadId);
pthread::getThreadId_fake.return_val = 4;
constexpr int initCount = 1;
constexpr int maxCount = 10;
{
auto sem = createSemaphore_fifo("test", initCount, maxCount);
EXPECT_TRUE(sem);
EXPECT_EQ(sem->getSignalCounter(), initCount);
}
{
auto sem = createSemaphore_fifo(nullptr, initCount, maxCount);
EXPECT_TRUE(sem);
EXPECT_EQ(sem->getSignalCounter(), initCount);
}
}
TEST(core_semaphore, polling_1) {
RESET_FAKE(pthread::getThreadId);
pthread::getThreadId_fake.return_val = 4;
constexpr int initCount = 1;
constexpr int maxCount = 10;
auto sem = createSemaphore_fifo("test", initCount, maxCount);
auto fut = std::async(std::launch::async, [&sem] {
{
auto res = sem->poll(2);
EXPECT_EQ(res, getErr(ErrCode::_EAGAIN));
}
{
auto res = sem->poll(1);
EXPECT_EQ(res, Ok);
EXPECT_EQ(sem->getSignalCounter(), 0);
}
{
auto res = sem->poll(2);
EXPECT_EQ(res, getErr(ErrCode::_EAGAIN));
}
{
auto resSignal = sem->signal(2);
EXPECT_EQ(resSignal, Ok);
auto res = sem->poll(2);
EXPECT_EQ(res, Ok);
EXPECT_EQ(sem->getSignalCounter(), 0);
}
{
auto resSignal = sem->signal(4);
EXPECT_EQ(resSignal, Ok);
auto res1 = sem->poll(2);
EXPECT_EQ(res1, Ok);
EXPECT_EQ(sem->getSignalCounter(), 2);
auto res2 = sem->poll(2);
EXPECT_EQ(res2, Ok);
EXPECT_EQ(sem->getSignalCounter(), 0);
}
});
auto res = fut.wait_for(std::chrono::milliseconds(10));
EXPECT_NE(res, std::future_status::timeout);
}
TEST(core_semaphore, signal_1) {
RESET_FAKE(pthread::getThreadId);
pthread::getThreadId_fake.return_val = 4;
constexpr int initCount = 1;
constexpr int maxCount = 10;
auto sem = createSemaphore_fifo("test", initCount, maxCount);
{ // Check wait instant release
auto resSignal = sem->signal(1);
EXPECT_EQ(resSignal, Ok);
EXPECT_EQ(sem->getSignalCounter(), 2);
auto fut = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, Ok);
}
});
auto res = fut.wait_for(std::chrono::milliseconds(10));
EXPECT_NE(res, std::future_status::timeout);
}
EXPECT_EQ(sem->getSignalCounter(), 0);
{ // Check timeout
auto resSignal = sem->signal(1);
EXPECT_EQ(resSignal, Ok);
EXPECT_EQ(sem->getSignalCounter(), 1);
auto fut = std::async(std::launch::async, [&sem] {
{
uint32_t micros = 1;
auto res = sem->wait(2, &micros); // wait timeout
EXPECT_EQ(res, getErr(ErrCode::_ETIMEDOUT));
}
});
auto res = fut.wait_for(std::chrono::milliseconds(10));
EXPECT_NE(res, std::future_status::timeout);
}
EXPECT_EQ(sem->getSignalCounter(), 1);
{ // Check wait signal afterwards
auto fut = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, Ok);
}
});
std::this_thread::sleep_for(std::chrono::microseconds(100)); // this or mock condition var
auto resSignal = sem->signal(1);
EXPECT_EQ(resSignal, Ok);
EXPECT_EQ(sem->getSignalCounter(), 2);
auto res = fut.wait_for(std::chrono::milliseconds(10));
EXPECT_NE(res, std::future_status::timeout);
}
}
TEST(core_semaphore, signal_2) {
RESET_FAKE(pthread::getThreadId);
pthread::getThreadId_fake.custom_fake = []() {
static int counter = 0;
return ++counter;
};
constexpr int initCount = 0;
constexpr int maxCount = 10;
auto sem = createSemaphore_fifo("test", initCount, maxCount);
EXPECT_EQ(sem->getSignalCounter(), 0);
size_t countItems = 0;
{ // Check wait signal afterwards (sequenze release)
auto fut1 = std::async(std::launch::async, [&sem, &countItems] {
{
auto res = sem->wait(2, nullptr); // wait forever
++countItems;
EXPECT_EQ(res, Ok);
}
});
auto fut2 = std::async(std::launch::async, [&sem, &countItems] {
{
auto res = sem->wait(2, nullptr); // wait forever
++countItems;
EXPECT_EQ(res, Ok);
}
});
std::this_thread::sleep_for(std::chrono::microseconds(100)); // this or mock condition var
{
auto resSignal = sem->signal(2);
EXPECT_EQ(resSignal, Ok);
}
auto res1 = fut1.wait_for(std::chrono::microseconds(100));
EXPECT_NE(res1, std::future_status::timeout);
EXPECT_EQ(sem->getSignalCounter(), 0);
EXPECT_EQ(countItems, 1);
{
auto resSignal = sem->signal(2);
EXPECT_EQ(resSignal, Ok);
}
auto res2 = fut2.wait_for(std::chrono::microseconds(100));
EXPECT_NE(res2, std::future_status::timeout);
EXPECT_EQ(countItems, 2);
}
{ // Check wait signal afterwards (direct release)
auto fut1 = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, Ok);
}
});
auto fut2 = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, Ok);
}
});
std::this_thread::sleep_for(std::chrono::microseconds(100)); // this or mock condition var
{
auto resSignal = sem->signal(4);
EXPECT_EQ(resSignal, Ok);
}
auto res1 = fut1.wait_for(std::chrono::microseconds(100));
EXPECT_NE(res1, std::future_status::timeout);
auto res2 = fut2.wait_for(std::chrono::microseconds(100));
EXPECT_NE(res2, std::future_status::timeout);
EXPECT_EQ(sem->getSignalCounter(), 0);
}
sem.reset();
}
TEST(core_semaphore, signal_exit) {
RESET_FAKE(pthread::getThreadId);
pthread::getThreadId_fake.custom_fake = []() {
static int counter = 0;
return ++counter;
};
constexpr int initCount = 0;
constexpr int maxCount = 10;
auto sem = createSemaphore_fifo("test", initCount, maxCount);
EXPECT_EQ(sem->getSignalCounter(), 0);
{ // Check wait signal afterwards and exit release
auto fut1 = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, Ok);
}
});
auto fut2 = std::async(std::launch::async, [&sem] {
{
auto res = sem->wait(2, nullptr); // wait forever
EXPECT_EQ(res, getErr(ErrCode::_ECANCELED));
}
});
std::this_thread::sleep_for(std::chrono::microseconds(100)); // this or mock condition var
auto resSignal = sem->signal(2);
EXPECT_EQ(resSignal, Ok);
auto res1 = fut1.wait_for(std::chrono::microseconds(100));
EXPECT_NE(res1, std::future_status::timeout);
EXPECT_EQ(sem->getSignalCounter(), 0);
auto res2 = fut2.wait_for(std::chrono::microseconds(100));
EXPECT_EQ(res2, std::future_status::timeout);
sem.reset();
}
}