| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / common tools sub-project | |
| * | |
| * GPAC 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, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #ifdef GPAC_CONFIG_ANDROID | |
| #include <jni.h> | |
| #endif | |
| #include <gpac/thread.h> | |
| #ifndef GPAC_DISABLE_THREADS | |
| #if defined(WIN32) || defined(_WIN32_WCE) | |
| /*win32 threads*/ | |
| #include <windows.h> | |
| typedef HANDLE TH_HANDLE; | |
| #else | |
| /*pthreads*/ | |
| #include <sched.h> | |
| #include <pthread.h> | |
| #include <semaphore.h> | |
| #include <errno.h> | |
| typedef pthread_t TH_HANDLE ; | |
| #endif | |
| /********************************************************************* | |
| OS-Specific Thread Object | |
| **********************************************************************/ | |
| struct __tag_thread | |
| { | |
| u32 status; | |
| TH_HANDLE threadH; | |
| u32 stackSize; | |
| /* the thread procedure */ | |
| u32 (*Run)(void *param); | |
| void *args; | |
| /* lock for signal */ | |
| GF_Semaphore *_signal; | |
| Bool blocking; | |
| Bool no_kill; | |
| #ifndef GPAC_DISABLE_LOG | |
| u32 id; | |
| char *log_name; | |
| #endif | |
| #ifdef GPAC_CONFIG_ANDROID | |
| u32 (*RunBeforeExit)(void *param); | |
| #endif | |
| }; | |
| #ifndef GPAC_DISABLE_LOG | |
| #include <gpac/list.h> | |
| static GF_List *thread_bank = NULL; | |
| static u32 main_th_id = 0; | |
| static void log_add_thread(GF_Thread *t) | |
| { | |
| if (!thread_bank) { | |
| thread_bank = gf_list_new(); | |
| main_th_id = gf_th_id(); | |
| } | |
| gf_list_add(thread_bank, t); | |
| } | |
| static void log_del_thread(GF_Thread *t) | |
| { | |
| gf_list_del_item(thread_bank, t); | |
| if (!gf_list_count(thread_bank)) { | |
| gf_list_del(thread_bank); | |
| thread_bank = NULL; | |
| } | |
| } | |
| static const char *log_th_name(u32 id, char szName[100]) | |
| { | |
| u32 i, count; | |
| if (!id) id = gf_th_id(); | |
| if (main_th_id == id) return "Main Process"; | |
| count = gf_list_count(thread_bank); | |
| for (i=0; i<count; i++) { | |
| GF_Thread *t = (GF_Thread*)gf_list_get(thread_bank, i); | |
| if (t->id == id) return t->log_name; | |
| } | |
| sprintf(szName, "%u", id); | |
| return szName; | |
| } | |
| const char *gf_th_log_name(GF_Thread *t) | |
| { | |
| return t ? t->log_name : "Main Process"; | |
| } | |
| #endif | |
| GF_EXPORT | |
| GF_Thread *gf_th_new(const char *name) | |
| { | |
| if (gf_opts_get_bool("core", "no-mx")) return NULL; | |
| GF_Thread *tmp = (GF_Thread*)gf_malloc(sizeof(GF_Thread)); | |
| memset(tmp, 0, sizeof(GF_Thread)); | |
| tmp->status = GF_THREAD_STATUS_STOP; | |
| #ifdef GPAC_CONFIG_EMSCRIPTEN | |
| tmp->blocking = GF_FALSE; | |
| #else | |
| tmp->blocking = GF_TRUE; | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| if (name) { | |
| tmp->log_name = gf_strdup(name); | |
| } else { | |
| char szN[20]; | |
| sprintf(szN, "%p", (void*)tmp); | |
| tmp->log_name = gf_strdup(szN); | |
| } | |
| log_add_thread(tmp); | |
| #endif | |
| return tmp; | |
| } | |
| #ifdef GPAC_CONFIG_ANDROID | |
| #include <pthread.h> | |
| static pthread_key_t currentThreadInfoKey = 0; | |
| /* Unique allocation of key */ | |
| static pthread_once_t currentThreadInfoKey_once = PTHREAD_ONCE_INIT; | |
| GF_Err gf_register_before_exit_function(GF_Thread *t, u32 (*toRunBeforePthreadExit)(void *param) ) | |
| { | |
| if (!t) | |
| return GF_BAD_PARAM; | |
| t->RunBeforeExit = toRunBeforePthreadExit; | |
| return GF_OK; | |
| } | |
| /* Unique key allocation */ | |
| static void currentThreadInfoKey_alloc() | |
| { | |
| int err; | |
| /* We do not use any destructor */ | |
| err = pthread_key_create(¤tThreadInfoKey, NULL); | |
| if (err) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] pthread_key_create() failed with error %d\n", err)); | |
| } | |
| } | |
| GF_Thread * gf_th_current() { | |
| return pthread_getspecific(currentThreadInfoKey); | |
| } | |
| #endif /* GPAC_CONFIG_ANDROID */ | |
| #if defined(GPAC_CONFIG_EMSCRIPTEN) && !defined(GPAC_DISABLE_THREADS) | |
| #include <emscripten/eventloop.h> | |
| #endif | |
| #ifdef WIN32 | |
| DWORD WINAPI RunThread(void *ptr) | |
| { | |
| DWORD ret = 0; | |
| #else | |
| void * RunThread(void *ptr) | |
| { | |
| long int ret = 0; | |
| #endif | |
| GF_Thread *t = (GF_Thread *)ptr; | |
| //not activated by default, not portable | |
| #if 0 && !defined(GPAC_DISABLE_LOG) | |
| if (t->log_name) { | |
| #ifdef WIN32 | |
| #elif defined(__DARWIN__) || defined(__APPLE__) | |
| pthread_setname_np(t->log_name); | |
| #else | |
| pthread_setname_np(t->threadH, t->log_name); | |
| #endif | |
| } | |
| #endif | |
| /* Signal the caller */ | |
| if (t->blocking && !t->_signal) goto exit; | |
| #ifdef GPAC_CONFIG_ANDROID | |
| if (pthread_once(¤tThreadInfoKey_once, ¤tThreadInfoKey_alloc) || pthread_setspecific(currentThreadInfoKey, t)) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] Couldn't run thread %s, ID %u\n", t->log_name, t->id)); | |
| #endif /* GPAC_CONFIG_ANDROID */ | |
| t->status = GF_THREAD_STATUS_RUN; | |
| if (t->blocking) | |
| gf_sema_notify(t->_signal, 1); | |
| #ifndef GPAC_DISABLE_LOG | |
| t->id = gf_th_id(); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("[Thread %s] At %d Entering thread proc - thread ID %u\n", t->log_name, gf_sys_clock(), t->id)); | |
| #endif | |
| /* Each thread has its own seed */ | |
| gf_rand_init(GF_FALSE); | |
| /* Run our thread */ | |
| ret = t->Run(t->args); | |
| exit: | |
| if (t->no_kill) { | |
| #if defined(GPAC_CONFIG_EMSCRIPTEN) && !defined(GPAC_DISABLE_THREADS) | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Thread %s] exit with no kill - unwinding to JS event loop\n", t->log_name)); | |
| emscripten_unwind_to_js_event_loop(); | |
| return (void *)ret; | |
| #elif defined(WIN32) | |
| return ret; | |
| #else | |
| return (void *)ret; | |
| #endif | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("[Thread %s] At %d Exiting thread proc, return code %d\n", t->log_name, gf_sys_clock(), ret)); | |
| #endif | |
| t->status = GF_THREAD_STATUS_DEAD; | |
| t->Run = NULL; | |
| #ifdef WIN32 | |
| if (!CloseHandle(t->threadH)) { | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Thread %s] Couldn't close handle when exiting thread proc, error code: %d\n", t->log_name, err)); | |
| } | |
| t->threadH = NULL; | |
| return ret; | |
| #else | |
| #ifdef GPAC_CONFIG_ANDROID | |
| #ifndef GPAC_DISABLE_LOG | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("[Thread %s] RunBeforeExit=%p\n", t->log_name, t->RunBeforeExit)); | |
| #endif | |
| if (t->RunBeforeExit) | |
| t->RunBeforeExit(t->args); | |
| #endif /* GPAC_CONFIG_ANDROID */ | |
| pthread_exit((void *)0); | |
| return (void *)ret; | |
| #endif | |
| } | |
| GF_EXPORT | |
| GF_Err gf_th_run(GF_Thread *t, u32 (*Run)(void *param), void *param) | |
| { | |
| #ifdef WIN32 | |
| DWORD id; | |
| #else | |
| pthread_attr_t att; | |
| #endif | |
| if (!t || t->Run || t->_signal) { | |
| return GF_BAD_PARAM; | |
| } | |
| t->Run = Run; | |
| t->args = param; | |
| if (t->blocking) { | |
| t->_signal = gf_sema_new(1, 0); | |
| if (!t->_signal) return GF_IO_ERR; | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("[Thread %s] Starting\n", t->log_name)); | |
| #ifdef WIN32 | |
| t->threadH = CreateThread(NULL, t->stackSize, &(RunThread), (void *)t, 0, &id); | |
| if (t->threadH != NULL) { | |
| #ifdef _MSC_VER | |
| /*add thread name for the msvc debugger*/ | |
| #pragma pack(push,8) | |
| typedef struct { | |
| DWORD dwType; | |
| LPCSTR szName; | |
| DWORD dwThreadID; | |
| DWORD dwFlags; | |
| } THREADNAME_INFO; | |
| #pragma pack(pop) | |
| THREADNAME_INFO info; | |
| info.dwType = 0x1000; | |
| info.szName = t->log_name; | |
| info.dwThreadID = id; | |
| info.dwFlags = 0; | |
| __try { | |
| RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); | |
| } __except (EXCEPTION_CONTINUE_EXECUTION) { | |
| } | |
| #endif | |
| } else { | |
| #else | |
| if ( pthread_attr_init(&att) != 0 ) return GF_IO_ERR; | |
| pthread_attr_setdetachstate(&att, PTHREAD_CREATE_JOINABLE); | |
| if ( pthread_create(&t->threadH, &att, RunThread, t) != 0 ) { | |
| #endif | |
| t->status = GF_THREAD_STATUS_DEAD; | |
| return GF_IO_ERR; | |
| } | |
| if (t->blocking) { | |
| /*wait for the child function to call us - do NOT return before, otherwise the thread status would be unknown*/ | |
| gf_sema_wait(t->_signal); | |
| gf_sema_del(t->_signal); | |
| t->_signal = NULL; | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MUTEX, ("[Thread %s] Started\n", t->log_name)); | |
| return GF_OK; | |
| } | |
| #if defined(GPAC_CONFIG_EMSCRIPTEN) && !defined(GPAC_DISABLE_THREADS) | |
| #include <emscripten/threading.h> | |
| GF_Err gf_th_async_call(GF_Thread *t, u32 (*Run)(void *param), void *param) | |
| { | |
| t->no_kill = 1; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Thread %s] async EM call\n", t->log_name)); | |
| emscripten_dispatch_to_thread_async_(t->threadH, EM_FUNC_SIG_II, Run, NULL, param); | |
| return GF_OK; | |
| } | |
| #endif | |
| /* Stops a thread. If Destroy is not 0, thread is destroyed DANGEROUS as no cleanup */ | |
| void Thread_Stop(GF_Thread *t, Bool Destroy) | |
| { | |
| if (gf_th_status(t) == GF_THREAD_STATUS_RUN) { | |
| #ifdef WIN32 | |
| if (Destroy) { | |
| DWORD dw = 1; | |
| BOOL ret = TerminateThread(t->threadH, dw); | |
| if (!ret) { | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Thread %s] Couldn't stop thread ID 0x%08x, error code %d\n", t->log_name, t->id, err)); | |
| } | |
| t->threadH = NULL; | |
| } else { | |
| WaitForSingleObject(t->threadH, INFINITE); | |
| } | |
| #else | |
| if (Destroy) { | |
| #if defined(GPAC_CONFIG_ANDROID) || defined(PTHREAD_HAS_NO_CANCEL) | |
| if (pthread_kill(t->threadH, SIGQUIT)) | |
| #else | |
| if (pthread_cancel(t->threadH)) | |
| #endif | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Thread %s] Couldn't kill thread ID 0x%08x\n", t->log_name, t->id)); | |
| t->threadH = 0; | |
| } else { | |
| /*gracefully wait for Run to finish*/ | |
| #ifdef GPAC_CONFIG_EMSCRIPTEN | |
| //only destroy if in main application thread (otherwise emscripten assert on cleanupThread() fails) | |
| if (emscripten_is_main_runtime_thread()) { | |
| int pthread_tryjoin_np(pthread_t, void **); | |
| void *rval=NULL; | |
| if (!t->no_kill && pthread_tryjoin_np(t->threadH, &rval)) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Thread %s] pthread_join() returned an error with thread ID 0x%08x\n", t->log_name, t->id)); | |
| } | |
| #else | |
| if (pthread_join(t->threadH, NULL)) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Thread %s] pthread_join() returned an error with thread ID 0x%08x\n", t->log_name, t->id)); | |
| #endif | |
| } | |
| #endif | |
| } | |
| t->status = GF_THREAD_STATUS_DEAD; | |
| } | |
| GF_EXPORT | |
| void gf_th_stop(GF_Thread *t) | |
| { | |
| if (t) Thread_Stop(t, GF_FALSE); | |
| } | |
| GF_EXPORT | |
| void gf_th_del(GF_Thread *t) | |
| { | |
| Thread_Stop(t, GF_FALSE); | |
| #ifdef WIN32 | |
| // if (t->threadH) CloseHandle(t->threadH); | |
| #else | |
| /* It is necessary to free pthread handle */ | |
| if (t->threadH) | |
| pthread_detach(t->threadH); | |
| t->threadH = 0; | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| gf_free(t->log_name); | |
| log_del_thread(t); | |
| #endif | |
| gf_free(t); | |
| } | |
| GF_EXPORT | |
| void gf_th_set_priority(GF_Thread *t, s32 priority) | |
| { | |
| #ifdef WIN32 | |
| /*!! in WinCE, changin thread priority is extremely dangerous, it may freeze threads randomly !!*/ | |
| #ifndef _WIN32_WCE | |
| int _prio; | |
| BOOL ret; | |
| switch (priority) { | |
| case GF_THREAD_PRIORITY_IDLE: | |
| _prio = THREAD_PRIORITY_IDLE; | |
| break; | |
| case GF_THREAD_PRIORITY_LESS_IDLE: | |
| _prio = THREAD_PRIORITY_IDLE; | |
| break; | |
| case GF_THREAD_PRIORITY_LOWEST: | |
| _prio = THREAD_PRIORITY_LOWEST; | |
| break; | |
| case GF_THREAD_PRIORITY_LOW: | |
| _prio = THREAD_PRIORITY_BELOW_NORMAL; | |
| break; | |
| case GF_THREAD_PRIORITY_NORMAL: | |
| _prio = THREAD_PRIORITY_NORMAL; | |
| break; | |
| case GF_THREAD_PRIORITY_HIGH: | |
| _prio = THREAD_PRIORITY_ABOVE_NORMAL; | |
| break; | |
| case GF_THREAD_PRIORITY_HIGHEST: | |
| _prio = THREAD_PRIORITY_HIGHEST; | |
| break; | |
| default: /*GF_THREAD_PRIORITY_REALTIME -> GF_THREAD_PRIORITY_REALTIME_END*/ | |
| _prio = THREAD_PRIORITY_TIME_CRITICAL; | |
| break; | |
| } | |
| ret = SetThreadPriority(t ? t->threadH : GetCurrentThread(), _prio); | |
| if (!ret) { | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("[Thread %s] Couldn't set priority for thread ID 0x%08x, error %d\n", t->log_name, t->id, err)); | |
| } | |
| #endif | |
| #elif !defined(GPAC_CONFIG_EMSCRIPTEN) | |
| struct sched_param s_par; | |
| if (!t) return; | |
| /* consider this as real-time priority */ | |
| if (priority > 200) { | |
| s_par.sched_priority = priority - 200; | |
| if (pthread_setschedparam(t->threadH, SCHED_RR, &s_par)) | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("[Thread %s] Couldn't set priority(1) for thread ID 0x%08x\n", t->log_name, t->id)); | |
| } else { | |
| s_par.sched_priority = priority; | |
| if (pthread_setschedparam(t->threadH, SCHED_OTHER, &s_par)) | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("[Thread %s] Couldn't set priority(2) for thread ID 0x%08x\n", t->log_name, t->id)); | |
| } | |
| #endif | |
| } | |
| GF_EXPORT | |
| u32 gf_th_status(GF_Thread *t) | |
| { | |
| if (!t) return 0; | |
| return t->status; | |
| } | |
| GF_EXPORT | |
| u32 gf_th_id() | |
| { | |
| #ifdef WIN32 | |
| return ((u32) GetCurrentThreadId()); | |
| #else | |
| return ((u32) (PTR_TO_U_CAST(pthread_self()))); | |
| #endif | |
| } | |
| /********************************************************************* | |
| OS-Specific Mutex Object | |
| **********************************************************************/ | |
| struct __tag_mutex | |
| { | |
| #ifdef WIN32 | |
| HANDLE hMutex; | |
| #else | |
| pthread_mutex_t hMutex; | |
| #endif | |
| /* We filter recursive calls (1 thread calling Lock several times in a row only locks | |
| ONCE the mutex. Holder is the current ThreadID of the mutex holder*/ | |
| volatile u32 Holder; | |
| u32 HolderCount; | |
| #ifndef GPAC_DISABLE_LOG | |
| char *log_name; | |
| Bool nolog; | |
| #endif | |
| }; | |
| GF_EXPORT | |
| GF_Mutex *gf_mx_new(const char *name) | |
| { | |
| if (gf_opts_get_bool("core", "no-mx")) return NULL; | |
| #ifndef WIN32 | |
| pthread_mutexattr_t attr; | |
| #endif | |
| GF_Mutex *tmp = (GF_Mutex*)gf_malloc(sizeof(GF_Mutex)); | |
| if (!tmp) return NULL; | |
| memset(tmp, 0, sizeof(GF_Mutex)); | |
| #ifdef WIN32 | |
| tmp->hMutex = CreateMutex(NULL, FALSE, NULL); | |
| if (!tmp->hMutex) { | |
| #else | |
| pthread_mutexattr_init(&attr); | |
| if ( pthread_mutex_init(&tmp->hMutex, &attr) != 0 ) { | |
| #endif | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] Couldn't create mutex %s\n", strlen(name) ? name : "")); | |
| gf_free(tmp); | |
| return NULL; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| if (name) { | |
| if (stricmp(name, "Logs")) { | |
| tmp->log_name = gf_strdup(name); | |
| } | |
| } else { | |
| char szN[20]; | |
| sprintf(szN, "%p", (void*)tmp); | |
| tmp->log_name = gf_strdup(szN); | |
| } | |
| #endif | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| void gf_mx_del(GF_Mutex *mx) | |
| { | |
| #ifndef WIN32 | |
| int err; | |
| #endif | |
| if (!mx) return; | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->Holder && (gf_th_id() != mx->Holder) && mx->log_name) { | |
| char szName1[100], szName2[100]; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MUTEX, ("[Mutex %s] Destroying mutex from thread %s but hold by thread %s\n", mx->log_name, log_th_name(gf_th_id(), szName1), log_th_name(mx->Holder, szName2) )); | |
| } | |
| #endif | |
| #ifdef WIN32 | |
| if (!CloseHandle(mx->hMutex)) { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex %s] CloseHandle when deleting mutex failed with error code %d\n", mx->log_name, err)); | |
| } | |
| #endif | |
| } | |
| #else | |
| int max_retries = 10; | |
| retry: | |
| err = pthread_mutex_destroy(&mx->hMutex); | |
| if (err == EBUSY && max_retries > 0) { | |
| gf_sleep(10); | |
| max_retries--; | |
| goto retry; | |
| } | |
| if (err) { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex %s] pthread_mutex_destroy failed with error code %d\n", mx->log_name, err)); | |
| } | |
| #endif | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| gf_free(mx->log_name); | |
| mx->log_name = NULL; | |
| } | |
| #endif | |
| gf_free(mx); | |
| } | |
| void gf_mx_toggle_log(GF_Mutex *mx, Bool nolog) | |
| { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx) | |
| mx->nolog = nolog; | |
| #endif | |
| } | |
| GF_EXPORT | |
| void gf_mx_v(GF_Mutex *mx) | |
| { | |
| u32 caller; | |
| if (!mx) return; | |
| caller = gf_th_id(); | |
| /*only if we own*/ | |
| if (caller != mx->Holder) { | |
| gf_fatal_assert(0); | |
| return; | |
| } | |
| gf_fatal_assert(mx->HolderCount > 0); | |
| mx->HolderCount -= 1; | |
| if (mx->HolderCount == 0) { | |
| mx->Holder = 0; | |
| gf_fatal_assert(mx->HolderCount == 0); | |
| #ifdef WIN32 | |
| { | |
| BOOL ret = ReleaseMutex(mx->hMutex); | |
| if (!ret) { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name && !mx->nolog) { | |
| char szName[100]; | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] Couldn't release mutex (thread %s, error %d)\n", log_th_name(mx->Holder, szName), err)); | |
| } | |
| #endif | |
| return; | |
| } | |
| } | |
| #else | |
| if (pthread_mutex_unlock(&mx->hMutex)) { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name && !mx->nolog) { | |
| char szName[100]; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] Couldn't release mutex (thread %s)\n", log_th_name(mx->Holder, szName))); | |
| } | |
| #endif | |
| return; | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name && !mx->nolog) { | |
| char szName[100]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] Released by thread %s (hcount %u)\n", mx->log_name, log_th_name(mx->Holder, szName), mx->HolderCount )); | |
| } | |
| #endif | |
| } | |
| } | |
| GF_EXPORT | |
| u32 gf_mx_p(GF_Mutex *mx) | |
| { | |
| #ifndef WIN32 | |
| int retCode; | |
| #endif | |
| u32 caller; | |
| #ifndef GPAC_DISABLE_LOG | |
| const char *mx_holder_name; | |
| #endif | |
| if (!mx) return 1; | |
| caller = gf_th_id(); | |
| if (caller == mx->Holder) { | |
| gf_fatal_assert(mx->HolderCount > 0); | |
| gf_fatal_assert(mx->Holder); | |
| mx->HolderCount += 1; | |
| return 1; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->Holder && mx->log_name && !mx->nolog) { | |
| char szName[100]; | |
| mx_holder_name = mx->Holder ? log_th_name(mx->Holder, szName) : "none"; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] Thread %s waiting a release from thread %s (hcount %d)\n", mx->log_name, log_th_name(caller, szName), mx_holder_name, mx->HolderCount )); | |
| } | |
| #endif | |
| #ifdef WIN32 | |
| switch (WaitForSingleObject(mx->hMutex, INFINITE)) { | |
| case WAIT_ABANDONED: | |
| case WAIT_TIMEOUT: | |
| return 0; | |
| default: | |
| break; | |
| } | |
| #else | |
| retCode = pthread_mutex_lock(&mx->hMutex); | |
| if (retCode != 0) { | |
| #ifndef GPAC_DISABLE_LOG | |
| char name[100] = {0}; | |
| if (mx->log_name) | |
| gf_strcpy(name, mx->log_name); | |
| else | |
| sprintf(name, "id=%u", gf_th_id()); | |
| if (retCode == EINVAL) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex %p:%s] Not properly initialized.\n", mx, name)); | |
| if (retCode == EDEADLK) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex %p:%s] Deadlock detected.\n", mx, name)); | |
| #endif /* GPAC_DISABLE_LOG */ | |
| gf_assert(0); | |
| return 0; | |
| } | |
| #endif /* NOT WIN32 */ | |
| gf_assert(mx->Holder == 0); | |
| gf_assert(mx->HolderCount == 0); | |
| mx->HolderCount = 1; | |
| mx->Holder = caller; | |
| gf_fatal_assert(mx->Holder); | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name && !mx->nolog) { | |
| char szName[100]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] Grabbed by thread %s\n", mx->log_name, log_th_name(mx->Holder, szName) )); | |
| } | |
| #endif | |
| return 1; | |
| } | |
| GF_EXPORT | |
| s32 gf_mx_get_num_locks(GF_Mutex *mx) | |
| { | |
| u32 caller; | |
| if (!mx) return 0; | |
| caller = gf_th_id(); | |
| if (caller == mx->Holder) { | |
| return mx->HolderCount; | |
| } | |
| return -1; | |
| } | |
| GF_EXPORT | |
| Bool gf_mx_try_lock(GF_Mutex *mx) | |
| { | |
| u32 caller; | |
| if (!mx) { | |
| return GF_TRUE; | |
| } | |
| caller = gf_th_id(); | |
| if (caller == mx->Holder) { | |
| gf_fatal_assert(mx->HolderCount > 0); | |
| gf_fatal_assert(mx->Holder); | |
| mx->HolderCount += 1; | |
| return GF_TRUE; | |
| } | |
| //DO NOT LOG ANYTHING BEFORE RELEASING, this will break logging with mutex@debug in JS where we use try_lock | |
| #ifdef WIN32 | |
| /*is the object signaled?*/ | |
| switch (WaitForSingleObject(mx->hMutex, 0)) { | |
| case WAIT_OBJECT_0: | |
| break; | |
| case WAIT_ABANDONED: | |
| case WAIT_TIMEOUT: | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| char szName1[100], szName2[100]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] At %d Couldn't be locked by thread %s (grabbed by thread %s)\n", mx->log_name, gf_sys_clock(), log_th_name(caller, szName1), log_th_name(mx->Holder, szName2) )); | |
| } | |
| #endif | |
| return GF_FALSE; | |
| case WAIT_FAILED: | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex %s] At %d WaitForSingleObject failed\n", mx->log_name, gf_sys_clock())); | |
| } | |
| #endif | |
| return GF_FALSE; | |
| default: | |
| gf_assert(0); | |
| return GF_FALSE; | |
| } | |
| #else | |
| if (pthread_mutex_trylock(&mx->hMutex) != 0 ) { | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| char szName1[100], szName2[100]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] Couldn't release it for thread %s (grabbed by thread %s)\n", mx->log_name, log_th_name(caller, szName1), log_th_name(mx->Holder, szName2) )); | |
| } | |
| #endif | |
| return GF_FALSE; | |
| } | |
| #endif | |
| gf_assert(mx->Holder == 0); | |
| gf_assert(mx->HolderCount == 0); | |
| mx->HolderCount = 1; | |
| mx->Holder = caller; | |
| gf_assert(mx->Holder); | |
| gf_assert(mx->HolderCount); | |
| #ifndef GPAC_DISABLE_LOG | |
| if (mx->log_name) { | |
| char szName[100]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MUTEX, ("[Mutex %s] Grabbed by thread %s\n", mx->log_name, log_th_name(mx->Holder, szName) )); | |
| } | |
| #endif | |
| return GF_TRUE; | |
| } | |
| /********************************************************************* | |
| OS-Specific Semaphore Object | |
| **********************************************************************/ | |
| struct __tag_semaphore | |
| { | |
| #ifdef WIN32 | |
| HANDLE hSemaphore; | |
| #else | |
| sem_t *hSemaphore; | |
| sem_t SemaData; | |
| #if defined(__DARWIN__) || defined(__APPLE__) | |
| char *SemName; | |
| #endif | |
| #endif | |
| }; | |
| GF_EXPORT | |
| GF_Semaphore *gf_sema_new(u32 MaxCount, u32 InitCount) | |
| { | |
| if (gf_opts_get_bool("core", "no-mx")) return NULL; | |
| GF_Semaphore *tmp = (GF_Semaphore*)gf_malloc(sizeof(GF_Semaphore)); | |
| if (!tmp) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Couldn't allocate semaphore\n")); | |
| return NULL; | |
| } | |
| #if defined(WIN32) | |
| MaxCount = MIN(MaxCount, GF_INT_MAX); | |
| tmp->hSemaphore = CreateSemaphore(NULL, InitCount, MIN(MaxCount, GF_INT_MAX), NULL); | |
| if (!tmp->hSemaphore) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Couldn't create semaphore\n")); | |
| gf_free(tmp); | |
| return NULL; | |
| } | |
| #elif defined(__DARWIN__) || defined(__APPLE__) | |
| /* sem_init isn't supported on Mac OS X 10.3 & 10.4; it returns ENOSYS | |
| To get around this, a NAMED semaphore needs to be used | |
| sem_t *sem_open(const char *name, int oflag, ...); | |
| http://users.evitech.fi/~hannuvl/ke04/reaaliaika_ohj/memmap_named_semaphores.c | |
| */ | |
| { | |
| char semaName[40]; | |
| u64 add = (u64) tmp ^ gf_net_get_utc() ^ gf_rand(); | |
| sprintf(semaName,"GPAC_SEM"LLU, add); | |
| tmp->SemName = gf_strdup(semaName); | |
| } | |
| sem_unlink(tmp->SemName); | |
| tmp->hSemaphore = sem_open(tmp->SemName, O_CREAT, S_IRUSR|S_IWUSR, InitCount); | |
| if (tmp->hSemaphore==SEM_FAILED) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Couldn't init semaphore: error %d\n", errno)); | |
| gf_free(tmp); | |
| return NULL; | |
| } | |
| #else | |
| if (sem_init(&tmp->SemaData, 0, InitCount) < 0 ) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("Couldn't init semaphore: error %d\n", errno)); | |
| gf_free(tmp); | |
| return NULL; | |
| } | |
| tmp->hSemaphore = &tmp->SemaData; | |
| #endif | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| void gf_sema_del(GF_Semaphore *sm) | |
| { | |
| if (!sm) return; | |
| #if defined(WIN32) | |
| if (!CloseHandle(sm->hSemaphore)) { | |
| DWORD err = GetLastError(); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Mutex] CloseHandle when deleting semaphore failed with error code %d\n", err)); | |
| } | |
| #elif defined(__DARWIN__) || defined(__APPLE__) | |
| sem_close(sm->hSemaphore); | |
| sem_unlink(sm->SemName); | |
| gf_free(sm->SemName); | |
| #else | |
| sem_destroy(sm->hSemaphore); | |
| #endif | |
| gf_free(sm); | |
| } | |
| GF_EXPORT | |
| Bool gf_sema_notify(GF_Semaphore *sm, u32 NbRelease) | |
| { | |
| #ifndef WIN32 | |
| sem_t *hSem; | |
| #else | |
| u32 prevCount=0; | |
| #endif | |
| if (!sm) return GF_FALSE; | |
| #if defined(WIN32) | |
| ReleaseSemaphore(sm->hSemaphore, NbRelease, (LPLONG) &prevCount); | |
| #else | |
| hSem = sm->hSemaphore; | |
| while (NbRelease) { | |
| if (sem_post(hSem) < 0) return GF_FALSE; | |
| NbRelease -= 1; | |
| } | |
| #endif | |
| return GF_TRUE; | |
| } | |
| GF_EXPORT | |
| Bool gf_sema_wait(GF_Semaphore *sm) | |
| { | |
| if (!sm) return GF_FALSE; | |
| #ifdef WIN32 | |
| switch (WaitForSingleObject(sm->hSemaphore, INFINITE) ) { | |
| case WAIT_FAILED: | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Semaphore] failed to wait: %d\n", GetLastError() )); | |
| return GF_FALSE; | |
| case WAIT_ABANDONED: | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Semaphore] failed to wait: owner thread exit\n")); | |
| return GF_FALSE; | |
| default: | |
| break; | |
| } | |
| #else | |
| retry: | |
| if (sem_wait(sm->hSemaphore) < 0) { | |
| if (errno == EINTR) goto retry; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MUTEX, ("[Semaphore] failed to wait for semaphore: %d\n", errno)); | |
| return GF_FALSE; | |
| } | |
| #endif | |
| return GF_TRUE; | |
| } | |
| GF_EXPORT | |
| Bool gf_sema_wait_for(GF_Semaphore *sm, u32 TimeOut) | |
| { | |
| #ifdef WIN32 | |
| if (!sm) return GF_FALSE; | |
| if (WaitForSingleObject(sm->hSemaphore, TimeOut) == WAIT_TIMEOUT) return GF_FALSE; | |
| return GF_TRUE; | |
| #else | |
| sem_t *hSem; | |
| if (!sm) return GF_FALSE; | |
| hSem = sm->hSemaphore; | |
| if (!TimeOut) { | |
| if (!sem_trywait(hSem)) return GF_TRUE; | |
| return GF_FALSE; | |
| } | |
| #if defined(__DARWIN__) || defined(__APPLE__) || defined(GPAC_CONFIG_IOS) || defined(GPAC_CONFIG_EMSCRIPTEN) | |
| TimeOut += gf_sys_clock(); | |
| do { | |
| if (!sem_trywait(hSem)) return GF_TRUE; | |
| //OSX/ios don't support sem_timedwait, so we sleep until the sem is notified or the timeout is done | |
| //don't be too greedy, use 5ms sleep | |
| //another approach would be to spawn a thread, use sem_wait and send an interrupt on the sema after the timeout ... | |
| gf_sleep(1); | |
| } while (gf_sys_clock() < TimeOut); | |
| return GF_FALSE; | |
| #else | |
| struct timespec tv; | |
| u32 secs; | |
| if (clock_gettime(CLOCK_REALTIME, &tv) == -1) return GF_FALSE; | |
| secs = TimeOut/1000; | |
| tv.tv_sec += secs; | |
| tv.tv_nsec += (u64) ((TimeOut-secs*1000)) * 1000000; | |
| if (tv.tv_nsec>1000000000) { | |
| tv.tv_nsec -= 1000000000; | |
| tv.tv_sec += 1; | |
| } | |
| if (!sem_timedwait(hSem, &tv)) return GF_TRUE; | |
| return GF_FALSE; | |
| #endif | |
| #endif | |
| } | |
| #endif //GPAC_DISABLE_THREADS |