* ecore_thread_reschedule().
* @li The function is prepared to leave early by checking if
* ecore_thread_check() returns @c EINA_TRUE.
+
+ * @li The function marks the thread as cancellable using
+ * eina_thread_cancellable_set(), allowing the thread to be terminated
+ * at explicit cancellation points defined with
+ * eina_thread_cancel_checkpoint() or with syscalls mentioned at
+ * man:pthreads(7). This allows blocking operations such as network or
+ * disk access to be stopped without polling
+ * ecore_thread_check(). Note that a cancelled thread may leak
+ * resources if no cleanup function is registered with
+ * EINA_THREAD_CLEANUP_PUSH(). Consider running such code using
+ * eina_thread_cancellable_run().
*
* The user function can cancel itself by calling ecore_thread_cancel(), but
* it should always use the ::Ecore_Thread handle passed to it and never
* returns @c EINA_TRUE or after the @c func_cancel callback returns.
*
* @see ecore_thread_check()
+ * @see eina_thread_cancellable_run()
*/
EAPI Eina_Bool ecore_thread_cancel(Ecore_Thread *thread);
static void
_ecore_thread_join(PH(thread))
{
+ DBG("joining thread=%" PRIu64, (uint64_t)thread);
PHJ(thread);
}
}
static void
+_ecore_short_job_cleanup(void *data)
+{
+ Ecore_Pthread_Worker *work = data;
+
+ DBG("cleanup work=%p, thread=%" PRIu64, work, (uint64_t)work->self);
+
+ SLKL(_ecore_running_job_mutex);
+ _ecore_running_job = eina_list_remove(_ecore_running_job, work);
+ SLKU(_ecore_running_job_mutex);
+
+ if (work->reschedule)
+ {
+ work->reschedule = EINA_FALSE;
+
+ SLKL(_ecore_pending_job_threads_mutex);
+ _ecore_pending_job_threads = eina_list_append(_ecore_pending_job_threads, work);
+ SLKU(_ecore_pending_job_threads_mutex);
+ }
+ else
+ {
+ ecore_main_loop_thread_safe_call_async(_ecore_thread_handler, work);
+ }
+}
+
+static void
_ecore_short_job(PH(thread))
{
Ecore_Pthread_Worker *work;
cancel = work->cancel;
SLKU(work->cancel_mutex);
work->self = thread;
+
+ EINA_THREAD_CLEANUP_PUSH(_ecore_short_job_cleanup, work);
if (!cancel)
work->u.short_run.func_blocking((void *) work->data, (Ecore_Thread*) work);
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+}
+
+static void
+_ecore_feedback_job_cleanup(void *data)
+{
+ Ecore_Pthread_Worker *work = data;
+
+ DBG("cleanup work=%p, thread=%" PRIu64, work, (uint64_t)work->self);
SLKL(_ecore_running_job_mutex);
_ecore_running_job = eina_list_remove(_ecore_running_job, work);
SLKU(_ecore_running_job_mutex);
-
+
if (work->reschedule)
{
work->reschedule = EINA_FALSE;
-
+
SLKL(_ecore_pending_job_threads_mutex);
- _ecore_pending_job_threads = eina_list_append(_ecore_pending_job_threads, work);
+ _ecore_pending_job_threads_feedback = eina_list_append(_ecore_pending_job_threads_feedback, work);
SLKU(_ecore_pending_job_threads_mutex);
}
else
cancel = work->cancel;
SLKU(work->cancel_mutex);
work->self = thread;
+
+ EINA_THREAD_CLEANUP_PUSH(_ecore_feedback_job_cleanup, work);
if (!cancel)
work->u.feedback_run.func_heavy((void *) work->data, (Ecore_Thread *) work);
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+}
- SLKL(_ecore_running_job_mutex);
- _ecore_running_job = eina_list_remove(_ecore_running_job, work);
- SLKU(_ecore_running_job_mutex);
+static void
+_ecore_direct_worker_cleanup(void *data)
+{
+ Ecore_Pthread_Worker *work = data;
- if (work->reschedule)
- {
- work->reschedule = EINA_FALSE;
-
- SLKL(_ecore_pending_job_threads_mutex);
- _ecore_pending_job_threads_feedback = eina_list_append(_ecore_pending_job_threads_feedback, work);
- SLKU(_ecore_pending_job_threads_mutex);
- }
- else
- {
- ecore_main_loop_thread_safe_call_async(_ecore_thread_handler, work);
- }
+ DBG("cleanup work=%p, thread=%" PRIu64 " (should join)", work, (uint64_t)work->self);
+
+ ecore_main_loop_thread_safe_call_async(_ecore_thread_handler, work);
+
+ ecore_main_loop_thread_safe_call_async((Ecore_Cb) _ecore_thread_join,
+ (void*)(intptr_t)PHS());
}
static void *
_ecore_direct_worker(Ecore_Pthread_Worker *work)
{
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
eina_thread_name_set(eina_thread_self(), "Ethread-feedback");
work->self = PHS();
+
+ EINA_THREAD_CLEANUP_PUSH(_ecore_direct_worker_cleanup, work);
if (work->message_run)
work->u.message_run.func_main((void *) work->data, (Ecore_Thread *) work);
else
work->u.feedback_run.func_heavy((void *) work->data, (Ecore_Thread *) work);
-
- ecore_main_loop_thread_safe_call_async(_ecore_thread_handler, work);
-
- ecore_main_loop_thread_safe_call_async((Ecore_Cb) _ecore_thread_join,
- (void*)(intptr_t)PHS());
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
return NULL;
}
+static void
+_ecore_thread_worker_cleanup(void *data EINA_UNUSED)
+{
+ DBG("cleanup thread=%" PRIu64 " (should join)", PHS());
+ SLKL(_ecore_pending_job_threads_mutex);
+ _ecore_thread_count--;
+ SLKU(_ecore_pending_job_threads_mutex);
+ ecore_main_loop_thread_safe_call_async((Ecore_Cb) _ecore_thread_join,
+ (void*)(intptr_t)PHS());
+}
+
static void *
_ecore_thread_worker(void *data EINA_UNUSED)
{
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+ EINA_THREAD_CLEANUP_PUSH(_ecore_thread_worker_cleanup, NULL);
restart:
+
+ /* these 2 are cancellation points as user cb may enable */
_ecore_short_job(PHS());
_ecore_feedback_job(PHS());
+ /* from here on, cancellations are guaranteed to be disabled */
+
/* FIXME: Check if there is feedback running task todo, and switch to feedback run handler. */
eina_thread_name_set(eina_thread_self(), "Ethread-worker");
SLKU(_ecore_pending_job_threads_mutex);
goto restart;
}
- _ecore_thread_count--;
-
- ecore_main_loop_thread_safe_call_async((Ecore_Cb) _ecore_thread_join,
- (void*)(intptr_t)PHS());
SLKU(_ecore_pending_job_threads_mutex);
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+
return NULL;
}
/* Delay the destruction */
on_exit:
+ eina_thread_cancel(work->self); /* noop unless eina_thread_cancellable_set() was used by user */
SLKL(work->cancel_mutex);
work->cancel = EINA_TRUE;
SLKU(work->cancel_mutex);
} Efl_Net_Resolve_Async_Data;
static void
-_efl_net_resolve_async_run(void *data, Ecore_Thread *thread)
+_efl_net_resolve_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Resolve_Async_Data *d = data;
- while (!ecore_thread_check(thread))
+ /* allows ecore_thread_cancel() to cancel at some points, see
+ * man:pthreads(7).
+ *
+ * no need to set cleanup functions since the main thread will
+ * handle that with _efl_net_resolve_async_cancel().
+ */
+ eina_thread_cancellable_set(EINA_TRUE, NULL);
+
+ while (EINA_TRUE)
{
DBG("resolving host='%s' port='%s'", d->host, d->port);
d->gai_error = getaddrinfo(d->host, d->port, d->hints, &d->result);
break;
}
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
} Efl_Net_Connect_Async_Data;
static void
-_efl_net_connect_async_run(void *data, Ecore_Thread *thread)
+_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Connect_Async_Data *d = data;
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
int r;
+ /* allows ecore_thread_cancel() to cancel at some points, see
+ * man:pthreads(7).
+ *
+ * no need to set cleanup functions since the main thread will
+ * handle that with _efl_net_connect_async_cancel().
+ */
+ eina_thread_cancellable_set(EINA_TRUE, NULL);
+
d->error = 0;
d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, d->close_on_exec);
return;
}
- if (ecore_thread_check(thread))
- {
- d->error = ECANCELED;
- close(d->sockfd);
- d->sockfd = -1;
- return;
- }
-
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
r = connect(d->sockfd, d->addr, d->addrlen);
if (r < 0)
{
+ int fd = d->sockfd;
d->error = errno;
- close(d->sockfd);
d->sockfd = -1;
- DBG("connect(%d, %s) failed: %s", d->sockfd, buf, strerror(errno));
+ /* close() is a cancellation point, thus unset sockfd before
+ * closing, so the main thread _efl_net_connect_async_cancel()
+ * won't close it again.
+ */
+ close(fd);
+ DBG("connect(%d, %s) failed: %s", fd, buf, strerror(errno));
return;
}
#include <eina_cpu.h>
#include <eina_sched.h>
#include <eina_tiler.h>
-#include <eina_thread.h>
#include <eina_hamster.h>
#include <eina_matrixsparse.h>
#include <eina_str.h>
#include <eina_quadtree.h>
#include <eina_simple_xml_parser.h>
#include <eina_lock.h>
+#include <eina_thread.h> /* after eina_lock.h since it will include pthread.h with proper flags */
#include <eina_prefix.h>
#include <eina_refcount.h>
#include <eina_mmap.h>
#include <stdlib.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"
return EINA_FALSE;
}
+EAPI Eina_Bool
+eina_thread_cancel(Eina_Thread t)
+{
+ return pthread_cancel((pthread_t)t) == 0;
+}
+
+EAPI 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;
+}
+
+EAPI void
+eina_thread_cancel_checkpoint(void)
+{
+ pthread_testcancel();
+}
+
+EAPI const void *EINA_THREAD_JOIN_CANCELED = PTHREAD_CANCELED;
+
Eina_Bool
eina_thread_init(void)
{
Eina_Thread_Cb func, const void *data) EINA_ARG_NONNULL(1, 4) EINA_WARN_UNUSED_RESULT;
/**
+ * The return value of eina_thread_join() if it was canceled with
+ * eina_thread_cancel().
+ *
+ * A thread must be explicitly flagged as cancellable with
+ * eina_thread_cancellable_set(), by default it's not and this value
+ * shouldn't be returned.
+ *
+ * @see eina_thread_join()
+ *
+ * @since 1.19
+ */
+EAPI extern const void *EINA_THREAD_JOIN_CANCELED;
+
+/**
* Join a currently running thread, waiting until it finishes.
*
* This function will block the current thread until @a t
* @param t thread identifier to wait.
* @return value returned by @a t creation function @c func() or
* @c NULL on errors. Check error with @ref Eina_Error_Group.
+ * If the thread was canceled, it will return
+ * EINA_THREAD_JOIN_CANCELED.
* @since 1.8
*/
EAPI void *eina_thread_join(Eina_Thread t);
EAPI Eina_Bool eina_thread_name_set(Eina_Thread t, const char *name);
/**
+ * Attempt to cancel a running thread.
+ *
+ * This function sends a cancellation request to the thread, however
+ * that request is only fulfilled if the thread is cancellable
+ * (eina_thread_cancellable_set() with EINA_TRUE as first paramter)
+ * and it will wait for a cancellation point, be
+ * eina_thread_cancel_checkpoint() or some syscall as defined in
+ * man:pthreads(7).
+ *
+ * A thread that was canceled will return EINA_THREAD_JOIN_CANCELED
+ * when eina_thread_join() is called.
+ *
+ * @param t thread to cancel.
+ *
+ * @return EINA_FALSE if thread was not running, EINA_TRUE
+ * otherwise. Note that if a thread is not cancellable and it
+ * is running, this function will return EINA_TRUE!
+ *
+ * @since 1.19
+ */
+EAPI Eina_Bool eina_thread_cancel(Eina_Thread t);
+
+/**
+ * Enable or disable if the current thread can be canceled.
+ *
+ * By default eina_thread_create() will return a thread with
+ * cancellation disabled. One can enable the cancellation by using
+ * EINA_TRUE in @a cancellable.
+ *
+ * Eina threads follow pthread_setcanceltype()
+ * PTHREAD_CANCEL_DEFERRED, that is, the actual termination will wait
+ * for a cancellation point, usually a syscall defined in
+ * man:pthreads(7) or an explicit cancellation point defined with
+ * eina_thread_cancel_checkpoint().
+ *
+ * In order to provide cleanup around critical blocks use
+ * EINA_THREAD_CLEANUP_PUSH() and EINA_THREAD_CLEANUP_POP() macros
+ * (which maps to pthread_cleanup_push() and pthread_cleanup_pop()),
+ * or the helper function eina_thread_cancellable_run() which does the
+ * pair for you.
+ *
+ * @param cancellable if EINA_TRUE, this thread will be accept
+ * cancellation requests. If EINA_FALSE -- the default, it will
+ * ignore cancellation requests.
+ * @param was_cancellable if non-NULL, will return the previous state,
+ * shall you want to restore.
+ *
+ * @return EINA_TRUE if it succeeds in setting the cancellable state
+ * or EINA_FALSE otherwise.
+ *
+ * @see eina_thread_cancel_checkpoint()
+ * @see EINA_THREAD_CLEANUP_PUSH()
+ * @see EINA_THREAD_CLEANUP_POP()
+ * @see eina_thread_cancellable_run()
+ * @see eina_thread_cancel()
+ *
+ * @since 1.19
+ */
+EAPI Eina_Bool eina_thread_cancellable_set(Eina_Bool cancellable, Eina_Bool *was_cancellable);
+
+/**
+ * If the current thread is cancellable, this introduces a
+ * cancellation check point. Otherwise it's a no-operation.
+ *
+ * Eina threads follow pthread_setcanceltype()
+ * PTHREAD_CANCEL_DEFERRED, that is, the actual termination will wait
+ * for a cancellation point, usually a syscall defined in
+ * man:pthreads(7) or an explicit cancellation point defined with this
+ * function.
+ *
+ * @see eina_thread_cancel_checkpoint()
+ *
+ * @since 1.19
+ */
+EAPI void eina_thread_cancel_checkpoint(void);
+
+/**
+ * @def EINA_THREAD_CLEANUP_PUSH(cleanup, data)
+ *
+ * @brief Push a cleanup function to be executed when the thread is
+ * canceled.
+ *
+ * This macro will schedule a function cleanup(data) to be executed if
+ * the thread is canceled with eina_thread_cancel() and the thread
+ * was previously marked as cancellable with
+ * eina_thread_cancellable_set().
+ *
+ * It @b must be paired with EINA_THREAD_CLEANUP_POP() in the same
+ * code block as they will expand to do {} while ()!
+ *
+ * The cleanup function may also be executed if
+ * EINA_THREAD_CLEANUP_POP(EINA_TRUE) is used.
+ *
+ * @note If the block within EINA_THREAD_CLEANUP_PUSH() and
+ * EINA_THREAD_CLEANUP_POP() returns, the cleanup callback will
+ * @b not be executed! To avoid problems prefer to use
+ * eina_thread_cancellable_run()!
+ *
+ * @param cleanup the function to execute on cancellation.
+ * @param data the context to give to cleanup function.
+ *
+ * @see eina_thread_cancellable_run()
+ *
+ * @since 1.19
+ */
+#define EINA_THREAD_CLEANUP_PUSH(cleanup, data) \
+ pthread_cleanup_push(cleanup, data)
+
+/**
+ * @def EINA_THREAD_CLEANUP_POP(exec_cleanup)
+ *
+ * @brief Pop a cleanup function to be executed when the thread is
+ * canceled.
+ *
+ * This macro will remove a previously pushed cleanup function, thus
+ * if the thread is canceled with eina_thread_cancel() and the thread
+ * was previously marked as cancellable with
+ * eina_thread_cancellable_set(), that cleanup won't be executed
+ * anymore.
+ *
+ * It @b must be paired with EINA_THREAD_CLEANUP_PUSH() in the same
+ * code block as they will expand to do {} while ()!
+ *
+ * @note If the block within EINA_THREAD_CLEANUP_PUSH() and
+ * EINA_THREAD_CLEANUP_POP() returns, the cleanup callback will
+ * @b not be executed even if exec_cleanup is EINA_TRUE! To
+ * avoid problems prefer to use eina_thread_cancellable_run()!
+ *
+ * @param exec_cleanup if EINA_TRUE, the function registered with
+ * EINA_THREAD_CLEANUP_PUSH() will be executed.
+ *
+ * @see eina_thread_cancellable_run()
+ *
+ * @since 1.19
+ */
+#define EINA_THREAD_CLEANUP_POP(exec_cleanup) \
+ pthread_cleanup_pop(exec_cleanup)
+
+/**
+ * @typedef Eina_Thread_Cancellable_Run_Cb
+ * Type for the definition of a cancellable callback to run.
+ *
+ * @since 1.19
+ */
+typedef void *(*Eina_Thread_Cancellable_Run_Cb)(void *data);
+
+/**
+ * This function will setup cleanup callback, turn the thread
+ * cancellable, execute the given callback, reset the cancellable
+ * state to its old value, run the cleanup callback and then return
+ * the callback return value.
+ *
+ * @note cleanup_cb is configured @b before the thread is made
+ * cancellable, thus it @b will be executed while @a cb may not
+ * in the case the thread was already canceled and that was
+ * pending.
+ *
+ * This helper does exactly the following code. Shall you need a
+ * slightly different behavior, use the base calls yourself.
+ *
+ * @code
+ * Eina_Bool old = EINA_FALSE;
+ * void *ret;
+ *
+ * EINA_THREAD_CLEANUP_PUSH(cleanup_cb, data);
+ * eina_thread_cancellable_set(EINA_TRUE, &old); // is a cancellation point
+ * ret = cb(data); // may not run if was previously canceled
+ * EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+ * eina_thread_cancellable_set(old, NULL);
+ * return ret;
+ * @endcode
+ *
+ * @param cb a cancellable callback to possibly run. The callback @b
+ * may not be executed if the thread had a pending cancellation
+ * request. During its execution the callback may be canceled
+ * at explicit cancellation points using
+ * eina_thread_cancel_checkpoint(), as well as some syscalls
+ * defined in man:pthreads(7).
+ * @param cleanup_cb a cleanup callback to be executed regardless of
+ * the thread being canceled or not. This function will be
+ * executed even if @a cb wasn't.
+ * @param data context to give to both @a cb and @a cleanup_cb.
+ *
+ * @return the return value of @a cb. If the thread was canceled,
+ * this function will not return.
+ *
+ * @since 1.19
+ */
+static inline void *
+eina_thread_cancellable_run(Eina_Thread_Cancellable_Run_Cb cb, Eina_Free_Cb cleanup_cb, void *data)
+{
+ Eina_Bool old = EINA_FALSE;
+ void *ret;
+
+ EINA_THREAD_CLEANUP_PUSH(cleanup_cb, data);
+ eina_thread_cancellable_set(EINA_TRUE, &old); // is a cancellation point
+ ret = cb(data); // may not run if was previously canceled
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+ eina_thread_cancellable_set(old, NULL);
+ return ret;
+}
+
+/**
* @}
*/