/** * threads.c: set of generic threading related routines * * See Copyright for the status of this software. * * Gary Pennington * daniel@veillard.com */ #define IN_LIBXML #include "libxml.h" #include #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_PTHREAD_H #include #endif #ifdef HAVE_WIN32_THREADS #include #ifndef HAVE_COMPILER_TLS #include #endif #endif #ifdef HAVE_BEOS_THREADS #include #include #endif #if defined(SOLARIS) #include #endif /* #define DEBUG_THREADS */ /* * TODO: this module still uses malloc/free and not xmlMalloc/xmlFree * to avoid some crazyness since xmlMalloc/xmlFree may actually * be hosted on allocated blocks needing them for the allocation ... */ /* * xmlMutex are a simple mutual exception locks */ struct _xmlMutex { #ifdef HAVE_PTHREAD_H pthread_mutex_t lock; #elif defined HAVE_WIN32_THREADS HANDLE mutex; #elif defined HAVE_BEOS_THREADS sem_id sem; thread_id tid; #else int empty; #endif }; /* * xmlRMutex are reentrant mutual exception locks */ struct _xmlRMutex { #ifdef HAVE_PTHREAD_H pthread_mutex_t lock; unsigned int held; unsigned int waiters; pthread_t tid; pthread_cond_t cv; #elif defined HAVE_WIN32_THREADS CRITICAL_SECTION cs; unsigned int count; #elif defined HAVE_BEOS_THREADS xmlMutexPtr lock; thread_id tid; int32 count; #else int empty; #endif }; /* * This module still has some internal static data. * - xmlLibraryLock a global lock * - globalkey used for per-thread data */ #ifdef HAVE_PTHREAD_H static pthread_key_t globalkey; static pthread_t mainthread; static pthread_once_t once_control = PTHREAD_ONCE_INIT; #elif defined HAVE_WIN32_THREADS #if defined(HAVE_COMPILER_TLS) static __declspec(thread) xmlGlobalState tlstate; static __declspec(thread) int tlstate_inited = 0; #else /* HAVE_COMPILER_TLS */ static DWORD globalkey = TLS_OUT_OF_INDEXES; #endif /* HAVE_COMPILER_TLS */ static DWORD mainthread; static int run_once_init = 1; /* endif HAVE_WIN32_THREADS */ #elif defined HAVE_BEOS_THREADS int32 globalkey = 0; thread_id mainthread = 0; int32 run_once_init = 0; #endif static xmlRMutexPtr xmlLibraryLock = NULL; #ifdef LIBXML_THREAD_ENABLED static void xmlOnceInit(void); #endif /** * xmlNewMutex: * * xmlNewMutex() is used to allocate a libxml2 token struct for use in * synchronizing access to data. * * Returns a new simple mutex pointer or NULL in case of error */ xmlMutexPtr xmlNewMutex(void) { xmlMutexPtr tok; if ((tok = malloc(sizeof(xmlMutex))) == NULL) return (NULL); #ifdef HAVE_PTHREAD_H pthread_mutex_init(&tok->lock, NULL); #elif defined HAVE_WIN32_THREADS tok->mutex = CreateMutex(NULL, FALSE, NULL); #elif defined HAVE_BEOS_THREADS if ((tok->sem = create_sem(1, "xmlMutex")) < B_OK) { free(tok); return NULL; } tok->tid = -1; #endif return (tok); } /** * xmlFreeMutex: * @tok: the simple mutex * * xmlFreeMutex() is used to reclaim resources associated with a libxml2 token * struct. */ void xmlFreeMutex(xmlMutexPtr tok) { if (tok == NULL) return; #ifdef HAVE_PTHREAD_H pthread_mutex_destroy(&tok->lock); #elif defined HAVE_WIN32_THREADS CloseHandle(tok->mutex); #elif defined HAVE_BEOS_THREADS delete_sem(tok->sem); #endif free(tok); } /** * xmlMutexLock: * @tok: the simple mutex * * xmlMutexLock() is used to lock a libxml2 token. */ void xmlMutexLock(xmlMutexPtr tok) { if (tok == NULL) return; #ifdef HAVE_PTHREAD_H pthread_mutex_lock(&tok->lock); #elif defined HAVE_WIN32_THREADS WaitForSingleObject(tok->mutex, INFINITE); #elif defined HAVE_BEOS_THREADS if (acquire_sem(tok->sem) != B_NO_ERROR) { #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlMutexLock():BeOS:Couldn't aquire semaphore\n"); exit(); #endif } tok->tid = find_thread(NULL); #endif } /** * xmlMutexUnlock: * @tok: the simple mutex * * xmlMutexUnlock() is used to unlock a libxml2 token. */ void xmlMutexUnlock(xmlMutexPtr tok) { if (tok == NULL) return; #ifdef HAVE_PTHREAD_H pthread_mutex_unlock(&tok->lock); #elif defined HAVE_WIN32_THREADS ReleaseMutex(tok->mutex); #elif defined HAVE_BEOS_THREADS if (tok->tid == find_thread(NULL)) { tok->tid = -1; release_sem(tok->sem); } #endif } /** * xmlNewRMutex: * * xmlRNewMutex() is used to allocate a reentrant mutex for use in * synchronizing access to data. token_r is a re-entrant lock and thus useful * for synchronizing access to data structures that may be manipulated in a * recursive fashion. * * Returns the new reentrant mutex pointer or NULL in case of error */ xmlRMutexPtr xmlNewRMutex(void) { xmlRMutexPtr tok; if ((tok = malloc(sizeof(xmlRMutex))) == NULL) return (NULL); #ifdef HAVE_PTHREAD_H pthread_mutex_init(&tok->lock, NULL); tok->held = 0; tok->waiters = 0; pthread_cond_init(&tok->cv, NULL); #elif defined HAVE_WIN32_THREADS InitializeCriticalSection(&tok->cs); tok->count = 0; #elif defined HAVE_BEOS_THREADS if ((tok->lock = xmlNewMutex()) == NULL) { free(tok); return NULL; } tok->count = 0; #endif return (tok); } /** * xmlFreeRMutex: * @tok: the reentrant mutex * * xmlRFreeMutex() is used to reclaim resources associated with a * reentrant mutex. */ void xmlFreeRMutex(xmlRMutexPtr tok ATTRIBUTE_UNUSED) { #ifdef HAVE_PTHREAD_H pthread_mutex_destroy(&tok->lock); #elif defined HAVE_WIN32_THREADS DeleteCriticalSection(&tok->cs); #elif defined HAVE_BEOS_THREADS xmlFreeMutex(tok->lock); #endif free(tok); } /** * xmlRMutexLock: * @tok: the reentrant mutex * * xmlRMutexLock() is used to lock a libxml2 token_r. */ void xmlRMutexLock(xmlRMutexPtr tok) { if (tok == NULL) return; #ifdef HAVE_PTHREAD_H pthread_mutex_lock(&tok->lock); if (tok->held) { if (pthread_equal(tok->tid, pthread_self())) { tok->held++; pthread_mutex_unlock(&tok->lock); return; } else { tok->waiters++; while (tok->held) pthread_cond_wait(&tok->cv, &tok->lock); tok->waiters--; } } tok->tid = pthread_self(); tok->held = 1; pthread_mutex_unlock(&tok->lock); #elif defined HAVE_WIN32_THREADS EnterCriticalSection(&tok->cs); ++tok->count; #elif defined HAVE_BEOS_THREADS if (tok->lock->tid == find_thread(NULL)) { tok->count++; return; } else { xmlMutexLock(tok->lock); tok->count = 1; } #endif } /** * xmlRMutexUnlock: * @tok: the reentrant mutex * * xmlRMutexUnlock() is used to unlock a libxml2 token_r. */ void xmlRMutexUnlock(xmlRMutexPtr tok ATTRIBUTE_UNUSED) { if (tok == NULL) return; #ifdef HAVE_PTHREAD_H pthread_mutex_lock(&tok->lock); tok->held--; if (tok->held == 0) { if (tok->waiters) pthread_cond_signal(&tok->cv); tok->tid = 0; } pthread_mutex_unlock(&tok->lock); #elif defined HAVE_WIN32_THREADS if (!--tok->count) LeaveCriticalSection(&tok->cs); #elif defined HAVE_BEOS_THREADS if (tok->lock->tid == find_thread(NULL)) { tok->count--; if (tok->count == 0) { xmlMutexUnlock(tok->lock); } return; } #endif } /************************************************************************ * * * Per thread global state handling * * * ************************************************************************/ #ifdef LIBXML_THREAD_ENABLED #ifdef xmlLastError #undef xmlLastError #endif /** * xmlFreeGlobalState: * @state: a thread global state * * xmlFreeGlobalState() is called when a thread terminates with a non-NULL * global state. It is is used here to reclaim memory resources. */ static void xmlFreeGlobalState(void *state) { xmlGlobalState *gs = (xmlGlobalState *) state; /* free any memory allocated in the thread's xmlLastError */ xmlResetError(&(gs->xmlLastError)); free(state); } /** * xmlNewGlobalState: * * xmlNewGlobalState() allocates a global state. This structure is used to * hold all data for use by a thread when supporting backwards compatibility * of libxml2 to pre-thread-safe behaviour. * * Returns the newly allocated xmlGlobalStatePtr or NULL in case of error */ static xmlGlobalStatePtr xmlNewGlobalState(void) { xmlGlobalState *gs; gs = malloc(sizeof(xmlGlobalState)); if (gs == NULL) return(NULL); memset(gs, 0, sizeof(xmlGlobalState)); xmlInitializeGlobalState(gs); return (gs); } #endif /* LIBXML_THREAD_ENABLED */ #ifdef HAVE_WIN32_THREADS #if !defined(HAVE_COMPILER_TLS) #if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL) typedef struct _xmlGlobalStateCleanupHelperParams { HANDLE thread; void *memory; } xmlGlobalStateCleanupHelperParams; static void xmlGlobalStateCleanupHelper (void *p) { xmlGlobalStateCleanupHelperParams *params = (xmlGlobalStateCleanupHelperParams *) p; WaitForSingleObject(params->thread, INFINITE); CloseHandle(params->thread); xmlFreeGlobalState(params->memory); free(params); _endthread(); } #else /* LIBXML_STATIC && !LIBXML_STATIC_FOR_DLL */ typedef struct _xmlGlobalStateCleanupHelperParams { void *memory; struct _xmlGlobalStateCleanupHelperParams * prev; struct _xmlGlobalStateCleanupHelperParams * next; } xmlGlobalStateCleanupHelperParams; static xmlGlobalStateCleanupHelperParams * cleanup_helpers_head = NULL; static CRITICAL_SECTION cleanup_helpers_cs; #endif /* LIBXMLSTATIC && !LIBXML_STATIC_FOR_DLL */ #endif /* HAVE_COMPILER_TLS */ #endif /* HAVE_WIN32_THREADS */ #if defined HAVE_BEOS_THREADS /** * xmlGlobalStateCleanup: * @data: unused parameter * * Used for Beos only */ void xmlGlobalStateCleanup(void *data) { void *globalval = tls_get(globalkey); if (globalval != NULL) xmlFreeGlobalState(globalval); } #endif /** * xmlGetGlobalState: * * xmlGetGlobalState() is called to retrieve the global state for a thread. * * Returns the thread global state or NULL in case of error */ xmlGlobalStatePtr xmlGetGlobalState(void) { #ifdef HAVE_PTHREAD_H xmlGlobalState *globalval; pthread_once(&once_control, xmlOnceInit); if ((globalval = (xmlGlobalState *) pthread_getspecific(globalkey)) == NULL) { xmlGlobalState *tsd = xmlNewGlobalState(); pthread_setspecific(globalkey, tsd); return (tsd); } return (globalval); #elif defined HAVE_WIN32_THREADS #if defined(HAVE_COMPILER_TLS) if (!tlstate_inited) { tlstate_inited = 1; xmlInitializeGlobalState(&tlstate); } return &tlstate; #else /* HAVE_COMPILER_TLS */ xmlGlobalState *globalval; xmlGlobalStateCleanupHelperParams * p; if (run_once_init) { run_once_init = 0; xmlOnceInit(); } #if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL) globalval = (xmlGlobalState *)TlsGetValue(globalkey); #else p = (xmlGlobalStateCleanupHelperParams*)TlsGetValue(globalkey); globalval = (xmlGlobalState *)(p ? p->memory : NULL); #endif if (globalval == NULL) { xmlGlobalState *tsd = xmlNewGlobalState(); p = (xmlGlobalStateCleanupHelperParams *) malloc(sizeof(xmlGlobalStateCleanupHelperParams)); p->memory = tsd; #if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL) DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &p->thread, 0, TRUE, DUPLICATE_SAME_ACCESS); TlsSetValue(globalkey, tsd); _beginthread(xmlGlobalStateCleanupHelper, 0, p); #else EnterCriticalSection(&cleanup_helpers_cs); if (cleanup_helpers_head != NULL) { cleanup_helpers_head->prev = p; } p->next = cleanup_helpers_head; p->prev = NULL; cleanup_helpers_head = p; TlsSetValue(globalkey, p); LeaveCriticalSection(&cleanup_helpers_cs); #endif return (tsd); } return (globalval); #endif /* HAVE_COMPILER_TLS */ #elif defined HAVE_BEOS_THREADS xmlGlobalState *globalval; xmlOnceInit(); if ((globalval = (xmlGlobalState *) tls_get(globalkey)) == NULL) { xmlGlobalState *tsd = xmlNewGlobalState(); tls_set(globalkey, tsd); on_exit_thread(xmlGlobalStateCleanup, NULL); return (tsd); } return (globalval); #else return(NULL); #endif } /************************************************************************ * * * Library wide thread interfaces * * * ************************************************************************/ /** * xmlGetThreadId: * * xmlGetThreadId() find the current thread ID number * * Returns the current thread ID number */ int xmlGetThreadId(void) { #ifdef HAVE_PTHREAD_H return((int) pthread_self()); #elif defined HAVE_WIN32_THREADS return GetCurrentThreadId(); #elif defined HAVE_BEOS_THREADS return find_thread(NULL); #else return((int) 0); #endif } /** * xmlIsMainThread: * * xmlIsMainThread() check whether the current thread is the main thread. * * Returns 1 if the current thread is the main thread, 0 otherwise */ int xmlIsMainThread(void) { #ifdef HAVE_PTHREAD_H pthread_once(&once_control, xmlOnceInit); #elif defined HAVE_WIN32_THREADS if (run_once_init) { run_once_init = 0; xmlOnceInit (); } #elif defined HAVE_BEOS_THREADS xmlOnceInit(); #endif #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlIsMainThread()\n"); #endif #ifdef HAVE_PTHREAD_H return(mainthread == pthread_self()); #elif defined HAVE_WIN32_THREADS return(mainthread == GetCurrentThreadId ()); #elif defined HAVE_BEOS_THREADS return(mainthread == find_thread(NULL)); #else return(1); #endif } /** * xmlLockLibrary: * * xmlLockLibrary() is used to take out a re-entrant lock on the libxml2 * library. */ void xmlLockLibrary(void) { #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlLockLibrary()\n"); #endif xmlRMutexLock(xmlLibraryLock); } /** * xmlUnlockLibrary: * * xmlUnlockLibrary() is used to release a re-entrant lock on the libxml2 * library. */ void xmlUnlockLibrary(void) { #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlUnlockLibrary()\n"); #endif xmlRMutexUnlock(xmlLibraryLock); } /** * xmlInitThreads: * * xmlInitThreads() is used to to initialize all the thread related * data of the libxml2 library. */ void xmlInitThreads(void) { #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlInitThreads()\n"); #endif #if defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL)) InitializeCriticalSection(&cleanup_helpers_cs); #endif } /** * xmlCleanupThreads: * * xmlCleanupThreads() is used to to cleanup all the thread related * data of the libxml2 library once processing has ended. */ void xmlCleanupThreads(void) { #ifdef DEBUG_THREADS xmlGenericError(xmlGenericErrorContext, "xmlCleanupThreads()\n"); #endif #if defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL)) if (globalkey != TLS_OUT_OF_INDEXES) { xmlGlobalStateCleanupHelperParams * p; EnterCriticalSection(&cleanup_helpers_cs); p = cleanup_helpers_head; while (p != NULL) { xmlGlobalStateCleanupHelperParams * temp = p; p = p->next; xmlFreeGlobalState(temp->memory); free(temp); } cleanup_helpers_head = 0; LeaveCriticalSection(&cleanup_helpers_cs); TlsFree(globalkey); globalkey = TLS_OUT_OF_INDEXES; } DeleteCriticalSection(&cleanup_helpers_cs); #endif } #ifdef LIBXML_THREAD_ENABLED /** * xmlOnceInit * * xmlOnceInit() is used to initialize the value of mainthread for use * in other routines. This function should only be called using * pthread_once() in association with the once_control variable to ensure * that the function is only called once. See man pthread_once for more * details. */ static void xmlOnceInit(void) { #ifdef HAVE_PTHREAD_H (void) pthread_key_create(&globalkey, xmlFreeGlobalState); mainthread = pthread_self(); #endif #if defined(HAVE_WIN32_THREADS) #if !defined(HAVE_COMPILER_TLS) globalkey = TlsAlloc(); #endif mainthread = GetCurrentThreadId(); #endif #ifdef HAVE_BEOS_THREADS if (atomic_add(&run_once_init, 1) == 0) { globalkey = tls_allocate(); tls_set(globalkey, NULL); mainthread = find_thread(NULL); } else atomic_add(&run_once_init, -1); #endif } #endif /** * DllMain: * @hinstDLL: handle to DLL instance * @fdwReason: Reason code for entry * @lpvReserved: generic pointer (depends upon reason code) * * Entry point for Windows library. It is being used to free thread-specific * storage. * * Returns TRUE always */ #if defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL)) #if defined(LIBXML_STATIC_FOR_DLL) BOOL WINAPI xmlDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) #else BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) #endif { switch(fdwReason) { case DLL_THREAD_DETACH: if (globalkey != TLS_OUT_OF_INDEXES) { xmlGlobalState *globalval = NULL; xmlGlobalStateCleanupHelperParams * p = (xmlGlobalStateCleanupHelperParams*)TlsGetValue(globalkey); globalval = (xmlGlobalState *)(p ? p->memory : NULL); if (globalval) { xmlFreeGlobalState(globalval); TlsSetValue(globalkey,NULL); } if (p) { EnterCriticalSection(&cleanup_helpers_cs); if (p == cleanup_helpers_head) cleanup_helpers_head = p->next; else p->prev->next = p->next; if (p->next != NULL) p->next->prev = p->prev; LeaveCriticalSection(&cleanup_helpers_cs); free(p); } } break; } return TRUE; } #endif