diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bea66fe8..48073ba1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ endif() # include(GNUInstallDirs) include(CheckIncludeFile) +include(CheckFunctionExists) find_package(Backtrace) find_package(Threads) find_package(OpenSSL) @@ -86,6 +87,11 @@ else() set(Backtrace_LIBRARIES) endif() +check_function_exists(thrd_create HAVE_THREADS) +if(HAVE_THREADS) + add_definitions(-DHAVE_THREADS) +endif() + if(CMAKE_USE_PTHREADS_INIT) add_definitions(-DHAVE_PTHREAD) set(HAVE_PTHREAD ON) @@ -543,6 +549,21 @@ elseif(HAVE_PTHREAD) ) endif() +if(HAVE_THREADS) + list(APPEND SRCS + src/thread/thread.c + ) +elseif(HAVE_PTHREAD) + list(APPEND SRCS + src/thread/thread.c + src/thread/posix.c + ) +elseif(CMAKE_USE_WIN32_THREADS_INIT) + list(APPEND SRCS + src/thread/thread.c + src/thread/win32.c + ) +endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND SRCS diff --git a/Makefile b/Makefile index 62b2b7c54..f114db74f 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ MODULES += dns MODULES += md5 crc32 sha hmac base64 MODULES += udp sa net tcp tls MODULES += list mbuf hash -MODULES += fmt tmr trace btrace main mem dbg sys lock mqueue +MODULES += fmt tmr trace btrace main mem dbg sys thread lock mqueue MODULES += mod conf MODULES += bfcp MODULES += aes srtp diff --git a/include/re.h b/include/re.h index 0899de70f..63fd457f3 100644 --- a/include/re.h +++ b/include/re.h @@ -56,6 +56,7 @@ extern "C" { #include "re_sys.h" #include "re_tcp.h" #include "re_telev.h" +#include "re_thread.h" #include "re_tmr.h" #include "re_trace.h" #include "re_tls.h" diff --git a/include/re_thread.h b/include/re_thread.h new file mode 100644 index 000000000..cf5071ea4 --- /dev/null +++ b/include/re_thread.h @@ -0,0 +1,236 @@ +/** + * @file re_thread.h Thread support + * + * Inspired by C11 thread support this provides a cross platform interfaces to + * thread, mutex and condition handling (C11, POSIX and Windows Threads). + * + * Preferred order: + * + * - C11 threads (glibc>=2.28, musl, FreeBSD>=10) + * - POSIX PTHREAD (Linux/UNIX, winpthreads) + * - Windows Thread API + * + * Copyright (C) 2022 Sebastian Reimers + */ + +#if defined(HAVE_THREADS) +#include + +#else + +#if defined(HAVE_PTHREAD) + +#include +#include +#define THREAD_ONCE_FLAG_INIT PTHREAD_ONCE_INIT +typedef pthread_once_t thrd_once_flag; +typedef pthread_t thrd_t; +typedef pthread_cond_t cnd_t; +typedef pthread_mutex_t mtx_t; + +#elif defined(WIN32) + +#include +#define THREAD_ONCE_FLAG_INIT INIT_ONCE_STATIC_INIT +typedef INIT_ONCE thrd_once_flag; +typedef HANDLE thrd_t; +typedef CONDITION_VARIABLE cnd_t; +typedef CRITICAL_SECTION mtx_t; + +#endif + +enum { mtx_plain = 0, mtx_try = 1, mtx_timed = 2, mtx_recursive = 4 }; + +/* Exit and error codes. */ +enum { + thrd_success = 0, + thrd_busy = 1, + thrd_error = 2, + thrd_nomem = 3, + thrd_timedout = 4 +}; + +typedef int (*thrd_start_t)(void *); + + +/****************************************************************************** + * Thread functions + *****************************************************************************/ + +/** + * Creates a new thread + * + * @param thr Pointer to new thread + * @param func Function to execute + * @param arg Argument to pass to the function + * + * @return 0 if success, otherwise errorcode + */ +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); + + +/** + * Checks whether `lhs` and `rhs` refer to the same thread. + * + * @return Non-zero value if lhs and rhs refer to the same value, 0 otherwise. + */ +int thrd_equal(thrd_t lhs, thrd_t rhs); + + +/** + * Return the identifier of the calling thread. + */ +thrd_t thrd_current(void); + + +/** + * Detaches the thread identified by `thr` from the current environment. + * + * @return 0 if success, otherwise errorcode + */ +int thrd_detach(thrd_t thr); + + +/** + * Blocks the current thread until the thread identified by `thr` finishes + * execution + * + * @param thr Thread + * @param res Result code location + * + * @return 0 if success, otherwise errorcode + */ +int thrd_join(thrd_t thr, int *res); + + +/** + * Calls a function exactly once + * + * @param flag Pointer to object initialized by THREAD_ONCE_FLAG_INIT + * @param func The function to execute only once + */ +void call_once(thrd_once_flag *flag, void (*func)(void)); + + +/** + * Terminates the calling thread + * + * @param res The result value to return + */ +void thrd_exit(int res); + + +/****************************************************************************** + * Condition functions + *****************************************************************************/ + +/** + * Initializes new condition variable + * + * @param cnd Pointer to a variable to store condition variable + * + * @return 0 if success, otherwise errorcode + */ +int cnd_init(cnd_t *cnd); + + +/** + * Unblocks one thread blocked on a condition variable + * + * @param cnd Pointer to condition variable + * + * @return 0 if success, otherwise errorcode + */ +int cnd_signal(cnd_t *cnd); + + +/** + * Unblocks all thrds blocked on a condition variable + * + * @param cnd Pointer to condition variable + * + * @return 0 if success, otherwise errorcode + */ +int cnd_broadcast(cnd_t *cnd); + + +/** + * Blocks on a condition variable + * + * @param cnd Pointer to condition variable + * @param lock Lock mutex pointer + * + * @return 0 if success, otherwise errorcode + */ +int cnd_wait(cnd_t *cnd, mtx_t *mtx); + + +/** + * Destroys the condition variable pointed to by cnd. + * If there are thrds waiting on cnd, the behavior is undefined. + * + * @param cnd pointer to the condition variable to destroy + */ +void cnd_destroy(cnd_t *cnd); + + +/****************************************************************************** + * Mutex functions + *****************************************************************************/ + +/** + * Creates a new mutex object with type. The object pointed to by mutex is set + * to an identifier of the newly created mutex. + * + * @param mtx Pointer to the mutex to initialize + * @param type The type of the mutex + * + * @return thrd_success on success, otherwise thrd_error + */ +int mtx_init(mtx_t *mtx, int type); + + +/** + * Blocks the current thread until the mutex pointed to by mutex is locked. + * The behavior is undefined if the current thread has already locked the + * mutex and the mutex is not recursive. + * + * @param mtx Pointer to the mutex + * + * @return thrd_success on success, otherwise thrd_error + */ +int mtx_lock(mtx_t *mtx); + + +/** + * Tries to lock the mutex pointed to by mutex without blocking. + * Returns immediately if the mutex is already locked. + * + * @param mtx Pointer to the mutex + * + * @return thrd_success on success, thrd_busy if alread locked, + * otherwise thrd_error + */ +int mtx_trylock(mtx_t *mtx); + + +/** + * Unlocks the mutex pointed to by mutex. + * + * @param mtx Pointer to the mutex + * + * @return thrd_success on success, otherwise thrd_error + */ +int mtx_unlock(mtx_t *mtx); + + +#endif /* C11 threads fallback */ + + +/****************************************************************************** + * Extra + *****************************************************************************/ +/* int thrd_prio(enum thrd_prio prio) */ +/* void thrd_print(struct re_printf *pf, void *unused); */ +int thrd_create_name(thrd_t *thr, const char *name, thrd_start_t func, + void *arg); diff --git a/mk/re.mk b/mk/re.mk index 6d30c4a5f..f414938ea 100644 --- a/mk/re.mk +++ b/mk/re.mk @@ -500,6 +500,10 @@ CFLAGS += -DUSE_ZLIB LIBS += -lz endif +HAVE_THREADS := $(shell $(call CC_TEST,threads.h)) +ifneq ($(HAVE_THREADS),) +CFLAGS += -DHAVE_THREADS +endif HAVE_PTHREAD := $(shell $(call CC_TEST,pthread.h)) ifneq ($(HAVE_PTHREAD),) diff --git a/src/thread/mod.mk b/src/thread/mod.mk new file mode 100644 index 000000000..59ae74292 --- /dev/null +++ b/src/thread/mod.mk @@ -0,0 +1,9 @@ +ifdef HAVE_THREADS +SRCS += thread/thread.c +else ifdef HAVE_PTHREAD +SRCS += thread/thread.c +SRCS += thread/posix.c +else ifeq ($(OS),win32) +SRCS += thread/thread.c +SRCS += thread/win32.c +endif diff --git a/src/thread/posix.c b/src/thread/posix.c new file mode 100644 index 000000000..26e2ac916 --- /dev/null +++ b/src/thread/posix.c @@ -0,0 +1,167 @@ +/** + * @file posix.c Pthread C11 thread implementation + * + * Copyright (C) 2022 Sebastian Reimers + */ +#define _GNU_SOURCE 1 + +#include +#include +#include +#include + + +struct thread { + thrd_start_t func; + void *arg; +}; + + +static void *thrd_handler(void *p) +{ + struct thread th = *(struct thread *)p; + + mem_deref(p); + + return (void *)(intptr_t)th.func(th.arg); +} + + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) +{ + struct thread *th; + int err; + + if (!thr || !func) + return EINVAL; + + th = mem_alloc(sizeof(struct thread), NULL); + if (!th) + return ENOMEM; + + th->func = func; + th->arg = arg; + + err = pthread_create(thr, NULL, thrd_handler, th); + if (err) + mem_deref(th); + + return err; +} + + +int thrd_equal(thrd_t lhs, thrd_t rhs) +{ + return pthread_equal(lhs, rhs); +} + + +thrd_t thrd_current(void) +{ + return pthread_self(); +} + + +int thrd_detach(thrd_t thr) +{ + return pthread_detach(thr); +} + + +int thrd_join(thrd_t thr, int *res) +{ + void *code; + int err; + + err = pthread_join(thr, &code); + + if (res) + *res = (int)(intptr_t)code; + + return err; +} + + +void call_once(thrd_once_flag *flag, void (*func)(void)) +{ + pthread_once(flag, func); +} + + +void thrd_exit(int res) +{ + pthread_exit((void *)(intptr_t)res); +} + + +int cnd_init(cnd_t *cnd) +{ + if (!cnd) + return EINVAL; + + return pthread_cond_init(cnd, NULL); +} + + +int cnd_signal(cnd_t *cnd) +{ + if (!cnd) + return EINVAL; + + return pthread_cond_signal(cnd); +} + + +int cnd_wait(cnd_t *cnd, mtx_t *mtx) +{ + if (!cnd || !mtx) + return EINVAL; + + return pthread_cond_wait(cnd, mtx); +} + + +int mtx_init(mtx_t *mtx, int type) +{ + pthread_mutexattr_t attr; + + if (!mtx) + return thrd_error; + + if ((type & mtx_recursive) == 0) { + pthread_mutex_init(mtx, NULL); + return thrd_success; + } + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mtx, &attr); + pthread_mutexattr_destroy(&attr); + return thrd_success; +} + + +int mtx_lock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + return (pthread_mutex_lock(mtx) == 0) ? thrd_success : thrd_error; +} + + +int mtx_trylock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; +} + + +int mtx_unlock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + return (pthread_mutex_unlock(mtx) == 0) ? thrd_success : thrd_error; +} diff --git a/src/thread/thread.c b/src/thread/thread.c new file mode 100644 index 000000000..6d2b4c261 --- /dev/null +++ b/src/thread/thread.c @@ -0,0 +1,18 @@ +#include +#include +#include + + +int thrd_create_name(thrd_t *thr, const char *name, thrd_start_t func, + void *arg) +{ + int err; + (void)name; /* @TODO implement */ + + if (!thr || !func) + return EINVAL; + + err = thrd_create(thr, func, arg); + + return err; +} diff --git a/src/thread/win32.c b/src/thread/win32.c new file mode 100644 index 000000000..900506d87 --- /dev/null +++ b/src/thread/win32.c @@ -0,0 +1,209 @@ +/** + * @file posix.c WIN32 thread implementation + * + * Copyright (C) 2022 Sebastian Reimers + */ + +#include +#include +#include +#include +#include + + +struct thread { + thrd_start_t func; + void *arg; +}; + + +static unsigned __stdcall thrd_handler(void *p) +{ + struct thread th = *(struct thread *)p; + + mem_deref(p); + + return th.func(th.arg); +} + + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) +{ + struct thread *th; + int err = 0; + uintptr_t handle; + + if (!thr || !func) + return EINVAL; + + th = mem_alloc(sizeof(struct thread), NULL); + if (!th) + return ENOMEM; + + th->func = func; + th->arg = arg; + + handle = _beginthreadex(NULL, 0, thrd_handler, th, 0, NULL); + if (handle == 0) { + if (errno == EAGAIN || errno == EACCES) { + err = thrd_nomem; + goto out; + } + err = thrd_error; + goto out; + } + + *thr = (thrd_t)handle; +out: + if (err) + mem_deref(th); + + return err; +} + + +int thrd_equal(thrd_t lhs, thrd_t rhs) +{ + return GetThreadId(lhs) == GetThreadId(rhs); +} + + +thrd_t thrd_current(void) +{ + return GetCurrentThread(); +} + + +int thrd_detach(thrd_t thr) +{ + CloseHandle(thr); + return thrd_success; +} + + +int thrd_join(thrd_t thr, int *res) +{ + DWORD w, code; + + w = WaitForSingleObject(thr, INFINITE); + if (w != WAIT_OBJECT_0) + return thrd_error; + + if (res) { + if (!GetExitCodeThread(thr, &code)) { + CloseHandle(thr); + return thrd_error; + } + *res = (int)code; + } + + CloseHandle(thr); + return thrd_success; +} + + +struct impl_call_once_param { + void (*func)(void); +}; +static BOOL CALLBACK call_once_callback(PINIT_ONCE InitOnce, PVOID Parameter, + PVOID *Context) +{ + struct impl_call_once_param *param = Parameter; + (void)InitOnce; + (void)Context; + + param->func(); + + return true; +} + + +void call_once(thrd_once_flag *flag, void (*func)(void)) +{ + struct impl_call_once_param param; + param.func = func; + InitOnceExecuteOnce(flag, call_once_callback, (PVOID)¶m, NULL); +} + + +void thrd_exit(int res) +{ + _endthreadex((unsigned)res); +} + + +int cnd_init(cnd_t *cnd) +{ + if (!cnd) + return thrd_error; + + InitializeConditionVariable(cnd); + + return thrd_success; +} + + +int cnd_signal(cnd_t *cnd) +{ + if (!cnd) + return thrd_error; + + WakeConditionVariable(cnd); + + return thrd_success; +} + + +int cnd_wait(cnd_t *cnd, mtx_t *mtx) +{ + if (!cnd || !mtx) + return thrd_error; + + SleepConditionVariableCS(cnd, mtx, INFINITE); + + return thrd_success; +} + + +int mtx_init(mtx_t *mtx, int type) +{ + (void)type; + + if (!mtx) + return thrd_error; + + InitializeCriticalSection(mtx); + + return thrd_success; +} + + +int mtx_lock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + EnterCriticalSection(mtx); + + return thrd_success; +} + + +int mtx_trylock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + return TryEnterCriticalSection(mtx) ? thrd_success : thrd_busy; +} + + +int mtx_unlock(mtx_t *mtx) +{ + if (!mtx) + return thrd_error; + + LeaveCriticalSection(mtx); + + return thrd_success; +}