efl/src/lib/eina/eina_coro.h

555 lines
22 KiB
C

/* EINA - EFL data type library
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#ifndef EINA_CORO_H_
#define EINA_CORO_H_
#include "eina_config.h"
#include "eina_types.h"
#include "eina_error.h"
typedef struct _Eina_Future Eina_Future;
typedef struct _Eina_Value Eina_Value;
/**
* @addtogroup Eina_Tools_Group Tools
*
* @{
*/
/**
* @defgroup Eina_Coro_Group Co-routines
*
* Co-routines are cooperative threads, that is, their execution will
* stop the caller's thread, execute the coroutine until it finishes
* or yield, then give back control to the caller thread.
*
* The purpose of this primitive is to allow two functions to run with
* their own stack and registers and be sure that the caller thread
* won't run meanwhile, then shared context (variables, pointers) do
* not need locks as this is done implicitly by the design: if one
* thread is running you can be sure the other is not. The coroutine
* must explicitly give back control to the caller thread, either by
* eina_coro_yield() or by return.
*
* Multiple coroutines may exist at a given time, however if they are
* managed by the same caller then it's guaranteed that they will
* cooperate among themselves.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such or Efl_Object's efl_domain_data_adopt()
* (done automatically from efl_object_init()).
*
* @see @ref Eina_Thread_Group for regular concurrent threads.
*
* @since 1.21
* @{
*/
typedef struct _Eina_Coro Eina_Coro;
/**
* @typedef Eina_Coro_Hook_Coro_Enter_Cb
*
* @brief Type for the definition of a coroutine hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that will be entered or exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
* return that value, meaning the coroutine should voluntarily exit.
*
* All hooks are called, and if any of them returns #EINA_FALSE,
* eina_coro_yield() will return the same.
*
* If #EINA_FALSE is returned prior the first execution of the
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
* parameter. This allows coroutine to cleanup whatever is needed
* and return.
*
* @since 1.21
*/
typedef Eina_Bool (*Eina_Coro_Hook_Coro_Enter_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Coro_Exit_Cb
*
* @brief Type for the definition of a coroutine exit hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Exit hooks are always called in the reverse order they were added,
* that is, the last added hook will run first (stack).
*
* @since 1.21
*/
typedef void (*Eina_Coro_Hook_Coro_Exit_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Main_Enter_Cb
*
* @brief Type for the definition of a main routine hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that will be entered or exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Unlike the coroutine exit hooks, the main routine Exit hooks are
* always called in the reverse order they were added, that is, the
* last added hook will run first (stack). This is because they match
* Eina_Coro_Hook_Coro_Exit_Cb, a coroutine exits so the main routine
* can enter.
*
* @since 1.21
*/
typedef void (*Eina_Coro_Hook_Main_Enter_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Main_Exit_Cb
*
* @brief Type for the definition of a main routine exit hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Unlike the coroutine enter hooks, the main routine Enter hooks are
* called in forward order, that is, the first added hook will run
* first. This is because they match Eina_Coro_Hook_Coro_Enter_Cb, a
* main routine exits so the coroutine can enter.
*
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
* return that value, meaning the coroutine should voluntarily exit.
*
* All hooks are called, and if any of them returns #EINA_FALSE,
* eina_coro_yield() will return the same.
*
* If #EINA_FALSE is returned prior the first execution of the
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
* parameter. This allows coroutine to cleanup whatever is needed
* and return.
*
* @since 1.21
*/
typedef Eina_Bool (*Eina_Coro_Hook_Main_Exit_Cb)(void *data, const Eina_Coro *coro);
/**
* Adds a hook to the coroutine subsystem.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* The main routine (the caller) is the opposite: when eina_coro_run()
* it will "exit" and it will "enter" before eina_coro_run() returns.
*
* Enter hooks are executed in order, while exit hooks are always
* called in the reverse order they were added, that is, the last
* added hook will run first (stack).
*
* If any enter hooks fail, then eina_coro_yield() will return
* #EINA_FALSE or the parameter @c canceled as #EINA_TRUE will be
* given to the coroutine function. This allows coroutines to cleanup
* and exit.
*
* The flow is the following:
*
* @li main_exit is called to notify main routine will be stopped.
* @li coro_enter is called to notify the coroutine will be started.
* @li coro_exit is called to notify the coroutine stopped.
* @li main_enter is called to notify main routine will be resumed.
*
* They may be useful to properly setup environment prior to callback
* user code, like Efl_Object must check if we're running in an actual
* thread and adopt Efl_Domain_Data so objects are accessible from the
* coroutine -- this is done automatically by efl_object_init().
*
* @note this must @b NOT be called from within the coroutine itself,
* usually do this from your "init".
*
* @param coro_enter the hook to be called when the coroutine
* enters. May be #NULL if no enter hook is needed.
* @param coro_exit the hook to be called when the coroutine
* exits. May be #NULL if no exit hook is needed.
* @param main_enter the hook to be called when the caller
* enters. May be #NULL if no enter hook is needed.
* @param main_exit the hook to be called when the caller
* exits. May be #NULL if no exit hook is needed.
* @param data the context to pass to hooks. May be #NULL if no
* context is needed.
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
*
* @since 1.12
*/
EAPI Eina_Bool eina_coro_hook_add(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
/**
* Removes a hook from the coroutine subsystem.
*
* @note this must @b NOT be called from within the coroutine itself,
* usually do this from your "init".
*
* @param coro_enter the pointer that was given to eina_coro_hook_add().
* @param coro_exit the pointer that was given to eina_coro_hook_add().
* @param main_enter the pointer that was given to eina_coro_hook_add().
* @param main_exit the pointer that was given to eina_coro_hook_add().
* @param data the pointer that was given to eina_coro_hook_add().
* @return #EINA_TRUE on success, #EINA_FALSE on failure (ie: not found).
*
* @since 1.12
*/
EAPI Eina_Bool eina_coro_hook_del(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
/**
* @typedef Eina_Coro_Cb
* Type for the definition of a coroutine callback function.
*
* Once eina_coro_new() returns non-NULL, this function will be always
* called. However it may receive @c canceled as #EINA_TRUE, in this
* case it should cleanup and exit as soon as possible.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @since 1.21
*/
typedef const void *(*Eina_Coro_Cb)(void *data, Eina_Bool canceled, Eina_Coro *coro);
/**
* @def EINA_CORO_STACK_SIZE_DEFAULT
*
* Use the system's default stack size, usually @c PTHREAD_STACK_MIN
* (16Kb - 16384 bytes).
*
* @since 1.21
*/
#define EINA_CORO_STACK_SIZE_DEFAULT 0
/**
* @brief Creates a new coroutine.
*
* Allocates a coroutine environment using the given @a stack_size to
* execute @a func with the given context @a data. The coroutine must
* be explicitly executed with eina_coro_run(), that will stop the
* caller and let the coroutine work.
*
* @note The coroutine is @b not executed when it's created, that is
* managed by eina_coro_run().
*
* @note Currently @a stack_size is ignored, the default thread stack
* size will be used.
*
* @param func function to run in the coroutine. Must @b not be @c NULL.
* @param data context data to provide to @a func as first argument.
* @param stack_size defines the stack size to use to run the function
* @a func. Usually must be multiple of @c PAGE_SIZE and most
* systems will define a minimum stack limit such as 16Kb -
* those nuances are handled automatically for you. Most users
* want #EINA_CORO_STACK_SIZE_DEFAULT.
* @return newly allocated coroutine handle on success, #NULL on failure.
*
* @see eina_coro_run()
*
* @since 1.21
*/
EAPI Eina_Coro *eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
/**
* @brief Yields control from coroutine to caller.
*
* This @b must be called from within the @b coroutine and will pause it,
* giving back control to the caller of eina_coro_run(). The coroutine
* will remain stalled until eina_coro_run() is executed again.
*
* Yield can return #EINA_FALSE if any hooks fail, see
* eina_coro_hook_add(), or if the coroutine is canceled with
* eina_coro_cancel().
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param coro the coroutine that will yield control. Must not be #NULL.
*
* @return #EINA_TRUE if coroutine is allowed to continue,
* #EINA_FALSE if the coroutine should cleanup and exit.
* Refer to macros eina_coro_yield_or_return() or
* eina_coro_yield_or_goto() to aid such tasks.
*
* @see eina_coro_run()
* @see eina_coro_new()
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_yield(Eina_Coro *coro) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
/**
* #def eina_coro_yield_or_return(coro, val)
*
* This will yield control back to the main thread and wait. If the
* yield returns #EINA_FALSE then it will return the given value.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param val the value to return (void *) if yield returns #EINA_FALSE.
*
* @see eina_coro_yield()
* @see eina_coro_yield_or_goto()
*
* @since 1.21
*/
#define eina_coro_yield_or_return(coro, val) \
do { if (!eina_coro_yield(coro)) return (val); } while (0)
/**
* #def eina_coro_yield_or_goto(coro, label)
*
* This will yield control back to the main thread and wait. If the
* yield returns #EINA_FALSE then it will goto the given label.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param label the label to jump if yield returns #EINA_FALSE.
*
* @see eina_coro_yield()
* @see eina_coro_yield_or_return()
*
* @since 1.21
*/
#define eina_coro_yield_or_goto(coro, label) \
do { if (!eina_coro_yield(coro)) goto label; } while (0)
/**
* @brief Yield control from coroutine to the caller and report it's
* awaiting a future to be resolved.
*
* This @b must be called from within the @b coroutine and will pause
* it, giving back control to the caller of eina_coro_run(). The
* coroutine will remain stalled until eina_coro_run() is executed
* again. The caller of eina_coro_run() will get @c p_awaiting set so
* it can wait for the future to be resolved and improve its
* scheduling. Note that this will busy wait using eina_coro_yield()
* until the future resolves, then it's safe to naively call
* eina_coro_run() if the future is still pending -- but it will keep
* CPU consumption to its maximum.
*
* Await can return #EINA_FALSE if any hooks fail, see
* eina_coro_hook_add(), or if the coroutine is canceled with
* eina_coro_cancel().
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param coro the coroutine that will await control. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
*
* @return #EINA_TRUE if coroutine is allowed to continue,
* #EINA_FALSE if the coroutine should cleanup and exit.
* Refer to macros eina_coro_await_or_return() or
* eina_coro_await_or_goto() to aid such tasks.
*
* @see eina_coro_run()
* @see eina_coro_new()
* @see eina_coro_yield()
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_await(Eina_Coro *coro, Eina_Future *future, Eina_Value *p_value) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT;
/**
* #def eina_coro_await_or_return(coro, future, p_value, val)
*
* This will yield control back to the main thread and wait for future
* to be resolved. If the await returns #EINA_FALSE then it will
* return the given value.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
* @param val the value to return (void *) if await returns #EINA_FALSE.
*
* @see eina_coro_await()
* @see eina_coro_await_or_goto()
*
* @since 1.21
*/
#define eina_coro_await_or_return(coro, future, p_value, val) \
do { if (!eina_coro_await(coro, future, p_value)) return (val); } while (0)
/**
* #def eina_coro_await_or_goto(coro, label)
*
* This will yield control back to the main thread and wait for future
* to be resolved. If the await returns #EINA_FALSE then it will goto
* the given label.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
* @param label the label to jump if await returns #EINA_FALSE.
*
* @see eina_coro_await()
* @see eina_coro_await_or_return()
*
* @since 1.21
*/
#define eina_coro_await_or_goto(coro, future, p_value, label) \
do { if (!eina_coro_await(coro, future, p_value)) goto label; } while (0)
/**
* @brief Run the coroutine and report if it's still alive.
*
* This @b must be called from @b outside the coroutine, what we call
* "the caller side", also known as "main thread". The caller will be
* paused and control will be handled to the coroutine until it
* eina_coro_yield() or exits (returns).
*
* If the coroutine exits (returns), then this function will free the
* coroutine created, make @a p_coro #NULL, set @a p_result to
* whatever the Eina_Coro_Cb returned and return #EINA_FALSE.
*
* If the coroutine yields, then this function will return #EINA_TRUE,
* meaning it must be called again.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param[inout] p_coro pointer to the coroutine that will be
* executed. Must @b not be #NULL.
* @param[out] p_result if the coroutine exited, then will be set to the
* returned value. May be #NULL.
* @param[out] p_awaiting if the coroutine is awaiting a future to be
* resolved, then the handle. The scheduler should consider
* eina_future_then() and only call the coroutine once it's
* resolved. This happens when coroutine used eina_coro_await()
* instead of eina_coro_yield().
*
* @return #EINA_TRUE if the coroutine yielded and the user must call
* eina_coro_run() again. #EINA_FALSE if the coroutine exited,
* then @a p_coro will point to #NULL and if @a p_result is
* given it will be set to whatever the coroutine @c func returned.
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting) EINA_ARG_NONNULL(1);
/**
* @brief Cancel the coroutine and wait for it to finish.
*
* This @b must be called from @b outside the coroutine, what we call
* "the caller side", also known as "main thread". The caller will be
* paused and control will be handled to the coroutine until it exits
* (returns).
*
* This function will free the coroutine created, make @a p_coro
* #NULL, set @a p_result to whatever the Eina_Coro_Cb returned and
* return #EINA_FALSE.
*
* @note This will busy wait on eina_coro_run() until the coroutine @b
* voluntarily finishes, it's not aborted in any way. The
* primitive eina_coro_yield() will return #EINA_FALSE when the
* coroutine is canceled, this gives the opportunity to cleanup
* and exit. Likewise, if canceled before it's ever ran, then
* it will pass @c canceled as #EINA_TRUE to the Eina_Coro_Cb
* given to eina_coro_new()
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param[inout] p_coro pointer to the coroutine that will be
* executed. Must @b not be #NULL.
*
* @return The value returned by the coroutine function, same as would
* be returned in eina_coro_run()'s @c p_result parameter.
*
* @since 1.21
*/
EAPI void *eina_coro_cancel(Eina_Coro **p_coro) EINA_ARG_NONNULL(1);
/**
* @}
*/
/**
* @}
*/
#endif