summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>2017-08-25 15:54:34 -0300
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>2017-08-27 11:47:55 -0300
commitd10a35628cc2b64b888648137ef5d218a1eb3c5b (patch)
treefeb0fa20fab168bc03e49cfa0f1b137d06a42fb4
parent4d7661b1746dcb2ba5d146700de887ecbe2e462e (diff)
eina: add Eina_Coro - coroutine support.
Coroutines are cooperative tasks, in the sense that the caller will stop until the target function runs. The target function must either yield control back to the caller (main thread), or exit. There is no preemption of the two tasks, thus no special care needs to be taken regarding shared data. If the target coroutine yields control using eina_coro_yield(), then it will be paused until it's manually ran again by caller (main thread), which is executed with eina_coro_run(). Another common usage is to await for another task to be completed, this can be done by waiting for a future to be resolved. It will automatically yield and inform the caller of the future so it can schedule properly instead of keep calling the task. Waiting for many tasks can be achieved by using eina_future_all() or eina_future_race(). This is done with eina_coro_await(). Due portability it was implemented using Eina_Thread, Eina_Lock and Eina_Condition. Regular threads will ensure that the state is fully preserved (stack, registers) in a platform independent way. Each thread will wait on its own turn using the Eina_Lock and Eina_Condition, thus it's guaranteed that only one is being executed at the same time. The API is small and should allow different implementations shall we need them, like manually saving the stack and registers, then restoring those -- problem is doing that in a portable way, setjmp()/longjmp() won't save the stack, makecontext()/swapcontext() doesn't work right on MacOS... Hooks can be used to be informed when the main routine exits and then enters, likewise when the coroutine enters and exits. These will be used, for instance, to automatically get, adopt and return Efl_Domain_Data needed to make Efl_Object work in such environment. The flow is simple: - main exit (called from main thread) - coroutine enter (called from worker thread) - coroutine exit (called from worker thread) - main enter (called from main thead) Performance may not be optimal, however this is meant as easy-to-use and it shouldn't be an issue in real life. It will be mostly exposed in two layers: - Efl.Loop.coro: will wrap eina_coro and and schedule using its main loop instance, returns an Eina_Future so it's easy to chain. - Eina_Promise/Eina_Future "async/await"-like behavior: will allow to write "synchronous" code that can wait for promises to be resolved. When eina_future_await(), it will actually register a new Eina_Future in the chain and then eina_coro_yield(). Once the future is called back it will call eina_coro_run() and allow the coroutine to resume. This is done on top fo eina_coro_await().
-rw-r--r--src/Makefile_Eina.am9
-rw-r--r--src/lib/eina/Eina.h1
-rw-r--r--src/lib/eina/eina_coro.c642
-rw-r--r--src/lib/eina/eina_coro.h554
-rw-r--r--src/lib/eina/eina_main.c3
-rw-r--r--src/lib/eina/eina_promise.c48
-rw-r--r--src/lib/eina/eina_promise.h62
-rw-r--r--src/tests/eina/eina_suite.c1
-rw-r--r--src/tests/eina/eina_suite.h1
-rw-r--r--src/tests/eina/eina_test_coro.c562
10 files changed, 1878 insertions, 5 deletions
diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am
index bd74cdcb64..64761feb84 100644
--- a/src/Makefile_Eina.am
+++ b/src/Makefile_Eina.am
@@ -107,7 +107,8 @@ lib/eina/eina_slice.h \
107lib/eina/eina_inline_slice.x \ 107lib/eina/eina_inline_slice.x \
108lib/eina/eina_inline_modinfo.x \ 108lib/eina/eina_inline_modinfo.x \
109lib/eina/eina_freeq.h \ 109lib/eina/eina_freeq.h \
110lib/eina/eina_slstr.h 110lib/eina/eina_slstr.h \
111lib/eina/eina_coro.h
111 112
112 113
113lib_eina_libeina_la_SOURCES = \ 114lib_eina_libeina_la_SOURCES = \
@@ -183,7 +184,8 @@ lib/eina/eina_quaternion.c \
183lib/eina/eina_bezier.c \ 184lib/eina/eina_bezier.c \
184lib/eina/eina_safepointer.c \ 185lib/eina/eina_safepointer.c \
185lib/eina/eina_freeq.c \ 186lib/eina/eina_freeq.c \
186lib/eina/eina_slstr.c 187lib/eina/eina_slstr.c \
188lib/eina/eina_coro.c
187 189
188 190
189if HAVE_WIN32 191if HAVE_WIN32
@@ -356,7 +358,8 @@ tests/eina/eina_test_bezier.c \
356tests/eina/eina_test_safepointer.c \ 358tests/eina/eina_test_safepointer.c \
357tests/eina/eina_test_slice.c \ 359tests/eina/eina_test_slice.c \
358tests/eina/eina_test_freeq.c \ 360tests/eina/eina_test_freeq.c \
359tests/eina/eina_test_slstr.c 361tests/eina/eina_test_slstr.c \
362tests/eina/eina_test_coro.c
360 363
361tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \ 364tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
362-DTESTS_WD=\"`pwd`\" \ 365-DTESTS_WD=\"`pwd`\" \
diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h
index 76b6d0adf6..5668a5c977 100644
--- a/src/lib/eina/Eina.h
+++ b/src/lib/eina/Eina.h
@@ -274,6 +274,7 @@ extern "C" {
274#include <eina_slstr.h> 274#include <eina_slstr.h>
275#include <eina_debug.h> 275#include <eina_debug.h>
276#include <eina_promise.h> 276#include <eina_promise.h>
277#include <eina_coro.h>
277 278
278#undef EAPI 279#undef EAPI
279#define EAPI 280#define EAPI
diff --git a/src/lib/eina/eina_coro.c b/src/lib/eina/eina_coro.c
new file mode 100644
index 0000000000..b19e34fe81
--- /dev/null
+++ b/src/lib/eina/eina_coro.c
@@ -0,0 +1,642 @@
1/* EINA - EFL data type library
2 * Copyright (C) 2017 ProFUSION embedded systems
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library;
16 * if not, see <http://www.gnu.org/licenses/>.
17 */
18
19#ifdef HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include <stdlib.h>
24#include <limits.h>
25
26#include "eina_config.h"
27#include "eina_private.h"
28#include "eina_log.h"
29#include "eina_mempool.h"
30#include "eina_lock.h"
31#include "eina_thread.h"
32#include "eina_inarray.h"
33#include "eina_promise.h"
34
35/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
36#include "eina_safety_checks.h"
37
38#include "eina_coro.h"
39#include "eina_value.h"
40#include "eina_value_util.h"
41
42static Eina_Mempool *_eina_coro_mp = NULL;
43static Eina_Lock _eina_coro_lock;
44
45static int _eina_coro_log_dom = -1;
46static int _eina_coro_usage = 0;
47
48static int _eina_coro_hooks_walking = 0;
49static Eina_Inarray _eina_coro_hooks;
50
51#ifdef CRIT
52#undef CRIT
53#endif
54#define CRIT(...) EINA_LOG_DOM_CRIT(_eina_coro_log_dom, __VA_ARGS__)
55
56#ifdef ERR
57#undef ERR
58#endif
59#define ERR(...) EINA_LOG_DOM_ERR(_eina_coro_log_dom, __VA_ARGS__)
60
61#ifdef INF
62#undef INF
63#endif
64#define INF(...) EINA_LOG_DOM_INFO(_eina_coro_log_dom, __VA_ARGS__)
65
66#ifdef DBG
67#undef DBG
68#endif
69#define DBG(...) EINA_LOG_DOM_DBG(_eina_coro_log_dom, __VA_ARGS__)
70
71typedef enum _Eina_Coro_Turn {
72 EINA_CORO_TURN_MAIN = 0,
73 EINA_CORO_TURN_COROUTINE
74} Eina_Coro_Turn;
75
76struct _Eina_Coro {
77 Eina_Coro_Cb func;
78 const void *data;
79 Eina_Future *awaiting;
80 Eina_Lock lock;
81 Eina_Condition condition;
82 Eina_Thread main;
83 Eina_Thread coroutine;
84 Eina_Bool finished;
85 Eina_Bool canceled;
86 Eina_Coro_Turn turn;
87};
88
89#define CORO_TURN_STR(turn) \
90 (((turn) == EINA_CORO_TURN_MAIN) ? "MAIN" : "COROUTINE")
91
92#define CORO_FMT "coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}"
93#define CORO_EXP(coro) \
94 coro, coro->func, coro->data, \
95 CORO_TURN_STR(coro->turn), \
96 (void *)coro->coroutine, \
97 eina_thread_self() == coro->coroutine ? '*' : 0, \
98 (void *)coro->main, \
99 eina_thread_self() == coro->main ? '*' : 0, \
100 coro->awaiting
101
102#define EINA_CORO_CHECK(coro, turn, ...) \
103 do \
104 { \
105 if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
106 { \
107 CRIT(#coro "=%p is invalid.", (coro)); \
108 return __VA_ARGS__; \
109 } \
110 else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
111 { \
112 CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
113 return __VA_ARGS__; \
114 } \
115 else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
116 { \
117 CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
118 return __VA_ARGS__; \
119 } \
120 } \
121 while (0)
122
123#define EINA_CORO_CHECK_GOTO(coro, turn, label) \
124 do \
125 { \
126 if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
127 { \
128 CRIT(#coro "=%p is invalid.", (coro)); \
129 goto label; \
130 } \
131 else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
132 { \
133 CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
134 goto label; \
135 } \
136 else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
137 { \
138 CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
139 goto label; \
140 } \
141 } \
142 while (0)
143
144
145typedef struct _Eina_Coro_Hook {
146 Eina_Coro_Hook_Coro_Enter_Cb coro_enter;
147 Eina_Coro_Hook_Coro_Exit_Cb coro_exit;
148 Eina_Coro_Hook_Main_Enter_Cb main_enter;
149 Eina_Coro_Hook_Main_Exit_Cb main_exit;
150 const void *data;
151} Eina_Coro_Hook;
152
153EAPI Eina_Bool
154eina_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)
155{
156 Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
157 int idx;
158
159 eina_lock_take(&_eina_coro_lock);
160
161 EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
162
163 idx = eina_inarray_push(&_eina_coro_hooks, &hook);
164 EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
165
166 eina_lock_release(&_eina_coro_lock);
167 return EINA_TRUE;
168
169 error:
170 eina_lock_release(&_eina_coro_lock);
171 return EINA_FALSE;
172}
173
174EAPI Eina_Bool
175eina_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)
176{
177 Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
178 int idx;
179
180 eina_lock_take(&_eina_coro_lock);
181
182 EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
183
184 idx = eina_inarray_remove(&_eina_coro_hooks, &hook);
185 EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
186
187 eina_lock_release(&_eina_coro_lock);
188 return EINA_TRUE;
189
190 error:
191 eina_lock_release(&_eina_coro_lock);
192 return EINA_FALSE;
193}
194
195
196// opposite of the coro_exit, similar to coro_enter
197static void
198_eina_coro_hooks_main_exit(Eina_Coro *coro)
199{
200 const Eina_Coro_Hook *itr;
201
202 eina_lock_take(&_eina_coro_lock);
203 _eina_coro_hooks_walking++;
204 eina_lock_release(&_eina_coro_lock);
205
206 EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
207 {
208 if (!itr->main_exit) continue;
209 if (itr->main_exit((void *)itr->data, coro)) continue;
210 coro->canceled = EINA_TRUE;
211 ERR("failed hook exit=%p data=%p for main routine " CORO_FMT,
212 itr->main_exit, itr->data, CORO_EXP(coro));
213 }
214}
215
216// opposite of the coro_enter, similar to coro_exit
217static void
218_eina_coro_hooks_main_enter(Eina_Coro *coro)
219{
220 const Eina_Coro_Hook *itr;
221
222 EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
223 {
224 if (!itr->main_enter) continue;
225 itr->main_enter((void *)itr->data, coro);
226 }
227
228 eina_lock_take(&_eina_coro_lock);
229 _eina_coro_hooks_walking--;
230 eina_lock_release(&_eina_coro_lock);
231}
232
233static Eina_Bool
234_eina_coro_hooks_coro_enter(Eina_Coro *coro)
235{
236 const Eina_Coro_Hook *itr;
237 Eina_Bool r = EINA_TRUE;
238
239 eina_lock_take(&_eina_coro_lock);
240 _eina_coro_hooks_walking++;
241 eina_lock_release(&_eina_coro_lock);
242
243 EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
244 {
245 if (!itr->coro_enter) continue;
246 if (itr->coro_enter((void *)itr->data, coro)) continue;
247 r = EINA_FALSE;
248 ERR("failed hook enter=%p data=%p for coroutine " CORO_FMT,
249 itr->coro_enter, itr->data, CORO_EXP(coro));
250 }
251
252 return r;
253}
254
255static void
256_eina_coro_hooks_coro_exit(Eina_Coro *coro)
257{
258 const Eina_Coro_Hook *itr;
259
260 EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
261 {
262 if (!itr->coro_exit) continue;
263 itr->coro_exit((void *)itr->data, coro);
264 }
265
266 eina_lock_take(&_eina_coro_lock);
267 _eina_coro_hooks_walking--;
268 eina_lock_release(&_eina_coro_lock);
269}
270
271static void
272_eina_coro_signal(Eina_Coro *coro, Eina_Coro_Turn turn)
273{
274 DBG("signal turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
275
276 eina_lock_take(&coro->lock);
277 coro->turn = turn;
278 eina_condition_signal(&coro->condition);
279 eina_lock_release(&coro->lock);
280}
281
282static void
283_eina_coro_wait(Eina_Coro *coro, Eina_Coro_Turn turn)
284{
285 DBG("waiting turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
286
287 eina_lock_take(&coro->lock);
288 while (coro->turn != turn)
289 eina_condition_wait(&coro->condition);
290 eina_lock_release(&coro->lock);
291
292 eina_main_loop_define();
293
294 DBG("wait is over: turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
295}
296
297static Eina_Bool
298_eina_coro_hooks_coro_enter_and_get_canceled(Eina_Coro *coro)
299{
300 if (!_eina_coro_hooks_coro_enter(coro)) return EINA_TRUE;
301 return coro->canceled;
302}
303
304static void *
305_eina_coro_thread(void *data, Eina_Thread t EINA_UNUSED)
306{
307 Eina_Coro *coro = data;
308 void *result = NULL;
309 Eina_Bool canceled = EINA_FALSE;
310
311 _eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
312
313 canceled = _eina_coro_hooks_coro_enter_and_get_canceled(coro);
314
315 DBG("call (canceled=%hhu) " CORO_FMT, canceled, CORO_EXP(coro));
316 result = (void *)coro->func((void *)coro->data, canceled, coro);
317 DBG("finished with result=%p " CORO_FMT, result, CORO_EXP(coro));
318
319 _eina_coro_hooks_coro_exit(coro);
320
321 coro->finished = EINA_TRUE;
322 _eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
323
324 return result;
325}
326
327static Eina_Coro *
328_eina_coro_alloc(void)
329{
330 Eina_Coro *coro = NULL;
331
332 eina_lock_take(&_eina_coro_lock);
333 if (EINA_UNLIKELY(!_eina_coro_mp))
334 {
335 const char *choice, *tmp;
336
337#ifdef EINA_DEFAULT_MEMPOOL
338 choice = "pass_through";
339#else
340 choice = "chained_mempool";
341#endif
342 tmp = getenv("EINA_MEMPOOL");
343 if (tmp && tmp[0])
344 choice = tmp;
345
346 _eina_coro_mp = eina_mempool_add
347 (choice, "coro", NULL, sizeof(Eina_Coro), 16);
348
349 if (!_eina_coro_mp)
350 {
351 ERR("Mempool for coro cannot be allocated in coro init.");
352 goto end;
353 }
354 }
355 coro = eina_mempool_calloc(_eina_coro_mp, sizeof(Eina_Coro));
356 if (coro) _eina_coro_usage++;
357
358 end:
359 eina_lock_release(&_eina_coro_lock);
360
361 return coro;
362}
363
364static void
365_eina_coro_free(Eina_Coro *coro)
366{
367 EINA_SAFETY_ON_NULL_RETURN(coro);
368
369 eina_lock_take(&_eina_coro_lock);
370
371 eina_mempool_free(_eina_coro_mp, coro);
372 _eina_coro_usage--;
373 if (_eina_coro_usage == 0)
374 {
375 eina_mempool_del(_eina_coro_mp);
376 _eina_coro_mp = NULL;
377 }
378
379 eina_lock_release(&_eina_coro_lock);
380}
381
382EAPI Eina_Coro *
383eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size)
384{
385 Eina_Coro *coro;
386 Eina_Bool r;
387
388 EINA_SAFETY_ON_NULL_RETURN_VAL(func, NULL);
389
390 coro = _eina_coro_alloc();
391 EINA_SAFETY_ON_NULL_RETURN_VAL(coro, NULL);
392
393 coro->func = func;
394 coro->data = data;
395 r = eina_lock_new(&coro->lock);
396 EINA_SAFETY_ON_FALSE_GOTO(r, failed_lock);
397 r = eina_condition_new(&coro->condition, &coro->lock);
398 EINA_SAFETY_ON_FALSE_GOTO(r, failed_condition);
399 coro->main = eina_thread_self();
400 coro->coroutine = 0;
401 coro->finished = EINA_FALSE;
402 coro->canceled = EINA_FALSE;
403 coro->turn = EINA_CORO_TURN_MAIN;
404
405 /* eina_thread_create() doesn't take attributes so we can set stack size */
406 if (stack_size)
407 DBG("currently stack size is ignored! Using thread default.");
408
409 if (!eina_thread_create(&coro->coroutine,
410 EINA_THREAD_NORMAL, -1,
411 _eina_coro_thread, coro))
412 {
413 ERR("could not create thread for " CORO_FMT, CORO_EXP(coro));
414 goto failed_thread;
415 }
416
417 INF(CORO_FMT, CORO_EXP(coro));
418 return coro;
419
420 failed_thread:
421 eina_condition_free(&coro->condition);
422 failed_condition:
423 eina_lock_free(&coro->lock);
424 failed_lock:
425 _eina_coro_free(coro);
426 return NULL;
427}
428
429EAPI Eina_Bool
430eina_coro_yield(Eina_Coro *coro)
431{
432 EINA_CORO_CHECK(coro, EINA_CORO_TURN_COROUTINE, EINA_FALSE);
433
434 _eina_coro_hooks_coro_exit(coro);
435
436 _eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
437 _eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
438
439 return !_eina_coro_hooks_coro_enter_and_get_canceled(coro);
440}
441
442EAPI Eina_Bool
443eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting)
444{
445 Eina_Coro *coro;
446
447 if (p_result) *p_result = NULL;
448 if (p_awaiting) *p_awaiting = NULL;
449
450 EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, EINA_FALSE);
451 EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, EINA_FALSE);
452
453 coro = *p_coro;
454
455 _eina_coro_hooks_main_exit(coro);
456
457 _eina_coro_signal(coro, EINA_CORO_TURN_COROUTINE);
458 _eina_coro_wait(coro, EINA_CORO_TURN_MAIN);
459
460 _eina_coro_hooks_main_enter(coro);
461
462 if (EINA_UNLIKELY(coro->finished)) {
463 void *result;
464 DBG("coroutine finished, join thread " CORO_FMT, CORO_EXP(coro));
465
466 result = eina_thread_join(coro->coroutine);
467 INF("coroutine finished with result=%p " CORO_FMT,
468 result, CORO_EXP(coro));
469 if (p_result) *p_result = result;
470 if (coro->awaiting) eina_future_cancel(coro->awaiting);
471 eina_condition_free(&coro->condition);
472 eina_lock_free(&coro->lock);
473 _eina_coro_free(coro);
474 *p_coro = NULL;
475 return EINA_FALSE;
476 }
477
478 if (p_awaiting) *p_awaiting = coro->awaiting;
479
480 DBG("coroutine yielded, must run again " CORO_FMT, CORO_EXP(coro));
481 return EINA_TRUE;
482}
483
484typedef struct _Eina_Coro_Await_Data {
485 Eina_Coro *coro;
486 Eina_Value *p_value;
487 Eina_Bool resolved;
488} Eina_Coro_Await_Data;
489
490static Eina_Value
491_eina_coro_await_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
492{
493 Eina_Coro_Await_Data *d = data;
494
495 DBG("future %p resolved with value type %p (%s) " CORO_FMT,
496 dead_future, value.type, value.type ? value.type->name : "EMPTY",
497 CORO_EXP(d->coro));
498
499 if (d->p_value)
500 {
501 // copy is needed as value contents is flushed when this function returns.
502 if (!value.type) *d->p_value = value;
503 else if (!eina_value_copy(&value, d->p_value))
504 {
505 ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", value.type, value.type->name);
506 eina_value_setup(d->p_value, EINA_VALUE_TYPE_ERROR);
507 eina_value_set(d->p_value, ENOTSUP);
508 }
509 }
510
511 d->resolved = EINA_TRUE;
512
513 return value;
514}
515
516EAPI Eina_Bool
517eina_coro_await(Eina_Coro *coro, Eina_Future *f, Eina_Value *p_value)
518{
519 Eina_Coro_Await_Data data = { coro, p_value, EINA_FALSE };
520
521 if (p_value) *p_value = EINA_VALUE_EMPTY;
522
523 EINA_CORO_CHECK_GOTO(coro, EINA_CORO_TURN_COROUTINE, no_coro);
524 EINA_SAFETY_ON_TRUE_GOTO(coro->awaiting != NULL, no_coro);
525
526 // storage will be NULL once future dies...
527 f = eina_future_then(f, _eina_coro_await_cb, &data, &coro->awaiting);
528 if (!f) return EINA_FALSE;
529
530 INF("await future %p " CORO_FMT, f, CORO_EXP(coro));
531 while (eina_coro_yield(coro) && !data.resolved)
532 DBG("future %p still pending " CORO_FMT, f, CORO_EXP(coro));
533
534 if (!data.resolved)
535 {
536 INF("future %p still pending and coroutine was canceled " CORO_FMT,
537 f, CORO_EXP(coro));
538 if (p_value)
539 {
540 eina_value_flush(p_value);
541 *p_value = eina_value_error_init(ECANCELED);
542 }
543 return EINA_FALSE;
544 }
545
546 INF("future %p resolved! continue coroutine " CORO_FMT, f, CORO_EXP(coro));
547 return EINA_TRUE;
548
549 no_coro:
550 if (p_value) *p_value = eina_value_error_init(EINVAL);
551 eina_future_cancel(f);
552 return EINA_FALSE;
553}
554
555EAPI void *
556eina_coro_cancel(Eina_Coro **p_coro)
557{
558 void *result = NULL;
559 Eina_Coro *coro;
560
561 EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, NULL);
562 EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, NULL);
563
564 coro = *p_coro;
565
566 coro->canceled = EINA_TRUE;
567
568 if (coro->awaiting) eina_future_cancel(coro->awaiting);
569
570 DBG("marked as canceled, run so it can exit... " CORO_FMT, CORO_EXP(coro));
571 while (eina_coro_run(p_coro, &result, NULL))
572 DBG("did not exited, try running again..." CORO_FMT, CORO_EXP(coro));
573
574 // be careful, coro is dead!
575 INF("coroutine %p canceled and returned %p", coro, result);
576
577 return result;
578}
579
580/**
581 * @internal
582 * @brief Initialize the coroutine module.
583 *
584 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
585 *
586 * This function sets up the coroutine module of Eina. It is called by
587 * eina_init().
588 *
589 * This function creates mempool to speed up and keep safety of coro
590 * handles, using EINA_MEMPOOL environment variable if it is set to
591 * choose the memory pool type to use.
592 *
593 * @see eina_init()
594 */
595Eina_Bool
596eina_coro_init(void)
597{
598 _eina_coro_log_dom = eina_log_domain_register("eina_coro",
599 EINA_LOG_COLOR_DEFAULT);
600 if (_eina_coro_log_dom < 0)
601 {
602 EINA_LOG_ERR("Could not register log domain: eina_coro");
603 return EINA_FALSE;
604 }
605
606 eina_lock_new(&_eina_coro_lock);
607 _eina_coro_usage = 0;
608 _eina_coro_hooks_walking = 0;
609 eina_inarray_step_set(&_eina_coro_hooks, sizeof(_eina_coro_hooks),
610 sizeof(Eina_Coro_Hook), 1);
611
612 return EINA_TRUE;
613}
614
615/**
616 * @internal
617 * @brief Shut down the coroutine module.
618 *
619 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
620 *
621 * This function shuts down the coroutine module set up by
622 * eina_coro_init(). It is called by eina_shutdown().
623 *
624 * @see eina_shutdown()
625 */
626Eina_Bool
627eina_coro_shutdown(void)
628{
629 eina_lock_take(&_eina_coro_lock);
630 EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_usage > 0, in_use);
631 eina_lock_release(&_eina_coro_lock);
632 eina_lock_free(&_eina_coro_lock);
633 eina_inarray_flush(&_eina_coro_hooks);
634
635 eina_log_domain_unregister(_eina_coro_log_dom);
636 _eina_coro_log_dom = -1;
637 return EINA_TRUE;
638
639 in_use:
640 eina_lock_release(&_eina_coro_lock);
641 return EINA_FALSE;
642}
diff --git a/src/lib/eina/eina_coro.h b/src/lib/eina/eina_coro.h
new file mode 100644
index 0000000000..aecb16e841
--- /dev/null
+++ b/src/lib/eina/eina_coro.h
@@ -0,0 +1,554 @@
1/* EINA - EFL data type library
2 * Copyright (C) 2017 ProFUSION embedded systems
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library;
16 * if not, see <http://www.gnu.org/licenses/>.
17 */
18
19#ifndef EINA_CORO_H_
20#define EINA_CORO_H_
21
22#include "eina_config.h"
23#include "eina_types.h"
24#include "eina_error.h"
25
26typedef struct _Eina_Future Eina_Future;
27typedef struct _Eina_Value Eina_Value;
28
29/**
30 * @addtogroup Eina_Tools_Group Tools
31 *
32 * @{
33 */
34
35/**
36 * @defgroup Eina_Coro_Group Co-routines
37 *
38 * Co-routines are cooperative threads, that is, their execution will
39 * stop the caller's thread, execute the coroutine until it finishes
40 * or yield, then give back control to the caller thread.
41 *
42 * The purpose of this primitive is to allow two functions to run with
43 * their own stack and registers and be sure that the caller thread
44 * won't run meanwhile, then shared context (variables, pointers) do
45 * not need locks as this is done implicitly by the design: if one
46 * thread is running you can be sure the other is not. The coroutine
47 * must explicitly give back control to the caller thread, either by
48 * eina_coro_yield() or by return.
49 *
50 * Multiple coroutines may exist at a given time, however if they are
51 * managed by the same caller then it's guaranteed that they will
52 * cooperate among themselves.
53 *
54 * @note The current implementation @b may use real threads with a
55 * lock and condition variable to ensure the behavior, this is
56 * an implementation detail that must not be relied upon.
57 * Depending on the platform it may use ucontext.h (SysV-like)
58 * or custom task save/restore. Libraries can use
59 * eina_coro_hook_add() to be called when the coroutine code
60 * will enter and exit, being able to retrieve context and set
61 * some other locks such or Efl_Object's efl_domain_data_adopt()
62 * (done automatically from efl_object_init()).
63 *
64 * @see @ref Eina_Thread_Group for regular concurrent threads.
65 *
66 * @since 1.21
67 * @{
68 */
69
70typedef struct _Eina_Coro Eina_Coro;
71
72/**
73 * @typedef Eina_Coro_Hook_Coro_Enter_Cb
74 *
75 * @brief Type for the definition of a coroutine hook.
76 *
77 * The pointer will be called back with the given @c data and the
78 * coroutine that will be entered or exited.
79 *
80 * The coroutine "enters" when eina_coro_run() is called and "exits"
81 * when the provided function returns or calls eina_coro_yield().
82 *
83 * If the callback returns #EINA_FALSE, then eina_coro_yield() will
84 * return that value, meaning the coroutine should voluntarily exit.
85 *
86 * All hooks are called, and if any of them returns #EINA_FALSE,
87 * eina_coro_yield() will return the same.
88 *
89 * If #EINA_FALSE is returned prior the first execution of the
90 * coroutine, then the coroutine will get a canceled #EINA_TRUE as
91 * parameter. This allows coroutine to cleanup whatever is needed
92 * and return.
93 *
94 * @since 1.21
95 */
96typedef Eina_Bool (*Eina_Coro_Hook_Coro_Enter_Cb)(void *data, const Eina_Coro *coro);
97
98/**
99 * @typedef Eina_Coro_Hook_Coro_Exit_Cb
100 *
101 * @brief Type for the definition of a coroutine exit hook.
102 *
103 * The pointer will be called back with the given @c data and the
104 * coroutine that exited.
105 *
106 * The coroutine "enters" when eina_coro_run() is called and "exits"
107 * when the provided function returns or calls eina_coro_yield().
108 *
109 * Exit hooks are always called in the reverse order they were added,
110 * that is, the last added hook will run first (stack).
111 *
112 * @since 1.21
113 */
114typedef void (*Eina_Coro_Hook_Coro_Exit_Cb)(void *data, const Eina_Coro *coro);
115
116/**
117 * @typedef Eina_Coro_Hook_Main_Enter_Cb
118 *
119 * @brief Type for the definition of a main routine hook.
120 *
121 * The pointer will be called back with the given @c data and the
122 * coroutine that will be entered or exited.
123 *
124 * The coroutine "enters" when eina_coro_run() is called and "exits"
125 * when the provided function returns or calls eina_coro_yield().
126 *
127 * Unlike the coroutine exit hooks, the main routine Exit hooks are
128 * always called in the reverse order they were added, that is, the
129 * last added hook will run first (stack). This is because they match
130 * Eina_Coro_Hook_Coro_Exit_Cb, a coroutine exits so the main routine
131 * can enter.
132 *
133 * @since 1.21
134 */
135typedef void (*Eina_Coro_Hook_Main_Enter_Cb)(void *data, const Eina_Coro *coro);
136
137/**
138 * @typedef Eina_Coro_Hook_Main_Exit_Cb
139 *
140 * @brief Type for the definition of a main routine exit hook.
141 *
142 * The pointer will be called back with the given @c data and the
143 * coroutine that exited.
144 *
145 * The coroutine "enters" when eina_coro_run() is called and "exits"
146 * when the provided function returns or calls eina_coro_yield().
147 *
148 * Unlike the coroutine enter hooks, the main routine Enter hooks are
149 * called in forward order, that is, the first added hook will run
150 * first. This is because they match Eina_Coro_Hook_Coro_Enter_Cb, a
151 * main routine exits so the coroutine can enter.
152 *
153 * If the callback returns #EINA_FALSE, then eina_coro_yield() will
154 * return that value, meaning the coroutine should voluntarily exit.
155 *
156 * All hooks are called, and if any of them returns #EINA_FALSE,
157 * eina_coro_yield() will return the same.
158 *
159 * If #EINA_FALSE is returned prior the first execution of the
160 * coroutine, then the coroutine will get a canceled #EINA_TRUE as
161 * parameter. This allows coroutine to cleanup whatever is needed
162 * and return.
163 *
164 * @since 1.21
165 */
166typedef Eina_Bool (*Eina_Coro_Hook_Main_Exit_Cb)(void *data, const Eina_Coro *coro);
167
168
169/**
170 * Adds a hook to the coroutine subsystem.
171 *
172 * The coroutine "enters" when eina_coro_run() is called and "exits"
173 * when the provided function returns or calls eina_coro_yield().
174 *
175 * The main routine (the caller) is the opposite: when eina_coro_run()
176 * it will "exit" and it will "enter" before eina_coro_run() returns.
177 *
178 * Enter hooks are executed in order, while exit hooks are always
179 * called in the reverse order they were added, that is, the last
180 * added hook will run first (stack).
181 *
182 * If any enter hooks fail, then eina_coro_yield() will return
183 * #EINA_FALSE or the parameter @c canceled as #EINA_TRUE will be
184 * given to the coroutine function. This allows coroutines to cleanup
185 * and exit.
186 *
187 * The flow is the following:
188 *
189 * @li main_exit is called to notify main routine will be stopped.
190 * @li coro_enter is called to notify the coroutine will be started.
191 * @li coro_exit is called to notify the coroutine stopped.
192 * @li main_enter is called to notify main routine will be resumed.
193 *
194 * They may be useful to properly setup environment prior to callback
195 * user code, like Efl_Object must check if we're running in an actual
196 * thread and adopt Efl_Domain_Data so objects are accessible from the
197 * coroutine -- this is done automatically by efl_object_init().
198 *
199 * @note this must @b NOT be called from within the coroutine itself,
200 * usually do this from your "init".
201 *
202 * @param coro_enter the hook to be called when the coroutine
203 * enters. May be #NULL if no enter hook is needed.
204 * @param coro_exit the hook to be called when the coroutine
205 * exits. May be #NULL if no exit hook is needed.
206 * @param main_enter the hook to be called when the caller
207 * enters. May be #NULL if no enter hook is needed.
208 * @param main_exit the hook to be called when the caller
209 * exits. May be #NULL if no exit hook is needed.
210 * @param data the context to pass to hooks. May be #NULL if no
211 * context is needed.
212 * @return #EINA_TRUE on success, #EINA_FALSE on failure.
213 *
214 * @since 1.12
215 */
216EAPI 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);
217
218/**
219 * Removes a hook from the coroutine subsystem.
220 *
221 * @note this must @b NOT be called from within the coroutine itself,
222 * usually do this from your "init".
223 *
224 * @param coro_enter the pointer that was given to eina_coro_hook_add().
225 * @param coro_exit the pointer that was given to eina_coro_hook_add().
226 * @param main_enter the pointer that was given to eina_coro_hook_add().
227 * @param main_exit the pointer that was given to eina_coro_hook_add().
228 * @param data the pointer that was given to eina_coro_hook_add().
229 * @return #EINA_TRUE on success, #EINA_FALSE on failure (ie: not found).
230 *
231 * @since 1.12
232 */
233EAPI 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);
234
235/**
236 * @typedef Eina_Coro_Cb
237 * Type for the definition of a coroutine callback function.
238 *
239 * Once eina_coro_new() returns non-NULL, this function will be always
240 * called. However it may receive @c canceled as #EINA_TRUE, in this
241 * case it should cleanup and exit as soon as possible.
242 *
243 * @note The current implementation @b may use real threads with a
244 * lock and condition variable to ensure the behavior, this is
245 * an implementation detail that must not be relied upon.
246 * Depending on the platform it may use ucontext.h (SysV-like)
247 * or custom task save/restore. Libraries can use
248 * eina_coro_hook_add() to be called when the coroutine code
249 * will enter and exit, being able to retrieve context and set
250 * some other locks such as the Python's GIL or Efl_Object's
251 * efl_domain_data_adopt() (done automatically from
252 * efl_object_init()).
253 *
254 * @since 1.21
255 */
256typedef const void *(*Eina_Coro_Cb)(void *data, Eina_Bool canceled, Eina_Coro *coro);
257
258/**
259 * @def EINA_CORO_STACK_SIZE_DEFAULT
260 *
261 * Use the system's default stack size, usually @c PTHREAD_STACK_MIN
262 * (16Kb - 16384 bytes).
263 *
264 * @since 1.21
265 */
266#define EINA_CORO_STACK_SIZE_DEFAULT 0
267
268/**
269 * @brief Creates a new coroutine.
270 *
271 * Allocates a coroutine environment using the given @a stack_size to
272 * execute @a func with the given context @a data. The coroutine must
273 * be explicitly executed with eina_coro_run(), that will stop the
274 * caller and let the coroutine work.
275 *
276 * @note The coroutine is @b not executed when it's created, that is
277 * managed by eina_coro_run().
278 *
279 * @note Currently @a stack_size is ignored, the default thread stack
280 * size will be used.
281 *
282 * @param func function to run in the coroutine. Must @b not be @c NULL.
283 * @param data context data to provide to @a func as first argument.
284 * @param stack_size defines the stack size to use to run the function
285 * @a func. Usually must be multiple of @c PAGE_SIZE and most
286 * systems will define a minimum stack limit such as 16Kb -
287 * those nuances are handled automatically for you. Most users
288 * want #EINA_CORO_STACK_SIZE_DEFAULT.
289 * @return newly allocated coroutine handle on success, #NULL on failure.
290 *
291 * @see eina_coro_run()
292 *
293 * @since 1.21
294 */
295EAPI Eina_Coro *eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
296
297/**
298 * @brief Yields control from coroutine to caller.
299 *
300 * This @b must be called from within the @b coroutine and will pause it,
301 * giving back control to the caller of eina_coro_run(). The coroutine
302 * will remain stalled until eina_coro_run() is executed again.
303 *
304 * Yield can return #EINA_FALSE if any hooks fail, see
305 * eina_coro_hook_add(), or if the coroutine is canceled with
306 * eina_coro_cancel().
307 *
308 * @note The current implementation @b may use real threads with a
309 * lock and condition variable to ensure the behavior, this is
310 * an implementation detail that must not be relied upon.
311 * Depending on the platform it may use ucontext.h (SysV-like)
312 * or custom task save/restore. Libraries can use
313 * eina_coro_hook_add() to be called when the coroutine code
314 * will enter and exit, being able to retrieve context and set
315 * some other locks such as the Python's GIL or Efl_Object's
316 * efl_domain_data_adopt() (done automatically from
317 * efl_object_init()).
318 *
319 * @param coro the coroutine that will yield control. Must not be #NULL.
320 *
321 * @return #EINA_TRUE if coroutine is allowed to continue,
322 * #EINA_FALSE if the coroutine should cleanup and exit.
323 * Refer to macros eina_coro_yield_or_return() or
324 * eina_coro_yield_or_goto() to aid such tasks.
325 *
326 * @see eina_coro_run()
327 * @see eina_coro_new()
328 *
329 * @since 1.21
330 */
331EAPI Eina_Bool eina_coro_yield(Eina_Coro *coro) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
332
333/**
334 * #def eina_coro_yield_or_return(coro, val)
335 *
336 * This will yield control back to the main thread and wait. If the
337 * yield returns #EINA_FALSE then it will return the given value.
338 *
339 * @param coro the coroutine handle. Must not be #NULL.
340 * @param val the value to return (void *) if yield returns #EINA_FALSE.
341 *
342 * @see eina_coro_yield()
343 * @see eina_coro_yield_or_goto()
344 *
345 * @since 1.21
346 */
347#define eina_coro_yield_or_return(coro, val) \
348 do { if (!eina_coro_yield(coro)) return (val); } while (0)
349
350/**
351 * #def eina_coro_yield_or_goto(coro, label)
352 *
353 * This will yield control back to the main thread and wait. If the
354 * yield returns #EINA_FALSE then it will goto the given label.
355 *
356 * @param coro the coroutine handle. Must not be #NULL.
357 * @param label the label to jump if yield returns #EINA_FALSE.
358 *
359 * @see eina_coro_yield()
360 * @see eina_coro_yield_or_return()
361 *
362 * @since 1.21
363 */
364#define eina_coro_yield_or_goto(coro, label) \
365 do { if (!eina_coro_yield(coro)) goto label; } while (0)
366
367/**
368 * @brief Yield control from coroutine to the caller and report it's
369 * awaiting a future to be resolved.
370 *
371 * This @b must be called from within the @b coroutine and will pause
372 * it, giving back control to the caller of eina_coro_run(). The
373 * coroutine will remain stalled until eina_coro_run() is executed
374 * again. The caller of eina_coro_run() will get @c p_awaiting set so
375 * it can wait for the future to be resolved and improve its
376 * scheduling. Note that this will busy wait using eina_coro_yield()
377 * until the future resolves, then it's safe to naively call
378 * eina_coro_run() if the future is still pending -- but it will keep
379 * CPU consumption to its maximum.
380 *
381 * Await can return #EINA_FALSE if any hooks fail, see
382 * eina_coro_hook_add(), or if the coroutine is canceled with
383 * eina_coro_cancel().
384 *
385 * @note The current implementation @b may use real threads with a
386 * lock and condition variable to ensure the behavior, this is
387 * an implementation detail that must not be relied upon.
388 * Depending on the platform it may use ucontext.h (SysV-like)
389 * or custom task save/restore. Libraries can use
390 * eina_coro_hook_add() to be called when the coroutine code
391 * will enter and exit, being able to retrieve context and set
392 * some other locks such as the Python's GIL or Efl_Object's
393 * efl_domain_data_adopt() (done automatically from
394 * efl_object_init()).
395 *
396 * @param coro the coroutine that will await control. Must not be #NULL.
397 * @param future the future that this will wait. Must not be #NULL.
398 * @param[out] p_value the pointer to value containing the future
399 * resolution. If given the value will be owned by caller and
400 * must be released with eina_value_flush(). May be #NULL.
401 *
402 * @return #EINA_TRUE if coroutine is allowed to continue,
403 * #EINA_FALSE if the coroutine should cleanup and exit.
404 * Refer to macros eina_coro_await_or_return() or
405 * eina_coro_await_or_goto() to aid such tasks.
406 *
407 * @see eina_coro_run()
408 * @see eina_coro_new()
409 * @see eina_coro_yield()
410 *
411 * @since 1.21
412 */
413EAPI Eina_Bool eina_coro_await(Eina_Coro *coro, Eina_Future *future, Eina_Value *p_value) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT;
414
415/**
416 * #def eina_coro_await_or_return(coro, future, p_value, val)
417 *
418 * This will yield control back to the main thread and wait for future
419 * to be resolved. If the await returns #EINA_FALSE then it will
420 * return the given value.
421 *
422 * @param coro the coroutine handle. Must not be #NULL.
423 * @param future the future that this will wait. Must not be #NULL.
424 * @param[out] p_value the pointer to value containing the future
425 * resolution. If given the value will be owned by caller and
426 * must be released with eina_value_flush(). May be #NULL.
427 * @param val the value to return (void *) if await returns #EINA_FALSE.
428 *
429 * @see eina_coro_await()
430 * @see eina_coro_await_or_goto()
431 *
432 * @since 1.21
433 */
434#define eina_coro_await_or_return(coro, future, p_value, val) \
435 do { if (!eina_coro_await(coro, future, p_value)) return (val); } while (0)
436
437/**
438 * #def eina_coro_await_or_goto(coro, label)
439 *
440 * This will yield control back to the main thread and wait for future
441 * to be resolved. If the await returns #EINA_FALSE then it will goto
442 * the given label.
443 *
444 * @param coro the coroutine handle. Must not be #NULL.
445 * @param future the future that this will wait. Must not be #NULL.
446 * @param[out] p_value the pointer to value containing the future
447 * resolution. If given the value will be owned by caller and
448 * must be released with eina_value_flush(). May be #NULL.
449 * @param label the label to jump if await returns #EINA_FALSE.
450 *
451 * @see eina_coro_await()
452 * @see eina_coro_await_or_return()
453 *
454 * @since 1.21
455 */
456#define eina_coro_await_or_goto(coro, future, p_value, label) \
457 do { if (!eina_coro_await(coro, future, p_value)) goto label; } while (0)
458
459
460/**
461 * @brief Run the coroutine and report if it's still alive.
462 *
463 * This @b must be called from @b outside the coroutine, what we call
464 * "the caller side", also known as "main thread". The caller will be
465 * paused and control will be handled to the coroutine until it
466 * eina_coro_yield() or exits (returns).
467 *
468 * If the coroutine exits (returns), then this function will free the
469 * coroutine created, make @a p_coro #NULL, set @a p_result to
470 * whatever the Eina_Coro_Cb returned and return #EINA_FALSE.
471 *
472 * If the coroutine yields, then this function will return #EINA_TRUE,
473 * meaning it must be called again.
474 *
475 * @note The current implementation @b may use real threads with a
476 * lock and condition variable to ensure the behavior, this is
477 * an implementation detail that must not be relied upon.
478 * Depending on the platform it may use ucontext.h (SysV-like)
479 * or custom task save/restore. Libraries can use
480 * eina_coro_hook_add() to be called when the coroutine code
481 * will enter and exit, being able to retrieve context and set
482 * some other locks such as the Python's GIL or Efl_Object's
483 * efl_domain_data_adopt() (done automatically from
484 * efl_object_init()).
485 *
486 * @param[inout] p_coro pointer to the coroutine that will be
487 * executed. Must @b not be #NULL.
488 * @param[out] p_result if the coroutine exited, then will be set to the
489 * returned value. May be #NULL.
490 * @param[out] p_awaiting if the coroutine is awaiting a future to be
491 * resolved, then the handle. The scheduler should consider
492 * eina_future_then() and only call the coroutine once it's
493 * resolved. This happens when coroutine used eina_coro_await()
494 * instead of eina_coro_yield().
495 *
496 * @return #EINA_TRUE if the coroutine yielded and the user must call
497 * eina_coro_run() again. #EINA_FALSE if the coroutine exited,
498 * then @a p_coro will point to #NULL and if @a p_result is
499 * given it will be set to whatever the coroutine @c func returned.
500 *
501 * @since 1.21
502 */
503EAPI Eina_Bool eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting) EINA_ARG_NONNULL(1);
504
505/**
506 * @brief Cancel the coroutine and wait for it to finish.
507 *
508 * This @b must be called from @b outside the coroutine, what we call
509 * "the caller side", also known as "main thread". The caller will be
510 * paused and control will be handled to the coroutine until it exits
511 * (returns).
512 *
513 * This function will free the coroutine created, make @a p_coro
514 * #NULL, set @a p_result to whatever the Eina_Coro_Cb returned and
515 * return #EINA_FALSE.
516 *
517 * @note This will busy wait on eina_coro_run() until the coroutine @b
518 * voluntarily finishes, it's not aborted in any way. The
519 * primitive eina_coro_yield() will return #EINA_FALSE when the
520 * coroutine is canceled, this gives the opportunity to cleanup
521 * and exit. Likewise, if canceled before it's ever ran, then
522 * it will pass @c canceled as #EINA_TRUE to the Eina_Coro_Cb
523 * given to eina_coro_new()
524 *
525 * @note The current implementation @b may use real threads with a
526 * lock and condition variable to ensure the behavior, this is
527 * an implementation detail that must not be relied upon.
528 * Depending on the platform it may use ucontext.h (SysV-like)
529 * or custom task save/restore. Libraries can use
530 * eina_coro_hook_add() to be called when the coroutine code
531 * will enter and exit, being able to retrieve context and set
532 * some other locks such as the Python's GIL or Efl_Object's
533 * efl_domain_data_adopt() (done automatically from
534 * efl_object_init()).
535 *
536 * @param[inout] p_coro pointer to the coroutine that will be
537 * executed. Must @b not be #NULL.
538 *
539 * @return The value returned by the coroutine function, same as would
540 * be returned in eina_coro_run()'s @c p_result parameter.
541 *
542 * @since 1.21
543 */
544EAPI void *eina_coro_cancel(Eina_Coro **p_coro) EINA_ARG_NONNULL(1);
545
546/**
547 * @}
548 */
549
550/**
551 * @}
552 */
553
554#endif
diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c
index b3a716f135..a863b04772 100644
--- a/src/lib/eina/eina_main.c
+++ b/src/lib/eina/eina_main.c
@@ -74,6 +74,7 @@
74#include "eina_evlog.h" 74#include "eina_evlog.h"
75#include "eina_freeq.h" 75#include "eina_freeq.h"
76#include "eina_slstr.h" 76#include "eina_slstr.h"
77#include "eina_coro.h"
77 78
78/*============================================================================* 79/*============================================================================*
79* Local * 80* Local *
@@ -160,6 +161,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
160 S(safepointer); 161 S(safepointer);
161 S(slstr); 162 S(slstr);
162 S(promise); 163 S(promise);
164 S(coro);
163#undef S 165#undef S
164 166
165struct eina_desc_setup 167struct eina_desc_setup
@@ -207,6 +209,7 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
207 S(safepointer), 209 S(safepointer),
208 S(slstr), 210 S(slstr),
209 S(promise), 211 S(promise),
212 S(coro),
210#undef S 213#undef S
211}; 214};
212static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) / 215static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /
diff --git a/src/lib/eina/eina_promise.c b/src/lib/eina/eina_promise.c
index 588c94ff2b..e01e6904bb 100644
--- a/src/lib/eina/eina_promise.c
+++ b/src/lib/eina/eina_promise.c
@@ -3,13 +3,20 @@
3#endif 3#endif
4 4
5#include "eina_private.h" 5#include "eina_private.h"
6#include "eina_promise.h"
7#include "eina_mempool.h" 6#include "eina_mempool.h"
8#include "eina_promise_private.h" 7#include "eina_coro.h"
9#include <errno.h> 8#include <errno.h>
10#include <stdarg.h> 9#include <stdarg.h>
11#include <assert.h> 10#include <assert.h>
12 11
12/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
13#include "eina_safety_checks.h"
14
15#include "eina_promise.h"
16#include "eina_promise_private.h"
17#include "eina_value_util.h"
18
19
13#define EINA_FUTURE_DISPATCHED ((Eina_Future_Cb)(0x01)) 20#define EINA_FUTURE_DISPATCHED ((Eina_Future_Cb)(0x01))
14 21
15#define EFL_MEMPOOL_CHECK_RETURN(_type, _mp, _p) \ 22#define EFL_MEMPOOL_CHECK_RETURN(_type, _mp, _p) \
@@ -1271,3 +1278,40 @@ eina_promise_race_array(Eina_Future *array[])
1271 _future2_array_cancel(array); 1278 _future2_array_cancel(array);
1272 return NULL; 1279 return NULL;
1273} 1280}
1281
1282EAPI Eina_Value
1283eina_future_await(Eina_Future *f, Eina_Coro *coro, const Eina_Value_Type *success_type)
1284{
1285 Eina_Value value = EINA_VALUE_EMPTY;
1286
1287 if (!eina_coro_await(coro, f, &value))
1288 goto log; // value will be some EINA_VALUE_TYPE_ERROR
1289
1290 if ((value.type != EINA_VALUE_TYPE_ERROR) &&
1291 ((success_type) && (value.type != success_type)))
1292 {
1293 ERR("Future %p, expected success_type %p (%s), got %p (%s)",
1294 f, success_type, success_type->name,
1295 value.type, value.type ? value.type->name : "EMPTY");
1296
1297 eina_value_flush(&value);
1298 value = eina_value_error_init(EINVAL);
1299 }
1300
1301 log:
1302 if (EINA_UNLIKELY(eina_log_domain_level_check(_promise2_log_dom,
1303 EINA_LOG_LEVEL_DBG)))
1304 {
1305 if (!value.type) DBG("Awaited future %p, got empty value", f);
1306 else
1307 {
1308 char *str = eina_value_to_string(&value);
1309 DBG("Awaited future %p - Value Type: %s Contents: %s (success_type: %s)",
1310 f, value.type->name, str,
1311 success_type ? success_type->name : "ALL");
1312 free(str);
1313 }
1314 }
1315
1316 return value;
1317}
diff --git a/src/lib/eina/eina_promise.h b/src/lib/eina/eina_promise.h
index febbeef323..0b8531b13e 100644
--- a/src/lib/eina/eina_promise.h
+++ b/src/lib/eina/eina_promise.h
@@ -9,6 +9,8 @@ extern "C" {
9#include "eina_types.h" 9#include "eina_types.h"
10#include "eina_value.h" 10#include "eina_value.h"
11 11
12typedef struct _Eina_Coro Eina_Coro;
13
12/** 14/**
13 * @ingroup Eina_Promise 15 * @ingroup Eina_Promise
14 * 16 *
@@ -1416,6 +1418,66 @@ eina_future_race_array(Eina_Future *array[])
1416 */ 1418 */
1417#define eina_future_chain_easy(_prev, ...) eina_future_chain_easy_array(_prev, (Eina_Future_Cb_Easy_Desc[]) {__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}}) 1419#define eina_future_chain_easy(_prev, ...) eina_future_chain_easy_array(_prev, (Eina_Future_Cb_Easy_Desc[]) {__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}})
1418 1420
1421/**
1422 * Allows an Eina_Coro to wait for a future to be resolved in a
1423 * synchronous way.
1424 *
1425 * This is another way to use future: instead of being called back
1426 * when it resolves, one can use an Eina_Coro coroutine and that will
1427 * "block" waiting for the value. Note that only the coroutine is
1428 * blocked, the main routine (ie: main loop) will still run so it can
1429 * resolve futures.
1430 *
1431 * Internally it will call eina_coro_await() and if the coroutine is
1432 * canceled ECANCELED is returned.
1433 *
1434 * Using pure Eina_Coro callbacks, it looks like:
1435 *
1436 * @code
1437 * const void *
1438 * my_coroutine(void *data, Eina_Bool canceled, Eina_Coro *coro)
1439 * {
1440 * Eina_Future *f = get_file_size_async("/MyFile.txt");
1441 * Eina_Value v = eina_future_await(f, coro, EINA_VALUE_TYPE_INT);
1442 * int size;
1443 * if (v.type == EINA_VALUE_TYPE_ERROR)
1444 * {
1445 * Eina_Error err;
1446 * eina_value_get(&v, &err);
1447 * fprintf(stderr, "Could not read the file size. Reason: %s\n", eina_error_msg_get(err));
1448 * ecore_main_loop_quit();
1449 * return NULL;
1450 * }
1451 * eina_value_get(&v, &size);
1452 * printf("File size is %d bytes\n", size);
1453 * return NULL;
1454 * }
1455 * @endcode
1456 *
1457 * However Efl_Loop offers efl_loop_coro() to create coroutines that
1458 * will be scheduled in the main loop, it's more convenient and can be
1459 * used as well. The only change is the function signature.
1460 *
1461 * @note this is a helper over eina_coro_await(), doing type checking
1462 * and returning as a value so it's easier to use.
1463 *
1464 * @param f A future to wait
1465 * @param coro A coroutine handle. Must be called from the coroutine itself.
1466 * @param success_type if non-NULL, will ensure the success value is
1467 * of the given type. Note that EINA_VALUE_TYPE_ERROR will be
1468 * returned, such as ECANCELED, EINVAL, ENOMEM and so on.
1469 *
1470 * @return A value. The caller owns it and must call
1471 * eina_value_flush().
1472 *
1473 * @see eina_future_new()
1474 * @see eina_future_then()
1475 * @see eina_future_chain()
1476 * @see eina_coro_new()
1477 * @see eina_coro_await()
1478 * @see efl_loop_coro()
1479 */
1480EAPI Eina_Value eina_future_await(Eina_Future *f, Eina_Coro *coro, const Eina_Value_Type *success_type) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT;
1419 1481
1420/** 1482/**
1421 * @} 1483 * @}
diff --git a/src/tests/eina/eina_suite.c b/src/tests/eina/eina_suite.c
index 8e833356f3..26e1387f0e 100644
--- a/src/tests/eina/eina_suite.c
+++ b/src/tests/eina/eina_suite.c
@@ -87,6 +87,7 @@ static const Efl_Test_Case etc[] = {
87 { "Free Queue", eina_test_freeq }, 87 { "Free Queue", eina_test_freeq },
88 { "Util", eina_test_util }, 88 { "Util", eina_test_util },
89 { "Short Lived Strings", eina_test_slstr }, 89 { "Short Lived Strings", eina_test_slstr },
90 { "Coroutines", eina_test_coro },
90 { NULL, NULL } 91 { NULL, NULL }
91}; 92};
92 93
diff --git a/src/tests/eina/eina_suite.h b/src/tests/eina/eina_suite.h
index 7bf643e478..f8dd388d00 100644
--- a/src/tests/eina/eina_suite.h
+++ b/src/tests/eina/eina_suite.h
@@ -74,5 +74,6 @@ void eina_test_safepointer(TCase *tc);
74void eina_test_slice(TCase *tc); 74void eina_test_slice(TCase *tc);
75void eina_test_freeq(TCase *tc); 75void eina_test_freeq(TCase *tc);
76void eina_test_slstr(TCase *tc); 76void eina_test_slstr(TCase *tc);
77void eina_test_coro(TCase *tc);
77 78
78#endif /* EINA_SUITE_H_ */ 79#endif /* EINA_SUITE_H_ */
diff --git a/src/tests/eina/eina_test_coro.c b/src/tests/eina/eina_test_coro.c
new file mode 100644
index 0000000000..60479340ae
--- /dev/null
+++ b/src/tests/eina/eina_test_coro.c
@@ -0,0 +1,562 @@
1#ifdef HAVE_CONFIG_H
2# include "config.h"
3#endif
4
5#include <Eina.h>
6
7#include "eina_suite.h"
8
9struct ctx {
10 int a, b;
11};
12
13#define VAL_A 1234
14#define VAL_B 4567
15#define RETVAL (void*)8901
16#define CANCELVAL (void*)0xCA
17#define COUNT 100
18
19#ifdef EINA_SAFETY_CHECKS
20struct log_ctx {
21 const char *msg;
22 const char *fnc;
23 int level;
24 Eina_Bool did;
25 Eina_Bool just_fmt;
26};
27
28/* tests should not output on success, just uncomment this for debugging */
29//#define SHOW_LOG 1
30
31static void
32_eina_test_safety_print_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, void *data, va_list args EINA_UNUSED)
33{
34 struct log_ctx *ctx = data;
35 va_list cp_args;
36 const char *str;
37
38 va_copy(cp_args, args);
39 str = va_arg(cp_args, const char *);
40 va_end(cp_args);
41
42 ck_assert_int_eq(level, ctx->level);
43 if (ctx->just_fmt)
44 ck_assert_str_eq(fmt, ctx->msg);
45 else
46 {
47 ck_assert_str_eq(fmt, "%s");
48 ck_assert_str_eq(ctx->msg, str);
49 }
50 ck_assert_str_eq(ctx->fnc, fnc);
51 ctx->did = EINA_TRUE;
52
53#ifdef SHOW_LOG
54 eina_log_print_cb_stderr(d, level, file, fnc, line, fmt, NULL, args);
55#else
56 (void)d;
57 (void)file;
58 (void)line;
59#endif
60}
61#endif
62
63
64static const void *
65coro_func_noyield(void *data, Eina_Bool canceled, Eina_Coro *coro EINA_UNUSED)
66{
67 struct ctx *ctx = data;
68
69 ck_assert_ptr_nonnull(ctx);
70 ck_assert_int_eq(ctx->a, VAL_A);
71
72 if (canceled) return CANCELVAL;
73
74 ctx->b = VAL_B;
75
76 return RETVAL;
77}
78
79START_TEST(coro_noyield)
80{
81 Eina_Coro *coro;
82 struct ctx ctx = {
83 .a = VAL_A,
84 .b = 0,
85 };
86 void *result = NULL;
87 int i = 0;
88
89 eina_init();
90
91 coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
92 ck_assert_ptr_nonnull(coro);
93
94 while (eina_coro_run(&coro, &result, NULL))
95 {
96 i++;
97 ck_assert_int_le(i, 1);
98 }
99 ck_assert_ptr_null(coro);
100
101 ck_assert_int_eq(ctx.b, VAL_B);
102 ck_assert_ptr_eq(result, RETVAL);
103
104 eina_shutdown();
105}
106END_TEST
107
108static const void *
109coro_func_yield(void *data, Eina_Bool canceled, Eina_Coro *coro)
110{
111 struct ctx *ctx = data;
112 char buf[256] = "hi there";
113 int i;
114
115 ck_assert_ptr_nonnull(ctx);
116 ck_assert_int_eq(ctx->a, VAL_A);
117
118 if (canceled) return CANCELVAL;
119
120 ctx->b = 1;
121 eina_coro_yield_or_return(coro, CANCELVAL);
122
123 ctx->b = 2;
124 eina_coro_yield_or_return(coro, CANCELVAL);
125
126 for (i = 0; i < COUNT; i++) {
127 ctx->b = i * 10;
128 /* have some stuff on stack and write to it, so we validate
129 * non-thread based solutions are really saving their stack.
130 */
131 snprintf(buf, sizeof(buf), "b=%d -----------------", ctx->b);
132 eina_coro_yield_or_return(coro, CANCELVAL);
133 }
134
135 ctx->b = VAL_B;
136
137 return RETVAL;
138}
139
140START_TEST(coro_yield)
141{
142 Eina_Coro *coro;
143 struct ctx ctx = {
144 .a = VAL_A,
145 .b = 0,
146 };
147 Eina_Bool r;
148 void *result = NULL;
149 int i = 0;
150
151 eina_init();
152
153 coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
154 ck_assert_ptr_nonnull(coro);
155
156 r = eina_coro_run(&coro, NULL, NULL);
157 ck_assert_int_eq(r, EINA_TRUE);
158 ck_assert_int_eq(ctx.b, 1);
159
160 r = eina_coro_run(&coro, NULL, NULL);
161 ck_assert_int_eq(r, EINA_TRUE);
162 ck_assert_int_eq(ctx.b, 2);
163
164 while (eina_coro_run(&coro, &result, NULL))
165 {
166 ck_assert_int_eq(ctx.b, i * 10);
167 i++;
168 ck_assert_int_le(i, COUNT);
169 /* change caller's stack to guarantee coroutine stack is
170 * being properly persisted.
171 */
172 memset(alloca(10), 0xff, 10);
173 }
174 ck_assert_ptr_null(coro);
175 ck_assert_int_eq(i, COUNT);
176
177 ck_assert_int_eq(ctx.b, VAL_B);
178 ck_assert_ptr_eq(result, RETVAL);
179
180 eina_shutdown();
181}
182END_TEST
183
184START_TEST(coro_cancel)
185{
186 Eina_Coro *coro;
187 struct ctx ctx = {
188 .a = VAL_A,
189 .b = 0,
190 };
191 Eina_Bool r;
192
193 eina_init();
194
195 // cancel before it runs
196 coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
197 ck_assert_ptr_nonnull(coro);
198
199 ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
200 ck_assert_ptr_null(coro);
201
202 // cancel after single run
203 coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
204 ck_assert_ptr_nonnull(coro);
205
206 r = eina_coro_run(&coro, NULL, NULL);
207 ck_assert_int_eq(r, EINA_TRUE);
208 ck_assert_int_eq(ctx.b, 1);
209
210 ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
211 ck_assert_ptr_null(coro);
212 ck_assert_int_eq(ctx.b, 1); // it's yielding after setting b=1
213
214 eina_shutdown();
215}
216END_TEST
217
218static Eina_Bool
219_coro_hook_enter_success(void *data, const Eina_Coro *coro EINA_UNUSED)
220{
221 int *i = data;
222 (*i) += 1;
223 return EINA_TRUE;
224}
225
226static void
227_coro_hook_exit(void *data, const Eina_Coro *coro EINA_UNUSED)
228{
229 int *i = data;
230 (*i) += 100;
231}
232
233static void
234_main_hook_enter(void *data, const Eina_Coro *coro EINA_UNUSED)
235{
236 int *i = data;
237 (*i) += 10;
238}
239
240static Eina_Bool
241_main_hook_exit_success(void *data, const Eina_Coro *coro EINA_UNUSED)
242{
243 int *i = data;
244 (*i) += 1000;
245 return EINA_TRUE;
246}
247
248START_TEST(coro_hook)
249{
250 Eina_Coro *coro;
251 struct ctx ctx = {
252 .a = VAL_A,
253 .b = 0,
254 };
255 void *result = NULL;
256 int i = 0, hooks_result = 0;
257
258 eina_init();
259
260 fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
261 _coro_hook_exit,
262 _main_hook_enter,
263 _main_hook_exit_success,
264 &hooks_result));
265
266 coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
267 ck_assert_ptr_nonnull(coro);
268
269 while (eina_coro_run(&coro, &result, NULL))
270 {
271 i++;
272 ck_assert_int_le(i, 1);
273 }
274 ck_assert_ptr_null(coro);
275
276 ck_assert_int_eq(ctx.b, VAL_B);
277 ck_assert_ptr_eq(result, RETVAL);
278
279 fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
280 _coro_hook_exit,
281 _main_hook_enter,
282 _main_hook_exit_success,
283 &hooks_result));
284
285 ck_assert_int_eq(hooks_result, 1111);
286
287 eina_shutdown();
288}
289END_TEST
290
291#ifdef EINA_SAFETY_CHECKS
292START_TEST(coro_new_null)
293{
294 Eina_Coro *coro;
295
296 eina_init();
297
298#ifdef SHOW_LOG
299 fprintf(stderr, "you should have a safety check failure below:\n");
300#endif
301 struct log_ctx lctx;
302
303#define TEST_MAGIC_SAFETY(fn, _msg) \
304 lctx.msg = _msg; \
305 lctx.fnc = fn; \
306 lctx.just_fmt = EINA_FALSE; \
307 lctx.level = EINA_LOG_LEVEL_ERR; \
308 lctx.did = EINA_FALSE
309
310 eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
311
312 TEST_MAGIC_SAFETY("eina_coro_new", "safety check failed: func == NULL");
313
314 coro = eina_coro_new(NULL, NULL, EINA_CORO_STACK_SIZE_DEFAULT);
315 ck_assert_ptr_null(coro);
316
317 fail_unless(lctx.did);
318
319 eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
320#undef TEST_MAGIC_SAFETY
321
322 eina_shutdown();
323}
324END_TEST
325
326START_TEST(coro_yield_incorrect)
327{
328 Eina_Coro *coro;
329 struct ctx ctx = {
330 .a = VAL_A,
331 .b = 0,
332 };
333 void *result = NULL;
334 int i = 0;
335
336 eina_init();
337
338#ifdef SHOW_LOG
339 fprintf(stderr, "you should have a safety check failure below:\n");
340#endif
341 struct log_ctx lctx;
342
343#define TEST_MAGIC_SAFETY(fn, _msg) \
344 lctx.msg = _msg; \
345 lctx.fnc = fn; \
346 lctx.just_fmt = EINA_TRUE; \
347 lctx.level = EINA_LOG_LEVEL_CRITICAL; \
348 lctx.did = EINA_FALSE
349
350 eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
351
352 coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
353 ck_assert_ptr_nonnull(coro);
354
355 TEST_MAGIC_SAFETY("eina_coro_yield", "must be called from coroutine! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
356 fail_if(eina_coro_yield(coro));
357 fail_unless(lctx.did);
358
359 eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
360#undef TEST_MAGIC_SAFETY
361
362 while (eina_coro_run(&coro, &result, NULL))
363 {
364 i++;
365 ck_assert_int_le(i, 1);
366 }
367 ck_assert_ptr_null(coro);
368
369 ck_assert_int_eq(ctx.b, VAL_B);
370 ck_assert_ptr_eq(result, RETVAL);
371
372 eina_shutdown();
373}
374END_TEST
375
376static const void *
377coro_func_run_incorrect(void *data, Eina_Bool canceled, Eina_Coro *coro)
378{
379#ifdef SHOW_LOG
380 fprintf(stderr, "you should have a safety check failure below:\n");
381#endif
382 struct log_ctx lctx;
383
384#define TEST_MAGIC_SAFETY(fn, _msg) \
385 lctx.msg = _msg; \
386 lctx.fnc = fn; \
387 lctx.just_fmt = EINA_TRUE; \
388 lctx.level = EINA_LOG_LEVEL_CRITICAL; \
389 lctx.did = EINA_FALSE
390
391 eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
392
393 TEST_MAGIC_SAFETY("eina_coro_run", "must be called from main thread! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
394 fail_if(eina_coro_run(&coro, NULL, NULL));
395 fail_unless(lctx.did);
396
397 eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
398#undef TEST_MAGIC_SAFETY
399
400 return coro_func_noyield(data, canceled, coro);
401}
402
403START_TEST(coro_run_incorrect)
404{
405 Eina_Coro *coro;
406 struct ctx ctx = {
407 .a = VAL_A,
408 .b = 0,
409 };
410 void *result = NULL;
411 int i = 0;
412
413 eina_init();
414
415 coro = eina_coro_new(coro_func_run_incorrect, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
416 ck_assert_ptr_nonnull(coro);
417
418 while (eina_coro_run(&coro, &result, NULL))
419 {
420 i++;
421 ck_assert_int_le(i, 1);
422 }
423 ck_assert_ptr_null(coro);
424
425 ck_assert_int_eq(ctx.b, VAL_B);
426 ck_assert_ptr_eq(result, RETVAL);
427
428 eina_shutdown();
429}
430END_TEST
431
432static Eina_Bool
433_coro_hook_enter_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
434{
435 int *i = data;
436 (*i) += 2;
437 return EINA_FALSE;
438}
439
440 static Eina_Bool
441_main_hook_exit_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
442{
443 int *i = data;
444 (*i) += 2000;
445 return EINA_FALSE;
446}
447
448START_TEST(coro_hook_failed)
449{
450 Eina_Coro *coro;
451 struct ctx ctx = {
452 .a = VAL_A,
453 .b = 0,
454 };
455 void *result = NULL;
456 int i = 0, hooks_result = 0;
457
458 eina_init();
459
460#ifdef SHOW_LOG
461 fprintf(stderr, "you should have a safety check failure below:\n");
462#endif
463 struct log_ctx lctx;
464
465#define TEST_MAGIC_SAFETY(fn, _msg) \
466 lctx.msg = _msg; \
467 lctx.fnc = fn; \
468 lctx.just_fmt = EINA_TRUE; \
469 lctx.level = EINA_LOG_LEVEL_ERR; \
470 lctx.did = EINA_FALSE
471
472 eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
473
474 fail_unless(eina_coro_hook_add(_coro_hook_enter_failed,
475 _coro_hook_exit,
476 _main_hook_enter,
477 _main_hook_exit_success,
478 &hooks_result));
479
480 coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
481 ck_assert_ptr_nonnull(coro);
482
483 TEST_MAGIC_SAFETY("_eina_coro_hooks_coro_enter", "failed hook enter=%p data=%p for coroutine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
484
485 while (eina_coro_run(&coro, &result, NULL))
486 {
487 i++;
488 ck_assert_int_le(i, 1);
489 }
490 ck_assert_ptr_null(coro);
491
492 ck_assert_int_eq(ctx.b, 0);
493 ck_assert_ptr_eq(result, CANCELVAL);
494
495 fail_unless(lctx.did);
496
497 fail_unless(eina_coro_hook_del(_coro_hook_enter_failed,
498 _coro_hook_exit,
499 _main_hook_enter,
500 _main_hook_exit_success,
501 &hooks_result));
502
503 ck_assert_int_eq(hooks_result, 1112);
504
505 // now fail main exit
506 hooks_result = 0;
507
508 fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
509 _coro_hook_exit,
510 _main_hook_enter,
511 _main_hook_exit_failed,
512 &hooks_result));
513
514 coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
515 ck_assert_ptr_nonnull(coro);
516
517 TEST_MAGIC_SAFETY("_eina_coro_hooks_main_exit", "failed hook exit=%p data=%p for main routine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
518
519 while (eina_coro_run(&coro, &result, NULL))
520 {
521 i++;
522 ck_assert_int_le(i, 1);
523 }
524 ck_assert_ptr_null(coro);
525
526 ck_assert_int_eq(ctx.b, 0);
527 ck_assert_ptr_eq(result, CANCELVAL);
528
529 fail_unless(lctx.did);
530
531 fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
532 _coro_hook_exit,
533 _main_hook_enter,
534 _main_hook_exit_failed,
535 &hooks_result));
536
537 ck_assert_int_eq(hooks_result, 2111);
538
539 eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
540#undef TEST_MAGIC_SAFETY
541
542 eina_shutdown();
543}
544END_TEST
545#endif
546
547void
548eina_test_coro(TCase *tc)
549{
550 tcase_add_test(tc, coro_noyield);
551 tcase_add_test(tc, coro_yield);
552 // coro_await is tested in ecore_suite, so it hooks into the main loop
553 tcase_add_test(tc, coro_cancel);
554 tcase_add_test(tc, coro_hook);
555
556#ifdef EINA_SAFETY_CHECKS
557 tcase_add_test(tc, coro_new_null);
558 tcase_add_test(tc, coro_yield_incorrect);
559 tcase_add_test(tc, coro_run_incorrect);
560 tcase_add_test(tc, coro_hook_failed);
561#endif
562}