#include "evas_common_private.h" #include "Ecore.h" #include static Eina_Thread evas_thread_worker; static Eina_Condition evas_thread_queue_condition; static Eina_Lock evas_thread_queue_lock; static Eina_Bool evas_thread_queue_ready = EINA_FALSE; static Eina_Inarray evas_thread_queue; static Evas_Thread_Command *evas_thread_queue_cache = NULL; static unsigned int evas_thread_queue_cache_max = 0; static Eina_Lock evas_thread_exited_lock; static int evas_thread_exited = 0; static Eina_Bool exit_thread = EINA_FALSE; static int init_count = 0; #define SHUTDOWN_TIMEOUT_RESET (0) #define SHUTDOWN_TIMEOUT_CHECK (1) #define SHUTDOWN_TIMEOUT (3000) struct fence_stuff { Eina_Lock lock; Eina_Condition cond; }; static void _evas_thread_queue_fence(void *data) { struct fence_stuff *f; f = data; eina_lock_take(&f->lock); eina_condition_signal(&f->cond); eina_lock_release(&f->lock); } EAPI void evas_thread_queue_wait(void) { struct fence_stuff f; /* No shortcuts here - if the thread queue looks empty it could just mean * it's being processed. Need a round trip. */ if (!eina_lock_new(&f.lock)) return; if (!eina_condition_new(&f.cond, &f.lock)) { eina_lock_free(&f.lock); return; } eina_lock_take(&f.lock); evas_thread_queue_flush(_evas_thread_queue_fence, &f); eina_condition_wait(&f.cond); eina_lock_release(&f.lock); eina_lock_free(&f.lock); eina_condition_free(&f.cond); } static Eina_Bool _shutdown_timeout(double *time, int mode, int timeout_ms) { struct timeval tv; gettimeofday(&tv, NULL); if ( mode == SHUTDOWN_TIMEOUT_RESET ) *time = (tv.tv_sec + tv.tv_usec / 1000000.0) * 1000.0; return ((tv.tv_sec + tv.tv_usec / 1000000.0) * 1000.0 - (*time)) > timeout_ms ; } static void evas_thread_queue_append(Evas_Thread_Command_Cb cb, void *data, Eina_Bool do_flush) { Evas_Thread_Command *cmd; eina_lock_take(&evas_thread_queue_lock); cmd = eina_inarray_grow(&evas_thread_queue, 1); if (cmd) { cmd->cb = cb; cmd->data = data; } else { ERR("Out of memory allocating thread command."); goto out; } if (do_flush) { evas_thread_queue_ready = EINA_TRUE; eina_condition_signal(&evas_thread_queue_condition); } out: eina_lock_release(&evas_thread_queue_lock); } EAPI void evas_thread_cmd_enqueue(Evas_Thread_Command_Cb cb, void *data) { evas_thread_queue_append(cb, data, EINA_FALSE); } EAPI void evas_thread_queue_flush(Evas_Thread_Command_Cb cb, void *data) { evas_thread_queue_append(cb, data, EINA_TRUE); } static void* evas_thread_worker_func(void *data EINA_UNUSED, Eina_Thread thread EINA_UNUSED) { eina_thread_name_set(eina_thread_self(), "Eevas-thread-wk"); while (1) { Evas_Thread_Command *cmd; unsigned int len, max; eina_lock_take(&evas_thread_queue_lock); while (!evas_thread_queue_ready) { if (exit_thread) { eina_lock_release(&evas_thread_queue_lock); goto out; } eina_condition_timedwait(&evas_thread_queue_condition, SHUTDOWN_TIMEOUT * 0.75); } if (!eina_inarray_count(&evas_thread_queue)) { ERR("Signaled to find an empty queue. BUG!"); evas_thread_queue_ready = EINA_FALSE; eina_lock_release(&evas_thread_queue_lock); continue; } cmd = evas_thread_queue.members; evas_thread_queue.members = evas_thread_queue_cache; evas_thread_queue_cache = cmd; max = evas_thread_queue.max; evas_thread_queue.max = evas_thread_queue_cache_max; evas_thread_queue_cache_max = max; len = evas_thread_queue.len; evas_thread_queue.len = 0; evas_thread_queue_ready = EINA_FALSE; eina_lock_release(&evas_thread_queue_lock); DBG("Evas render thread command queue length: %u", len); eina_evlog("+thread", NULL, 0.0, NULL); while (len) { assert(cmd->cb); eina_evlog("+thread_do", cmd->data, 0.0, NULL); cmd->cb(cmd->data); eina_evlog("-thread_do", cmd->data, 0.0, NULL); cmd++; len--; } eina_evlog("-thread", NULL, 0.0, NULL); } out: eina_lock_take(&evas_thread_exited_lock); evas_thread_exited = 1; eina_lock_release(&evas_thread_exited_lock); return NULL; } static void evas_thread_fork_reset(void *data EINA_UNUSED) { if (!eina_lock_new(&evas_thread_exited_lock)) { CRI("Could not create exit thread lock (%m)"); goto on_error; } if (!eina_lock_new(&evas_thread_queue_lock)) { CRI("Could not create draw thread lock (%m)"); goto on_error; } if (!eina_condition_new(&evas_thread_queue_condition, &evas_thread_queue_lock)) { CRI("Could not create draw thread condition (%m)"); goto on_error; } if (!eina_thread_create(&evas_thread_worker, EINA_THREAD_NORMAL, -1, evas_thread_worker_func, NULL)) { CRI("Could not recreate draw thread."); goto on_error; } return ; on_error: eina_lock_free(&evas_thread_exited_lock); eina_lock_free(&evas_thread_queue_lock); eina_condition_free(&evas_thread_queue_condition); evas_thread_worker = 0; free(evas_thread_queue_cache); evas_thread_queue_cache = NULL; evas_thread_queue_cache_max = 0; eina_inarray_flush(&evas_thread_queue); eina_threads_shutdown(); } int evas_thread_init(void) { if (init_count++) return init_count; exit_thread = EINA_FALSE; evas_thread_exited = 0; if(!eina_threads_init()) { CRI("Could not init eina threads"); goto fail_on_eina_thread_init; } eina_inarray_step_set(&evas_thread_queue, sizeof (Eina_Inarray), sizeof (Evas_Thread_Command), 128); if (!eina_lock_new(&evas_thread_exited_lock)) { CRI("Could not create exit thread lock (%m)"); goto fail_on_lock_creation; } if (!eina_lock_new(&evas_thread_queue_lock)) { CRI("Could not create draw thread lock (%m)"); goto fail_on_lock_creation; } if (!eina_condition_new(&evas_thread_queue_condition, &evas_thread_queue_lock)) { CRI("Could not create draw thread condition (%m)"); goto fail_on_cond_creation; } if (!eina_thread_create(&evas_thread_worker, EINA_THREAD_NORMAL, -1, evas_thread_worker_func, NULL)) { CRI("Could not create draw thread (%m)"); goto fail_on_thread_creation; } ecore_fork_reset_callback_add(evas_thread_fork_reset, NULL); return init_count; fail_on_thread_creation: evas_thread_worker = 0; eina_condition_free(&evas_thread_queue_condition); fail_on_cond_creation: eina_lock_free(&evas_thread_exited_lock); eina_lock_free(&evas_thread_queue_lock); fail_on_lock_creation: eina_threads_shutdown(); fail_on_eina_thread_init: exit_thread = EINA_TRUE; evas_thread_exited = 1; return --init_count; } int evas_thread_shutdown(void) { double to = 0 ; if (init_count <= 0) { ERR("Too many calls to shutdown, ignored."); return 0; } if (--init_count) return init_count; if (!evas_thread_worker) return init_count; eina_lock_take(&evas_thread_queue_lock); ecore_fork_reset_callback_del(evas_thread_fork_reset, NULL); exit_thread = EINA_TRUE; eina_condition_signal(&evas_thread_queue_condition); eina_lock_release(&evas_thread_queue_lock); _shutdown_timeout(&to, SHUTDOWN_TIMEOUT_RESET, SHUTDOWN_TIMEOUT); while (1) { int exited; eina_lock_take(&evas_thread_exited_lock); exited = evas_thread_exited; eina_lock_release(&evas_thread_exited_lock); if (exited || (evas_async_events_process() == -1)) break; if(_shutdown_timeout(&to, SHUTDOWN_TIMEOUT_CHECK, SHUTDOWN_TIMEOUT)) { CRI("Timeout shutdown thread. Skipping thread_join. Some resources could be leaked"); goto timeout_shutdown; } } eina_thread_join(evas_thread_worker); timeout_shutdown: eina_lock_free(&evas_thread_exited_lock); eina_lock_free(&evas_thread_queue_lock); eina_condition_free(&evas_thread_queue_condition); evas_thread_worker = 0; free(evas_thread_queue_cache); evas_thread_queue_cache = NULL; evas_thread_queue_cache_max = 0; eina_inarray_flush(&evas_thread_queue); eina_threads_shutdown(); return 0; }