From 6bc474865731e5ed3fc9040c85ff2ff75ea89d0b Mon Sep 17 00:00:00 2001 From: Wander Lairson Costa Date: Tue, 15 Dec 2020 16:13:48 -0300 Subject: [PATCH] eina: Implement Eina_Thread for native windows The implementation design respects the fact that Eina_Thread is an uintptr_t. Thus we allocate the thread struct in the heap and return a pointer to it. As such, we store the created thread structure in the target thread TLS slot. For threads that were not created through eina API, in eina_thread_self we allocate a new structure, push it to the TLS slot and mark it to be freed on thread exit. Reviewers: jptiz, walac, vtorri, woohyun, lucas Reviewed By: jptiz, cedric Subscribers: raster, cedric, #reviewers, #committers, lucas Tags: #efl Differential Revision: https://phab.enlightenment.org/D12037 --- header_checks/meson.build | 3 +- src/lib/eina/Eina.h | 1 - src/lib/eina/eina_sched.c | 84 ------- src/lib/eina/eina_sched.h | 53 ---- src/lib/eina/eina_thread.c | 295 +--------------------- src/lib/eina/eina_thread.h | 36 +++ src/lib/eina/eina_thread_posix.c | 368 +++++++++++++++++++++++++++ src/lib/eina/eina_thread_win32.c | 397 ++++++++++++++++++++++++++++++ src/lib/eina/eina_win32_dllmain.c | 18 ++ src/lib/eina/meson.build | 6 +- src/tests/eina/eina_suite.c | 1 + src/tests/eina/eina_suite.h | 1 + src/tests/eina/eina_test_thread.c | 124 ++++++++++ src/tests/eina/meson.build | 1 + 14 files changed, 956 insertions(+), 432 deletions(-) delete mode 100644 src/lib/eina/eina_sched.c delete mode 100644 src/lib/eina/eina_sched.h create mode 100644 src/lib/eina/eina_thread_posix.c create mode 100644 src/lib/eina/eina_thread_win32.c create mode 100644 src/lib/eina/eina_win32_dllmain.c create mode 100644 src/tests/eina/eina_test_thread.c diff --git a/header_checks/meson.build b/header_checks/meson.build index a2a0e097eb..aaae83d62e 100644 --- a/header_checks/meson.build +++ b/header_checks/meson.build @@ -53,7 +53,8 @@ header_checks = [ 'features.h', 'langinfo.h', 'locale.h', - 'crt_externs.h' + 'crt_externs.h', + 'pthread.h', ] #### The below is logically broken diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h index 7bf09b739c..c7fd41eded 100644 --- a/src/lib/eina/Eina.h +++ b/src/lib/eina/Eina.h @@ -241,7 +241,6 @@ extern "C" { #include #include #include -#include #include #include #include diff --git a/src/lib/eina/eina_sched.c b/src/lib/eina/eina_sched.c deleted file mode 100644 index 3864054712..0000000000 --- a/src/lib/eina/eina_sched.c +++ /dev/null @@ -1,84 +0,0 @@ -/* EINA - EFL data type library - * Copyright (C) 2010 ProFUSION embedded systems - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; - * if not, see . - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#ifdef __linux__ -# include -# include -# include -# include -#endif - -#include "eina_sched.h" -#include "eina_log.h" - -#define RTNICENESS 1 -#define NICENESS 5 - -EINA_API void -eina_sched_prio_drop(void) -{ - struct sched_param param; - int pol, ret; - pthread_t pthread_id; - - pthread_id = pthread_self(); - ret = pthread_getschedparam(pthread_id, &pol, ¶m); - if (ret) - { - EINA_LOG_ERR("Unable to query sched parameters"); - return; - } - - if (EINA_UNLIKELY(pol == SCHED_RR || pol == SCHED_FIFO)) - { - param.sched_priority -= RTNICENESS; - - /* We don't change the policy */ - if (param.sched_priority < 1) - { - EINA_LOG_INFO("RT prio < 1, setting to 1 instead"); - param.sched_priority = 1; - } - - pthread_setschedparam(pthread_id, pol, ¶m); - } -# ifdef __linux__ - else - { - int prio; - errno = 0; - prio = getpriority(PRIO_PROCESS, 0); - if (errno == 0) - { - prio += NICENESS; - if (prio > 19) - { - EINA_LOG_INFO("Max niceness reached; keeping max (19)"); - prio = 19; - } - - setpriority(PRIO_PROCESS, 0, prio); - } - } -# endif -} diff --git a/src/lib/eina/eina_sched.h b/src/lib/eina/eina_sched.h deleted file mode 100644 index 77d90a31e4..0000000000 --- a/src/lib/eina/eina_sched.h +++ /dev/null @@ -1,53 +0,0 @@ -/* EINA - EFL data type library - * Copyright (C) 2010 ProFUSION embedded systems - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; - * if not, see . - */ - -/** - * @defgroup Schedule Schedule - * @ingroup Eina_Tools_Group - * - * @{ - * - * TODO: description - * - */ - -#ifndef EINA_SCHED_H_ -#define EINA_SCHED_H_ - -#include "eina_types.h" - - -/** - * @brief Lowers the priority of the current thread. - * - * @details It's used by worker threads so that they use up the background CPU and do not stall - * the main thread. If the current thread is running with real-time priority, we - * decrease our priority by @c RTNICENESS. This is done in a portable way. - * - * Otherwise, (we are running with the SCHED_OTHER policy) there's no portable way to - * set the nice level on the current thread. In Linux, it does work and it's the - * only one that is implemented as of now. In this case, the nice level is - * incremented on this thread by @c NICENESS. - */ -EINA_API void eina_sched_prio_drop(void); - -/** - * @} - */ - -#endif /* EINA_SCHED_H_ */ diff --git a/src/lib/eina/eina_thread.c b/src/lib/eina/eina_thread.c index 75623edb0a..abb04a1bf5 100644 --- a/src/lib/eina/eina_thread.c +++ b/src/lib/eina/eina_thread.c @@ -20,284 +20,15 @@ # include "config.h" #endif -#include +#include +#ifdef HAVE_PTHREAD_H +# include +#endif + +#include "eina_types.h" #include "eina_config.h" -#include "eina_lock.h" /* it will include pthread.h with proper flags */ #include "eina_thread.h" -#include "eina_sched.h" -#include "eina_cpu.h" - -/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ -#include "eina_safety_checks.h" - -#include "eina_debug_private.h" - -#include -#include -#ifndef _WIN32 -# include -#endif -# include - -#if defined(EINA_HAVE_PTHREAD_AFFINITY) || defined(EINA_HAVE_PTHREAD_SETNAME) -#ifndef __linux__ -#include -#define cpu_set_t cpuset_t -#endif -#endif - -static inline void * -_eina_thread_join(Eina_Thread t) -{ - void *ret = NULL; - int err = pthread_join((pthread_t)t, &ret); - - if (err == 0) return ret; - return NULL; -} - -static inline Eina_Bool -_eina_thread_create(Eina_Thread *t, int affinity, void *(*func)(void *data), void *data) -{ - int err; - pthread_attr_t attr; -#ifndef _WIN32 - sigset_t oldset, newset; -#endif - - if (pthread_attr_init(&attr) != 0) - { - return EINA_FALSE; - } - if (affinity >= 0) - { -#ifdef EINA_HAVE_PTHREAD_AFFINITY - cpu_set_t cpu; - - CPU_ZERO(&cpu); - CPU_SET(affinity, &cpu); - pthread_attr_setaffinity_np(&attr, sizeof(cpu), &cpu); -#endif - } - - /* setup initial locks */ -#ifndef _WIN32 - sigemptyset(&newset); - sigaddset(&newset, SIGPIPE); - sigaddset(&newset, SIGALRM); - sigaddset(&newset, SIGCHLD); - sigaddset(&newset, SIGUSR1); - sigaddset(&newset, SIGUSR2); - sigaddset(&newset, SIGHUP); - sigaddset(&newset, SIGQUIT); - sigaddset(&newset, SIGINT); - sigaddset(&newset, SIGTERM); -# ifdef SIGPWR - sigaddset(&newset, SIGPWR); -# endif - pthread_sigmask(SIG_BLOCK, &newset, &oldset); -#endif - err = pthread_create((pthread_t *)t, &attr, func, data); -#ifndef _WIN32 - pthread_sigmask(SIG_SETMASK, &oldset, NULL); -#endif - pthread_attr_destroy(&attr); - - if (err == 0) return EINA_TRUE; - - return EINA_FALSE; -} - -static inline Eina_Bool -_eina_thread_equal(Eina_Thread t1, Eina_Thread t2) -{ - return pthread_equal((pthread_t)t1, (pthread_t)t2); -} - -static inline Eina_Thread -_eina_thread_self(void) -{ - return (Eina_Thread)pthread_self(); -} - - -typedef struct _Eina_Thread_Call Eina_Thread_Call; -struct _Eina_Thread_Call -{ - Eina_Thread_Cb func; - const void *data; - - Eina_Thread_Priority prio; - int affinity; -}; - -static void * -_eina_internal_call(void *context) -{ - Eina_Thread_Call *c = context; - void *r; - pthread_t self; - - // Default this thread to not cancellable as per Eina documentation - eina_thread_cancellable_set(EINA_FALSE, NULL); - - EINA_THREAD_CLEANUP_PUSH(free, c); - - self = pthread_self(); - - if (c->prio == EINA_THREAD_IDLE) - { - struct sched_param params; - int min; -#ifdef SCHED_IDLE - int pol = SCHED_IDLE; -#else - int pol; - pthread_getschedparam(self, &pol, ¶ms); -#endif - min = sched_get_priority_min(pol); - params.sched_priority = min; - pthread_setschedparam(self, pol, ¶ms); - } - else if (c->prio == EINA_THREAD_BACKGROUND) - { - struct sched_param params; - int min, max; -#ifdef SCHED_BATCH - int pol = SCHED_BATCH; -#else - int pol; - pthread_getschedparam(self, &pol, ¶ms); -#endif - min = sched_get_priority_min(pol); - max = sched_get_priority_max(pol); - params.sched_priority = (max - min) / 2; - pthread_setschedparam(self, pol, ¶ms); - } -// do nothing for normal -// else if (c->prio == EINA_THREAD_NORMAL) -// { -// } - else if (c->prio == EINA_THREAD_URGENT) - { - struct sched_param params; - int max, pol; - - pthread_getschedparam(self, &pol, ¶ms); - max = sched_get_priority_max(pol); - params.sched_priority += 5; - if (params.sched_priority > max) params.sched_priority = max; - pthread_setschedparam(self, pol, ¶ms); - } - - _eina_debug_thread_add(&self); - EINA_THREAD_CLEANUP_PUSH(_eina_debug_thread_del, &self); - r = c->func((void*) c->data, eina_thread_self()); - EINA_THREAD_CLEANUP_POP(EINA_TRUE); - EINA_THREAD_CLEANUP_POP(EINA_TRUE); - - return r; -} - -EINA_API Eina_Thread -eina_thread_self(void) -{ - return _eina_thread_self(); -} - -EINA_API Eina_Bool -eina_thread_equal(Eina_Thread t1, Eina_Thread t2) -{ - return !!_eina_thread_equal(t1, t2); -} - -EINA_API Eina_Bool -eina_thread_create(Eina_Thread *t, - Eina_Thread_Priority prio, int affinity, - Eina_Thread_Cb func, const void *data) -{ - Eina_Thread_Call *c; - - EINA_SAFETY_ON_NULL_RETURN_VAL(t, EINA_FALSE); - EINA_SAFETY_ON_NULL_RETURN_VAL(func, EINA_FALSE); - - c = malloc(sizeof (Eina_Thread_Call)); - if (!c) return EINA_FALSE; - - c->func = func; - c->data = data; - c->prio = prio; - c->affinity = affinity; - - // valgrind complains c is lost - but it's not - it is handed to the - // child thread to be freed when c->func returns in _eina_internal_call(). - if (_eina_thread_create(t, affinity, _eina_internal_call, c)) - return EINA_TRUE; - - free(c); - return EINA_FALSE; -} - -EINA_API void * -eina_thread_join(Eina_Thread t) -{ - return _eina_thread_join(t); -} - -EINA_API Eina_Bool -eina_thread_name_set(Eina_Thread t, const char *name) -{ -#ifdef EINA_HAVE_PTHREAD_SETNAME - char buf[16]; - if (name) - { - strncpy(buf, name, 15); - buf[15] = 0; - } - else buf[0] = 0; -#ifndef __linux__ - pthread_set_name_np((pthread_t)t, buf); - return EINA_TRUE; -#else - if (pthread_setname_np((pthread_t)t, buf) == 0) return EINA_TRUE; -#endif -#else - (void)t; - (void)name; -#endif - return EINA_FALSE; -} - -EINA_API Eina_Bool -eina_thread_cancel(Eina_Thread t) -{ - if (!t) return EINA_FALSE; - return pthread_cancel((pthread_t)t) == 0; -} - -EINA_API Eina_Bool -eina_thread_cancellable_set(Eina_Bool cancellable, Eina_Bool *was_cancellable) -{ - int state = cancellable ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE; - int old = 0; - int r; - - /* enforce deferred in case users changed to asynchronous themselves */ - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old); - - r = pthread_setcancelstate(state, &old); - if (was_cancellable && r == 0) - *was_cancellable = (old == PTHREAD_CANCEL_ENABLE); - - return r == 0; -} - -EINA_API void -eina_thread_cancel_checkpoint(void) -{ - pthread_testcancel(); -} EINA_API void * eina_thread_cancellable_run(Eina_Thread_Cancellable_Run_Cb cb, Eina_Free_Cb cleanup_cb, void *data) @@ -312,17 +43,3 @@ eina_thread_cancellable_run(Eina_Thread_Cancellable_Run_Cb cb, Eina_Free_Cb clea eina_thread_cancellable_set(old, NULL); return ret; } - -EINA_API const void *EINA_THREAD_JOIN_CANCELED = PTHREAD_CANCELED; - -Eina_Bool -eina_thread_init(void) -{ - return EINA_TRUE; -} - -Eina_Bool -eina_thread_shutdown(void) -{ - return EINA_TRUE; -} diff --git a/src/lib/eina/eina_thread.h b/src/lib/eina/eina_thread.h index d0ca8138c2..9c0b93bf1f 100644 --- a/src/lib/eina/eina_thread.h +++ b/src/lib/eina/eina_thread.h @@ -51,6 +51,12 @@ typedef uintptr_t Eina_Thread; */ typedef void *(*Eina_Thread_Cb)(void *data, Eina_Thread t); +/** + * @typedef Eina_Thread_Cleanup_Cb + * Type for the definition of a thread cleanup function + */ +typedef void (*Eina_Thread_Cleanup_Cb) (void *data); + /** * @typedef Eina_Thread_Priority * Type to enumerate different thread priorities @@ -248,8 +254,16 @@ EINA_API void eina_thread_cancel_checkpoint(void); * * @since 1.19 */ +#ifdef _WIN32 +EINA_API Eina_Bool +eina_thread_cleanup_push(Eina_Thread_Cleanup_Cb fn, void *data); + +#define EINA_THREAD_CLEANUP_PUSH(cleanup, data) \ + eina_thread_cleanup_push(cleanup, data) +#else #define EINA_THREAD_CLEANUP_PUSH(cleanup, data) \ pthread_cleanup_push(cleanup, data) +#endif /** * @def EINA_THREAD_CLEANUP_POP(exec_cleanup) @@ -278,8 +292,16 @@ EINA_API void eina_thread_cancel_checkpoint(void); * * @since 1.19 */ +#ifdef _WIN32 +EINA_API void +eina_thread_cleanup_pop(int execute); + +#define EINA_THREAD_CLEANUP_POP(exec_cleanup) \ + eina_thread_cleanup_pop(exec_cleanup) +#else #define EINA_THREAD_CLEANUP_POP(exec_cleanup) \ pthread_cleanup_pop(exec_cleanup) +#endif /** * @typedef Eina_Thread_Cancellable_Run_Cb @@ -333,6 +355,20 @@ typedef void *(*Eina_Thread_Cancellable_Run_Cb)(void *data); */ EINA_API void *eina_thread_cancellable_run(Eina_Thread_Cancellable_Run_Cb cb, Eina_Free_Cb cleanup_cb, void *data); +/** + * @brief Lowers the priority of the current thread. + * + * @details It's used by worker threads so that they use up the background CPU and do not stall + * the main thread. If the current thread is running with real-time priority, we + * decrease our priority by @c RTNICENESS. This is done in a portable way. + * + * Otherwise, (we are running with the SCHED_OTHER policy) there's no portable way to + * set the nice level on the current thread. In Linux, it does work and it's the + * only one that is implemented as of now. In this case, the nice level is + * incremented on this thread by @c NICENESS. + */ +EINA_API void eina_sched_prio_drop(void); + /** * @} */ diff --git a/src/lib/eina/eina_thread_posix.c b/src/lib/eina/eina_thread_posix.c new file mode 100644 index 0000000000..da793bd48d --- /dev/null +++ b/src/lib/eina/eina_thread_posix.c @@ -0,0 +1,368 @@ +/* EINA - EFL data type library + * Copyright (C) 2012 Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "eina_config.h" +#include "eina_lock.h" /* it will include pthread.h with proper flags */ +#include "eina_thread.h" +#include "eina_cpu.h" + +/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ +#include "eina_safety_checks.h" + +#include "eina_debug_private.h" + +#include +#include +#ifndef _WIN32 +# include +#endif +# include + +#if defined(EINA_HAVE_PTHREAD_AFFINITY) || defined(EINA_HAVE_PTHREAD_SETNAME) +#ifndef __linux__ +#include +#define cpu_set_t cpuset_t +#endif +#endif + +#ifdef __linux__ +# include +# include +# include +#endif + +#include "eina_log.h" + +#define RTNICENESS 1 +#define NICENESS 5 + + +static inline void * +_eina_thread_join(Eina_Thread t) +{ + void *ret = NULL; + int err = pthread_join((pthread_t)t, &ret); + + if (err == 0) return ret; + return NULL; +} + +static inline Eina_Bool +_eina_thread_create(Eina_Thread *t, int affinity, void *(*func)(void *data), void *data) +{ + int err; + pthread_attr_t attr; + sigset_t oldset, newset; + + if (pthread_attr_init(&attr) != 0) + { + return EINA_FALSE; + } + if (affinity >= 0) + { +#ifdef EINA_HAVE_PTHREAD_AFFINITY + cpu_set_t cpu; + + CPU_ZERO(&cpu); + CPU_SET(affinity, &cpu); + pthread_attr_setaffinity_np(&attr, sizeof(cpu), &cpu); +#endif + } + + /* setup initial locks */ + sigemptyset(&newset); + sigaddset(&newset, SIGPIPE); + sigaddset(&newset, SIGALRM); + sigaddset(&newset, SIGCHLD); + sigaddset(&newset, SIGUSR1); + sigaddset(&newset, SIGUSR2); + sigaddset(&newset, SIGHUP); + sigaddset(&newset, SIGQUIT); + sigaddset(&newset, SIGINT); + sigaddset(&newset, SIGTERM); +# ifdef SIGPWR + sigaddset(&newset, SIGPWR); +# endif + pthread_sigmask(SIG_BLOCK, &newset, &oldset); + err = pthread_create((pthread_t *)t, &attr, func, data); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy(&attr); + + if (err == 0) return EINA_TRUE; + + return EINA_FALSE; +} + +static inline Eina_Bool +_eina_thread_equal(Eina_Thread t1, Eina_Thread t2) +{ + return pthread_equal((pthread_t)t1, (pthread_t)t2); +} + +static inline Eina_Thread +_eina_thread_self(void) +{ + return (Eina_Thread)pthread_self(); +} + + +typedef struct _Eina_Thread_Call Eina_Thread_Call; +struct _Eina_Thread_Call +{ + Eina_Thread_Cb func; + const void *data; + + Eina_Thread_Priority prio; + int affinity; +}; + +static void * +_eina_internal_call(void *context) +{ + Eina_Thread_Call *c = context; + void *r; + pthread_t self; + + // Default this thread to not cancellable as per Eina documentation + eina_thread_cancellable_set(EINA_FALSE, NULL); + + EINA_THREAD_CLEANUP_PUSH(free, c); + + self = pthread_self(); + + if (c->prio == EINA_THREAD_IDLE) + { + struct sched_param params; + int min; +#ifdef SCHED_IDLE + int pol = SCHED_IDLE; +#else + int pol; + pthread_getschedparam(self, &pol, ¶ms); +#endif + min = sched_get_priority_min(pol); + params.sched_priority = min; + pthread_setschedparam(self, pol, ¶ms); + } + else if (c->prio == EINA_THREAD_BACKGROUND) + { + struct sched_param params; + int min, max; +#ifdef SCHED_BATCH + int pol = SCHED_BATCH; +#else + int pol; + pthread_getschedparam(self, &pol, ¶ms); +#endif + min = sched_get_priority_min(pol); + max = sched_get_priority_max(pol); + params.sched_priority = (max - min) / 2; + pthread_setschedparam(self, pol, ¶ms); + } +// do nothing for normal +// else if (c->prio == EINA_THREAD_NORMAL) +// { +// } + else if (c->prio == EINA_THREAD_URGENT) + { + struct sched_param params; + int max, pol; + + pthread_getschedparam(self, &pol, ¶ms); + max = sched_get_priority_max(pol); + params.sched_priority += 5; + if (params.sched_priority > max) params.sched_priority = max; + pthread_setschedparam(self, pol, ¶ms); + } + + _eina_debug_thread_add(&self); + EINA_THREAD_CLEANUP_PUSH(_eina_debug_thread_del, &self); + r = c->func((void*) c->data, eina_thread_self()); + EINA_THREAD_CLEANUP_POP(EINA_TRUE); + EINA_THREAD_CLEANUP_POP(EINA_TRUE); + + return r; +} + +EINA_API Eina_Thread +eina_thread_self(void) +{ + return _eina_thread_self(); +} + +EINA_API Eina_Bool +eina_thread_equal(Eina_Thread t1, Eina_Thread t2) +{ + return !!_eina_thread_equal(t1, t2); +} + +EINA_API Eina_Bool +eina_thread_create(Eina_Thread *t, + Eina_Thread_Priority prio, int affinity, + Eina_Thread_Cb func, const void *data) +{ + Eina_Thread_Call *c; + + EINA_SAFETY_ON_NULL_RETURN_VAL(t, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(func, EINA_FALSE); + + c = malloc(sizeof (Eina_Thread_Call)); + if (!c) return EINA_FALSE; + + c->func = func; + c->data = data; + c->prio = prio; + c->affinity = affinity; + + // valgrind complains c is lost - but it's not - it is handed to the + // child thread to be freed when c->func returns in _eina_internal_call(). + if (_eina_thread_create(t, affinity, _eina_internal_call, c)) + return EINA_TRUE; + + free(c); + return EINA_FALSE; +} + +EINA_API void * +eina_thread_join(Eina_Thread t) +{ + return _eina_thread_join(t); +} + +EINA_API Eina_Bool +eina_thread_name_set(Eina_Thread t, const char *name) +{ +#ifdef EINA_HAVE_PTHREAD_SETNAME + char buf[16]; + if (name) + { + strncpy(buf, name, 15); + buf[15] = 0; + } + else buf[0] = 0; +#ifndef __linux__ + pthread_set_name_np((pthread_t)t, buf); + return EINA_TRUE; +#else + if (pthread_setname_np((pthread_t)t, buf) == 0) return EINA_TRUE; +#endif +#else + (void)t; + (void)name; +#endif + return EINA_FALSE; +} + +EINA_API Eina_Bool +eina_thread_cancel(Eina_Thread t) +{ + if (!t) return EINA_FALSE; + return pthread_cancel((pthread_t)t) == 0; +} + +EINA_API Eina_Bool +eina_thread_cancellable_set(Eina_Bool cancellable, Eina_Bool *was_cancellable) +{ + int state = cancellable ? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE; + int old = 0; + int r; + + /* enforce deferred in case users changed to asynchronous themselves */ + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old); + + r = pthread_setcancelstate(state, &old); + if (was_cancellable && r == 0) + *was_cancellable = (old == PTHREAD_CANCEL_ENABLE); + + return r == 0; +} + +EINA_API void +eina_thread_cancel_checkpoint(void) +{ + pthread_testcancel(); +} + +EINA_API const void *EINA_THREAD_JOIN_CANCELED = PTHREAD_CANCELED; + +EINA_API void +eina_sched_prio_drop(void) +{ + struct sched_param param; + int pol, ret; + pthread_t pthread_id; + + pthread_id = pthread_self(); + ret = pthread_getschedparam(pthread_id, &pol, ¶m); + if (ret) + { + EINA_LOG_ERR("Unable to query sched parameters"); + return; + } + + if (EINA_UNLIKELY(pol == SCHED_RR || pol == SCHED_FIFO)) + { + param.sched_priority -= RTNICENESS; + + /* We don't change the policy */ + if (param.sched_priority < 1) + { + EINA_LOG_INFO("RT prio < 1, setting to 1 instead"); + param.sched_priority = 1; + } + + pthread_setschedparam(pthread_id, pol, ¶m); + } +# ifdef __linux__ + else + { + int prio; + errno = 0; + prio = getpriority(PRIO_PROCESS, 0); + if (errno == 0) + { + prio += NICENESS; + if (prio > 19) + { + EINA_LOG_INFO("Max niceness reached; keeping max (19)"); + prio = 19; + } + + setpriority(PRIO_PROCESS, 0, prio); + } + } +# endif +} + +EINA_API Eina_Bool +eina_thread_init(void) +{ + return EINA_TRUE; +} + +EINA_API Eina_Bool +eina_thread_shutdown(void) +{ + return EINA_TRUE; +} diff --git a/src/lib/eina/eina_thread_win32.c b/src/lib/eina/eina_thread_win32.c new file mode 100644 index 0000000000..8f5bf3c1ff --- /dev/null +++ b/src/lib/eina/eina_thread_win32.c @@ -0,0 +1,397 @@ +/* EINA - EFL data type library + * Copyright (C) 2020 Expertise Solutions Cons em Inf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "eina_types.h" +#include "eina_config.h" +#include "eina_array.h" +#include "eina_thread.h" +#include "eina_main.h" +#include "eina_debug_private.h" +#include "eina_log.h" + +#include +#include +#include + +#define RTNICENESS 1 +#define NICENESS 5 + +/* + * The underlying type of Eina_Thread + */ +struct Thread +{ + CRITICAL_SECTION cancel_lock; /* mutex to protect the cancel handle */ + char name[16]; /* the thread name */ + HANDLE handle; /* thread handle */ + void *data; /* on entry, the thread function argument, on exit, the return value */ + Eina_Thread_Cb fn; /* the thread function */ + Eina_Array *cleanup_fn; + Eina_Array *cleanup_arg; + unsigned id; /* thread id */ + Eina_Bool free_on_exit; /* free the structure when thread exit */ + volatile Eina_Bool cancel; /* the cancel event handle */ + volatile Eina_Bool cancellable; /* is cancel enabled? */ +}; + +typedef struct Thread Thread_t; + +/* + * This TLS stores the Eina_Thread for the current thread + */ +static DWORD tls_thread_self = 0; + +static Thread_t main_thread = { 0 }; + +/* + * If we alloc'ed the Thread_t in eina_thread_self, we set + * free_on_exit flag to true, we then free it here + */ +void +free_thread(void) +{ + Thread_t *t = TlsGetValue(tls_thread_self); + if (t && t->free_on_exit) + { + if (t) eina_array_free(t->cleanup_fn); + if (t) eina_array_free(t->cleanup_arg); + free(t); + } +} + +static unsigned +thread_fn(void *arg) +{ + Thread_t *thr = arg; + TlsSetValue(tls_thread_self, thr); + _eina_debug_thread_add(&thr); + EINA_THREAD_CLEANUP_PUSH(_eina_debug_thread_del, &thr); + thr->data = thr->fn(thr->data, (Eina_Thread) thr); + EINA_THREAD_CLEANUP_POP(EINA_TRUE); + return 0; +} + +EINA_API Eina_Thread +eina_thread_self(void) +{ + Thread_t *self = TlsGetValue(tls_thread_self); + /* + * If self is NULL this means + * 1) This function was called before eina_thread_init + * 2) This thread wasn't created by eina_thread_create + * + * In either case we alloc a new Thread struct and return + * it. + */ + if (!self) + { + self = calloc(1, sizeof(*self)); + self->handle = GetCurrentThread(); + self->id = GetCurrentThreadId(); + self->free_on_exit = EINA_TRUE; + self->cleanup_fn = eina_array_new(4); + self->cleanup_arg = eina_array_new(4); + if (tls_thread_self) + TlsSetValue(tls_thread_self, self); + } + return (Eina_Thread) self; +} + +EINA_API Eina_Bool +eina_thread_equal(Eina_Thread t1, Eina_Thread t2) +{ + return ((Thread_t *) t1)->id == ((Thread_t *) t2)->id; +} + +EINA_API Eina_Bool +eina_thread_create(Eina_Thread *t, Eina_Thread_Priority prio, + int affinity, Eina_Thread_Cb func, const void *data) +{ + Thread_t *thr = calloc(1, sizeof(Thread_t)); + if (!thr) + return EINA_FALSE; + + thr->data = (void *) data; + thr->fn = func; + + thr->handle = (HANDLE) _beginthreadex(NULL, 0, thread_fn, thr, CREATE_SUSPENDED, &thr->id); + if (!thr->handle) + goto fail; + + int priority; + switch (prio) + { + case EINA_THREAD_URGENT: + priority = THREAD_PRIORITY_HIGHEST; + break; + case EINA_THREAD_BACKGROUND: + priority = THREAD_PRIORITY_BELOW_NORMAL; + break; + case EINA_THREAD_IDLE: + priority = THREAD_PRIORITY_IDLE; + break; + default: + priority = THREAD_PRIORITY_NORMAL; + } + + if (!SetThreadPriority(thr->handle, priority)) + goto fail; + + if ((affinity >= 0) && (!SetThreadAffinityMask(thr->handle, 1 << affinity))) + goto fail; + + thr->id = GetThreadId(thr->handle); + if (!thr->id) + goto fail; + + thr->cleanup_fn = eina_array_new(4); + thr->cleanup_arg = eina_array_new(4); + if ((!thr->cleanup_fn) || (!thr->cleanup_arg)) + goto fail; + + InitializeCriticalSection(&thr->cancel_lock); + + if (!ResumeThread(thr->handle)) + goto cs_fail; + + GetModuleFileNameA(NULL, thr->name, sizeof(thr->name)); + *t = (Eina_Thread) thr; + return EINA_TRUE; + +cs_fail: + DeleteCriticalSection(&thr->cancel_lock); +fail: + if (thr) + { + if (thr->handle) CloseHandle(thr->handle); + if (thr->cleanup_fn) eina_array_free(thr->cleanup_fn); + if (thr->cleanup_arg) eina_array_free(thr->cleanup_arg); + free(thr); + } + return EINA_FALSE; +} + +EINA_API void * +eina_thread_join(Eina_Thread t) +{ + void *data; + Thread_t *thr = (Thread_t *) t; + + if (WAIT_OBJECT_0 == WaitForSingleObject(thr->handle, INFINITE)) + data = thr->data; + else + data = NULL; + + DeleteCriticalSection(&thr->cancel_lock); + CloseHandle(thr->handle); + eina_array_free(thr->cleanup_fn); + eina_array_free(thr->cleanup_arg); + free(thr); + + return data; +} + +EINA_API Eina_Bool +eina_thread_name_set(Eina_Thread t, const char *name) +{ + Thread_t *thr = (Thread_t *) t; + strncpy(thr->name, name, sizeof(thr->name)); + thr->name[sizeof(thr->name)-1] = '\0'; + return EINA_TRUE; +} + +EINA_API Eina_Bool +eina_thread_cancel(Eina_Thread t) +{ + Eina_Bool ret = EINA_FALSE; + Thread_t *thr = (Thread_t *) t; + + if (thr) + { + EnterCriticalSection(&thr->cancel_lock); + if (thr->cancellable) + { + thr->cancel = EINA_TRUE; + ret = EINA_TRUE; + } + LeaveCriticalSection(&thr->cancel_lock); + } + return ret; +} + +EINA_API Eina_Bool +eina_thread_cancellable_set(Eina_Bool cancellable, Eina_Bool *was_cancellable) +{ + Thread_t *t = (Thread_t *) eina_thread_self(); + + EnterCriticalSection(&t->cancel_lock); + if (was_cancellable) *was_cancellable = t->cancellable; + t->cancellable = cancellable; + LeaveCriticalSection(&t->cancel_lock); + + return EINA_TRUE; +} + +EINA_API void +eina_thread_cancel_checkpoint(void) +{ + Eina_Bool cancel; + Thread_t *t = (Thread_t *) eina_thread_self(); + + EnterCriticalSection(&t->cancel_lock); + cancel = t->cancellable && t->cancel; + LeaveCriticalSection(&t->cancel_lock); + + if (cancel) + { + t->data = (void *) EINA_THREAD_JOIN_CANCELED; + while (eina_array_count(t->cleanup_fn)) + { + Eina_Thread_Cleanup_Cb fn = (Eina_Thread_Cleanup_Cb) eina_array_pop(t->cleanup_fn); + void *arg = eina_array_pop(t->cleanup_arg); + + if (fn) + fn(arg); + } + + ExitThread(0); + } +} + +EINA_API Eina_Bool +eina_thread_cleanup_push(Eina_Thread_Cleanup_Cb fn, void *data) +{ + Thread_t *t = TlsGetValue(tls_thread_self); + assert(t); + + if (!eina_array_push(t->cleanup_fn, fn)) + return EINA_FALSE; + + if (!eina_array_push(t->cleanup_arg, data)) + { + eina_array_pop(t->cleanup_fn); + return EINA_FALSE; + } + + return EINA_TRUE; +} + +EINA_API void +eina_thread_cleanup_pop(int execute) +{ + Thread_t *t = TlsGetValue(tls_thread_self); + assert(t); + + if (eina_array_count(t->cleanup_fn)) + { + Eina_Thread_Cleanup_Cb fn = (Eina_Thread_Cleanup_Cb) eina_array_pop(t->cleanup_fn); + void *arg = eina_array_pop(t->cleanup_arg); + + if (execute && fn) + fn(arg); + } +} + +EINA_API const void *EINA_THREAD_JOIN_CANCELED = (void *) -1L; + +void +eina_sched_prio_drop(void) +{ + Thread_t *thread; + int sched_priority; + + thread = (Thread_t *) eina_thread_self(); + + sched_priority = GetThreadPriority(thread->handle); + + if (EINA_UNLIKELY(sched_priority == THREAD_PRIORITY_TIME_CRITICAL)) + { + sched_priority -= RTNICENESS; + + /* We don't change the policy */ + if (sched_priority < 1) + { + EINA_LOG_INFO("RT prio < 1, setting to 1 instead"); + sched_priority = 1; + } + if (!SetThreadPriority(thread->handle, sched_priority)) + { + EINA_LOG_ERR("Unable to query sched parameters"); + } + } + else + { + sched_priority += NICENESS; + + /* We don't change the policy */ + if (sched_priority > THREAD_PRIORITY_TIME_CRITICAL) + { + EINA_LOG_INFO("Max niceness reached; keeping max (THREAD_PRIORITY_TIME_CRITICAL)"); + sched_priority = THREAD_PRIORITY_TIME_CRITICAL; + } + if (!SetThreadPriority(thread->handle, sched_priority)) + { + EINA_LOG_ERR("Unable to query sched parameters"); + } + } +} + +EINA_API Eina_Bool +eina_thread_init(void) +{ + if (!eina_main_loop_is()) + return EINA_FALSE; + + tls_thread_self = TlsAlloc(); + if (TLS_OUT_OF_INDEXES == tls_thread_self) + return EINA_FALSE; + + if (!TlsSetValue(tls_thread_self, &main_thread)) + { + assert(0); + TlsFree(tls_thread_self); + return EINA_FALSE; + } + + main_thread.cancellable = EINA_FALSE; + main_thread.cancel = EINA_FALSE; + main_thread.handle = GetCurrentThread(); + main_thread.id = GetCurrentThreadId(); + + InitializeCriticalSection(&main_thread.cancel_lock); + main_thread.cleanup_fn = eina_array_new(2); + main_thread.cleanup_arg = eina_array_new(2); + + GetModuleFileNameA(NULL, main_thread.name, sizeof(main_thread.name)/sizeof(main_thread.name[0])); + + return EINA_TRUE; +} + +EINA_API Eina_Bool +eina_thread_shutdown(void) +{ + DeleteCriticalSection(&main_thread.cancel_lock); + eina_array_free(main_thread.cleanup_fn); + eina_array_free(main_thread.cleanup_arg); + TlsFree(tls_thread_self); + return EINA_TRUE; +} diff --git a/src/lib/eina/eina_win32_dllmain.c b/src/lib/eina/eina_win32_dllmain.c new file mode 100644 index 0000000000..3285aebebb --- /dev/null +++ b/src/lib/eina/eina_win32_dllmain.c @@ -0,0 +1,18 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "eina_config.h" +#include "eina_types.h" +#include + +void free_thread(void); + +BOOL WINAPI +DllMain(HINSTANCE inst EINA_UNUSED, WORD reason, PVOID reserved EINA_UNUSED) +{ + if (DLL_THREAD_DETACH == reason) + free_thread(); + + return TRUE; +} diff --git a/src/lib/eina/meson.build b/src/lib/eina/meson.build index 45d0781680..21d35493e5 100644 --- a/src/lib/eina/meson.build +++ b/src/lib/eina/meson.build @@ -66,7 +66,6 @@ public_sub_headers = [ 'eina_main.h', 'eina_cpu.h', 'eina_inline_cpu.x', -'eina_sched.h', 'eina_tiler.h', 'eina_hamster.h', 'eina_matrixsparse.h', @@ -171,7 +170,6 @@ eina_src = files([ 'eina_rbtree.c', 'eina_rectangle.c', 'eina_safety_checks.c', -'eina_sched.c', 'eina_share_common.c', 'eina_simple_xml_parser.c', 'eina_str.c', @@ -203,10 +201,10 @@ eina_src = files([ ]) + eina_mp_sources if sys_windows == true - eina_src += files('eina_file_win32.c') + eina_src += files('eina_file_win32.c', 'eina_win32_dllmain.c', 'eina_thread_win32.c') eina_src += 'eina_fnmatch.c' else - eina_src += files('eina_file.c') + eina_src += files('eina_file.c', 'eina_thread_posix.c') endif eina_config = configuration_data() diff --git a/src/tests/eina/eina_suite.c b/src/tests/eina/eina_suite.c index 01ed82a532..7561878c8b 100644 --- a/src/tests/eina/eina_suite.c +++ b/src/tests/eina/eina_suite.c @@ -91,6 +91,7 @@ static const Efl_Test_Case etc[] = { { "Vpath", eina_test_vpath }, { "debug", eina_test_debug }, { "Abstract Content", eina_test_abstract_content }, + { "thread", eina_test_thread }, { NULL, NULL } }; diff --git a/src/tests/eina/eina_suite.h b/src/tests/eina/eina_suite.h index 84d6e60516..c9e5476ae7 100644 --- a/src/tests/eina/eina_suite.h +++ b/src/tests/eina/eina_suite.h @@ -79,5 +79,6 @@ void eina_test_slstr(TCase *tc); void eina_test_vpath(TCase *tc); void eina_test_debug(TCase *tc); void eina_test_abstract_content(TCase *tc); +void eina_test_thread(TCase *tc); #endif /* EINA_SUITE_H_ */ diff --git a/src/tests/eina/eina_test_thread.c b/src/tests/eina/eina_test_thread.c new file mode 100644 index 0000000000..2b35907db7 --- /dev/null +++ b/src/tests/eina/eina_test_thread.c @@ -0,0 +1,124 @@ +/* EINA - EFL data type library + * Copyright (C) 2020 Expertise Solutions Cons em Inf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_UNISTD +# include +#endif + +#ifdef _WIN32 +# include /* mkdir */ +#endif + +#include +#include "eina_suite.h" + +static void +thread_cleanup_fn(void *arg) +{ + *(int *) arg = 1; +} + +static void * +thread_fn_execute(void *arg, Eina_Thread t EINA_UNUSED) +{ + EINA_THREAD_CLEANUP_PUSH(thread_cleanup_fn, arg); + EINA_THREAD_CLEANUP_POP(1); + return NULL; +} + +static void * +thread_fn_skip(void *arg, Eina_Thread t EINA_UNUSED) +{ + EINA_THREAD_CLEANUP_PUSH(thread_cleanup_fn, arg); + EINA_THREAD_CLEANUP_POP(0); + return NULL; +} + +static void * +thread_fn_cancel(void *arg, Eina_Thread t EINA_UNUSED) +{ + Eina_Condition *cond = arg; + + ck_assert(eina_thread_cancellable_set(EINA_TRUE, NULL)); + ck_assert(eina_condition_signal(cond)); + + for (size_t i = 0; i < 100; ++i) + { + eina_thread_cancel_checkpoint(); +#ifdef _WIN32 + Sleep(100); +#else + usleep(100 * 1000); +#endif + } + + return NULL; +} + +EFL_START_TEST(eina_thread_test_cleanup_execute) +{ + Eina_Thread t; + int flag = 0; + ck_assert(eina_thread_create(&t, EINA_THREAD_NORMAL, -1, thread_fn_execute, &flag)); + eina_thread_join(t); + ck_assert_uint_eq(flag, 1); +} +EFL_END_TEST + +EFL_START_TEST(eina_thread_test_cleanup_skip) +{ + Eina_Thread t; + int flag = 2; + ck_assert(eina_thread_create(&t, EINA_THREAD_NORMAL, -1, thread_fn_skip, &flag)); + eina_thread_join(t); + ck_assert_uint_eq(flag, 2); +} +EFL_END_TEST + +EFL_START_TEST(eina_thread_test_cancel) +{ + Eina_Thread t; + Eina_Lock mutex; + Eina_Condition cond; + + ck_assert(eina_lock_new(&mutex)); + ck_assert(eina_condition_new(&cond, &mutex)); + + ck_assert(eina_thread_create(&t, EINA_THREAD_NORMAL, -1, thread_fn_cancel, &cond)); + ck_assert(eina_lock_take(&mutex)); + ck_assert(eina_condition_wait(&cond)); + ck_assert(eina_thread_cancel(t)); + ck_assert_ptr_eq(eina_thread_join(t), EINA_THREAD_JOIN_CANCELED); + + eina_condition_free(&cond); + eina_lock_free(&mutex); +} +EFL_END_TEST + +void +eina_test_thread(TCase *tc) +{ + tcase_add_test(tc, eina_thread_test_cleanup_skip); + tcase_add_test(tc, eina_thread_test_cleanup_execute); + tcase_add_test(tc, eina_thread_test_cancel); +} diff --git a/src/tests/eina/meson.build b/src/tests/eina/meson.build index 18c8f91ced..0d09819e74 100644 --- a/src/tests/eina/meson.build +++ b/src/tests/eina/meson.build @@ -56,6 +56,7 @@ eina_test_src = files( 'eina_test_slstr.c', 'eina_test_vpath.c', 'eina_test_abstract_content.c', +'eina_test_thread.c', )