HEADS UP -- BINDINGS:

If you wish to expose the new "cancellable" property, or cope with
naughty users or 3rd party deps that may call
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE), please register your
cleanup functions with:

   EINA_THREAD_CLEANUP_PUSH(cb, ctx); // will call cb() on
pthread_cancel()/ecore_thread_cancel()/eina_thread_cancel()
   call_user();
   EINA_THREAD_CLEANUP_POP(EINA_TRUE); // will call cb() on clean exit

If different functions are desired:

   EINA_THREAD_CLEANUP_PUSH(cb, ctx); // will call cb() on
pthread_cancel()/ecore_thread_cancel()/eina_thread_cancel()
   call_user();
   EINA_THREAD_CLEANUP_POP(EINA_FALSE); // will NOT call cb() on clean exit
   another_cb(ctx);

This is recommended if you expose ecore_thread to your users. If you
don't, then you do not need to do anything.




On Wed, Sep 14, 2016 at 1:47 AM, Gustavo Sverzut Barbieri
<barbi...@gmail.com> wrote:
> barbieri pushed a commit to branch master.
>
> http://git.enlightenment.org/core/efl.git/commit/?id=960e1a1d168ba0044544ca22b05cbf505941f150
>
> commit 960e1a1d168ba0044544ca22b05cbf505941f150
> Author: Gustavo Sverzut Barbieri <barbi...@profusion.mobi>
> Date:   Wed Sep 14 01:38:58 2016 -0300
>
>     eina/ecore: allow threads to be canceled, use in ecore_con.
>
>     As discussed in the mailing list, many people will use worker threads
>     to execute blocking syscalls and mandating ecore_thread_check() for
>     voluntary preemption reduces the ecore_thread usefulness a lot.
>
>     A clear example is ecore_con usage of connect() and getaddrinfo() in
>     threads. If the connect timeout expires, the thread will be cancelled,
>     but it was blocked on syscalls and they will hang around for long
>     time. If the application exits, ecore will print an error saying it
>     can SEGV.
>
>     Then enable access to pthread_setcancelstate(PTHREAD_CANCEL_ENABLE)
>     via eina_thread_cancellable_set(EINA_TRUE), to pthread_cancel() via
>     eina_thread_cancel(), to pthread_cleanup_push()/pthread_cleanup_pop()
>     via EINA_THREAD_CLEANUP_PUSH()/EINA_THREAD_CLEANUP_POP() and so on.
>
>     Ecore threads will enforce non-cancellable threads on its own code,
>     but the user may decide to enable that and allow cancellation, that's
>     not an issue since ecore_thread now plays well and use cleanup
>     functions.
>
>     Ecore con connect/resolve make use of that and enable cancellable
>     state, efl_net_dialer_tcp benefits a lot from that.
>
>     A good comparison of the benefit is to run:
>
>        ./src/examples/ecore/efl_io_copier_example tcp://google.com:1234 
> :stdout:
>
>     before and after. It will timeout after 30s and with this patch the
>     thread is gone, no ecore error is printed about possible SEGV.
> ---
>  src/lib/ecore/Ecore_Common.h  |  12 +++
>  src/lib/ecore/ecore_thread.c  | 108 +++++++++++++++------
>  src/lib/ecore_con/ecore_con.c |  41 +++++---
>  src/lib/eina/Eina.h           |   2 +-
>  src/lib/eina/eina_thread.c    |  32 ++++++
>  src/lib/eina/eina_thread.h    | 219 
> ++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 373 insertions(+), 41 deletions(-)
>
> diff --git a/src/lib/ecore/Ecore_Common.h b/src/lib/ecore/Ecore_Common.h
> index 5404438..22da9ab 100644
> --- a/src/lib/ecore/Ecore_Common.h
> +++ b/src/lib/ecore/Ecore_Common.h
> @@ -1794,6 +1794,17 @@ EAPI Ecore_Thread 
> *ecore_thread_feedback_run(Ecore_Thread_Cb func_heavy, Ecore_T
>   * 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
> @@ -1804,6 +1815,7 @@ EAPI Ecore_Thread 
> *ecore_thread_feedback_run(Ecore_Thread_Cb func_heavy, Ecore_T
>   * 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);
>
> diff --git a/src/lib/ecore/ecore_thread.c b/src/lib/ecore/ecore_thread.c
> index 6a68d6b..6a10164 100644
> --- a/src/lib/ecore/ecore_thread.c
> +++ b/src/lib/ecore/ecore_thread.c
> @@ -202,6 +202,7 @@ _ecore_thread_data_free(void *data)
>  static void
>  _ecore_thread_join(PH(thread))
>  {
> +   DBG("joining thread=%" PRIu64, (uint64_t)thread);
>     PHJ(thread);
>  }
>
> @@ -320,6 +321,31 @@ _ecore_message_notify_handler(void *data)
>  }
>
>  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;
> @@ -346,19 +372,31 @@ _ecore_short_job(PH(thread))
>     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
> @@ -393,52 +431,69 @@ _ecore_feedback_job(PH(thread))
>     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");
>
> @@ -463,12 +518,10 @@ restart:
>          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;
>  }
>
> @@ -728,6 +781,7 @@ ecore_thread_cancel(Ecore_Thread *thread)
>
>     /* 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);
> diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c
> index 63f2c8e..4efcb2a 100644
> --- a/src/lib/ecore_con/ecore_con.c
> +++ b/src/lib/ecore_con/ecore_con.c
> @@ -3126,11 +3126,19 @@ typedef struct _Efl_Net_Resolve_Async_Data
>  } 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);
> @@ -3141,6 +3149,8 @@ _efl_net_resolve_async_run(void *data, Ecore_Thread 
> *thread)
>          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")] = "";
> @@ -3235,12 +3245,20 @@ typedef struct _Efl_Net_Connect_Async_Data
>  } 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);
> @@ -3251,14 +3269,6 @@ _efl_net_connect_async_run(void *data, Ecore_Thread 
> *thread)
>          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);
>
> @@ -3267,10 +3277,15 @@ _efl_net_connect_async_run(void *data, Ecore_Thread 
> *thread)
>     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;
>       }
>
> diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h
> index f57cc62..a7a364f 100644
> --- a/src/lib/eina/Eina.h
> +++ b/src/lib/eina/Eina.h
> @@ -243,7 +243,6 @@ extern "C" {
>  #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>
> @@ -254,6 +253,7 @@ extern "C" {
>  #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>
> diff --git a/src/lib/eina/eina_thread.c b/src/lib/eina/eina_thread.c
> index 52cbc39..d40073a 100644
> --- a/src/lib/eina/eina_thread.c
> +++ b/src/lib/eina/eina_thread.c
> @@ -23,6 +23,7 @@
>  #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"
> @@ -226,6 +227,37 @@ eina_thread_name_set(Eina_Thread t, const char *name)
>     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)
>  {
> diff --git a/src/lib/eina/eina_thread.h b/src/lib/eina/eina_thread.h
> index 6a2c26d..07ecec3 100644
> --- a/src/lib/eina/eina_thread.h
> +++ b/src/lib/eina/eina_thread.h
> @@ -100,6 +100,20 @@ EAPI Eina_Bool eina_thread_create(Eina_Thread *t,
>                                    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
> @@ -110,6 +124,8 @@ EAPI Eina_Bool eina_thread_create(Eina_Thread *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);
> @@ -132,6 +148,209 @@ 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;
> +}
> +
> +/**
>   * @}
>   */
>
>
> --
>
>



-- 
Gustavo Sverzut Barbieri
--------------------------------------
Mobile: +55 (16) 99354-9890

------------------------------------------------------------------------------
_______________________________________________
enlightenment-devel mailing list
enlightenment-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/enlightenment-devel

Reply via email to