| /* | |
| Copyright (c) 2011-2016 mingw-w64 project | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
| DEALINGS IN THE SOFTWARE. | |
| */ | |
| #include <windows.h> | |
| #include <strsafe.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <malloc.h> | |
| #include <signal.h> | |
| #include "pthread.h" | |
| #include "thread.h" | |
| #include "misc.h" | |
| #include "winpthread_internal.h" | |
| static _pthread_v *__pthread_self_lite (void); | |
| void (**_pthread_key_dest)(void *) = NULL; | |
| static volatile long _pthread_cancelling; | |
| static int _pthread_concur; | |
| /* FIXME Will default to zero as needed */ | |
| static pthread_once_t _pthread_tls_once; | |
| static DWORD _pthread_tls = 0xffffffff; | |
| static pthread_rwlock_t _pthread_key_lock = PTHREAD_RWLOCK_INITIALIZER; | |
| static unsigned long _pthread_key_max=0L; | |
| static unsigned long _pthread_key_sch=0L; | |
| static _pthread_v *pthr_root = NULL, *pthr_last = NULL; | |
| static pthread_mutex_t mtx_pthr_locked = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; | |
| static __pthread_idlist *idList = NULL; | |
| static size_t idListCnt = 0; | |
| static size_t idListMax = 0; | |
| static pthread_t idListNextId = 0; | |
| #if !defined(_MSC_VER) | |
| #define USE_VEH_FOR_MSC_SETTHREADNAME | |
| #endif | |
| #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) | |
| /* forbidden RemoveVectoredExceptionHandler/AddVectoredExceptionHandler APIs */ | |
| #undef USE_VEH_FOR_MSC_SETTHREADNAME | |
| #endif | |
| #if defined(USE_VEH_FOR_MSC_SETTHREADNAME) | |
| static void *SetThreadName_VEH_handle = NULL; | |
| static LONG __stdcall | |
| SetThreadName_VEH (PEXCEPTION_POINTERS ExceptionInfo) | |
| { | |
| if (ExceptionInfo->ExceptionRecord != NULL && | |
| ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SET_THREAD_NAME) | |
| return EXCEPTION_CONTINUE_EXECUTION; | |
| return EXCEPTION_CONTINUE_SEARCH; | |
| } | |
| #endif | |
| typedef struct _THREADNAME_INFO | |
| { | |
| DWORD dwType; /* must be 0x1000 */ | |
| LPCSTR szName; /* pointer to name (in user addr space) */ | |
| DWORD dwThreadID; /* thread ID (-1=caller thread) */ | |
| DWORD dwFlags; /* reserved for future use, must be zero */ | |
| } THREADNAME_INFO; | |
| static void | |
| SetThreadName (DWORD dwThreadID, LPCSTR szThreadName) | |
| { | |
| THREADNAME_INFO info; | |
| DWORD infosize; | |
| info.dwType = 0x1000; | |
| info.szName = szThreadName; | |
| info.dwThreadID = dwThreadID; | |
| info.dwFlags = 0; | |
| infosize = sizeof (info) / sizeof (ULONG_PTR); | |
| #if defined(_MSC_VER) && !defined (USE_VEH_FOR_MSC_SETTHREADNAME) | |
| __try | |
| { | |
| RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize, (ULONG_PTR *)&info); | |
| } | |
| __except (EXCEPTION_EXECUTE_HANDLER) | |
| { | |
| } | |
| #else | |
| /* Without a debugger we *must* have an exception handler, | |
| * otherwise raising an exception will crash the process. | |
| */ | |
| #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) | |
| if ((!IsDebuggerPresent ()) && (SetThreadName_VEH_handle == NULL)) | |
| #else | |
| if (!IsDebuggerPresent ()) | |
| #endif | |
| return; | |
| RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize, (ULONG_PTR *) &info); | |
| #endif | |
| } | |
| /* Search the list idList for an element with identifier ID. If | |
| found, its associated _pthread_v pointer is returned, otherwise | |
| NULL. | |
| NOTE: This method is not locked. */ | |
| static struct _pthread_v * | |
| __pthread_get_pointer (pthread_t id) | |
| { | |
| size_t l, r, p; | |
| if (!idListCnt) | |
| return NULL; | |
| if (idListCnt == 1) | |
| return (idList[0].id == id ? idList[0].ptr : NULL); | |
| l = 0; r = idListCnt - 1; | |
| while (l <= r) | |
| { | |
| p = (l + r) >> 1; | |
| if (idList[p].id == id) | |
| return idList[p].ptr; | |
| else if (idList[p].id > id) | |
| { | |
| if (p == l) | |
| return NULL; | |
| r = p - 1; | |
| } | |
| else | |
| { | |
| l = p + 1; | |
| } | |
| } | |
| return NULL; | |
| } | |
| static void | |
| __pth_remove_use_for_key (pthread_key_t key) | |
| { | |
| int i; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| for (i = 0; i < idListCnt; i++) | |
| { | |
| if (idList[i].ptr != NULL | |
| && idList[i].ptr->keyval != NULL | |
| && key < idList[i].ptr->keymax) | |
| { | |
| idList[i].ptr->keyval[key] = NULL; | |
| idList[i].ptr->keyval_set[key] = 0; | |
| } | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| } | |
| /* Search the list idList for an element with identifier ID. If | |
| found, its associated _pthread_v pointer is returned, otherwise | |
| NULL. | |
| NOTE: This method uses lock mtx_pthr_locked. */ | |
| struct _pthread_v * | |
| __pth_gpointer_locked (pthread_t id) | |
| { | |
| struct _pthread_v *ret; | |
| if (!id) | |
| return NULL; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| ret = __pthread_get_pointer (id); | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return ret; | |
| } | |
| /* Registers in the list idList an element with _pthread_v pointer | |
| and creates and unique identifier ID. If successful created the | |
| ID of this element is returned, otherwise on failure zero ID gets | |
| returned. | |
| NOTE: This method is not locked. */ | |
| static pthread_t | |
| __pthread_register_pointer (struct _pthread_v *ptr) | |
| { | |
| __pthread_idlist *e; | |
| size_t i; | |
| if (!ptr) | |
| return 0; | |
| /* Check if a resize of list is necessary. */ | |
| if (idListCnt >= idListMax) | |
| { | |
| if (!idListCnt) | |
| { | |
| e = (__pthread_idlist *) malloc (sizeof (__pthread_idlist) * 16); | |
| if (!e) | |
| return 0; | |
| idListMax = 16; | |
| idList = e; | |
| } | |
| else | |
| { | |
| e = (__pthread_idlist *) realloc (idList, sizeof (__pthread_idlist) * (idListMax + 16)); | |
| if (!e) | |
| return 0; | |
| idListMax += 16; | |
| idList = e; | |
| } | |
| } | |
| do | |
| { | |
| ++idListNextId; | |
| /* If two MSB are set we reset to id 1. We need to check here bits | |
| to avoid gcc's no-overflow issue on increment. Additionally we | |
| need to handle different size of pthread_t on 32-bit/64-bit. */ | |
| if ((idListNextId & ( ((pthread_t) 1) << ((sizeof (pthread_t) * 8) - 2))) != 0) | |
| idListNextId = 1; | |
| } | |
| while (idListNextId == 0 || __pthread_get_pointer (idListNextId)); | |
| /* We assume insert at end of list. */ | |
| i = idListCnt; | |
| if (i != 0) | |
| { | |
| /* Find position we can actual insert sorted. */ | |
| while (i > 0 && idList[i - 1].id > idListNextId) | |
| --i; | |
| if (i != idListCnt) | |
| memmove (&idList[i + 1], &idList[i], sizeof (__pthread_idlist) * (idListCnt - i)); | |
| } | |
| idList[i].id = idListNextId; | |
| idList[i].ptr = ptr; | |
| ++idListCnt; | |
| return idListNextId; | |
| } | |
| /* Deregisters in the list idList an element with identifier ID and | |
| returns its _pthread_v pointer on success. Otherwise NULL is returned. | |
| NOTE: This method is not locked. */ | |
| static struct _pthread_v * | |
| __pthread_deregister_pointer (pthread_t id) | |
| { | |
| size_t l, r, p; | |
| if (!idListCnt) | |
| return NULL; | |
| l = 0; r = idListCnt - 1; | |
| while (l <= r) | |
| { | |
| p = (l + r) >> 1; | |
| if (idList[p].id == id) | |
| { | |
| struct _pthread_v *ret = idList[p].ptr; | |
| p++; | |
| if (p < idListCnt) | |
| memmove (&idList[p - 1], &idList[p], sizeof (__pthread_idlist) * (idListCnt - p)); | |
| --idListCnt; | |
| /* Is this last element in list then free list. */ | |
| if (idListCnt == 0) | |
| { | |
| free (idList); | |
| idListCnt = idListMax = 0; | |
| } | |
| return ret; | |
| } | |
| else if (idList[p].id > id) | |
| { | |
| if (p == l) | |
| return NULL; | |
| r = p - 1; | |
| } | |
| else | |
| { | |
| l = p + 1; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* Save a _pthread_v element for reuse in pool. */ | |
| static void | |
| push_pthread_mem (_pthread_v *sv) | |
| { | |
| if (!sv || sv->next != NULL) | |
| return; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| if (sv->x != 0) | |
| __pthread_deregister_pointer (sv->x); | |
| if (sv->keyval) | |
| free (sv->keyval); | |
| if (sv->keyval_set) | |
| free (sv->keyval_set); | |
| if (sv->thread_name) | |
| free (sv->thread_name); | |
| memset (sv, 0, sizeof(struct _pthread_v)); | |
| if (pthr_last == NULL) | |
| pthr_root = pthr_last = sv; | |
| else | |
| { | |
| pthr_last->next = sv; | |
| pthr_last = sv; | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| } | |
| /* Get a _pthread_v element from pool, or allocate it. | |
| Note the unique identifier is created for the element here, too. */ | |
| static _pthread_v * | |
| pop_pthread_mem (void) | |
| { | |
| _pthread_v *r = NULL; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| if ((r = pthr_root) == NULL) | |
| { | |
| if ((r = (_pthread_v *)calloc (1,sizeof(struct _pthread_v))) != NULL) | |
| { | |
| r->x = __pthread_register_pointer (r); | |
| if (r->x == 0) | |
| { | |
| free (r); | |
| r = NULL; | |
| } | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return r; | |
| } | |
| r->x = __pthread_register_pointer (r); | |
| if (r->x == 0) | |
| r = NULL; | |
| else | |
| { | |
| if((pthr_root = r->next) == NULL) | |
| pthr_last = NULL; | |
| r->next = NULL; | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return r; | |
| } | |
| /* Free memory consumed in _pthread_v pointer pool. */ | |
| static void | |
| free_pthread_mem (void) | |
| { | |
| _pthread_v *t; | |
| if (1) | |
| return; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| t = pthr_root; | |
| while (t != NULL) | |
| { | |
| _pthread_v *sv = t; | |
| t = t->next; | |
| if (sv->x != 0 && sv->ended == 0 && sv->valid != DEAD_THREAD) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| pthread_cancel (t->x); | |
| Sleep (0); | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| t = pthr_root; | |
| continue; | |
| } | |
| else if (sv->x != 0 && sv->valid != DEAD_THREAD) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| Sleep (0); | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| continue; | |
| } | |
| if (sv->x != 0) | |
| __pthread_deregister_pointer (sv->x); | |
| sv->x = 0; | |
| free (sv); | |
| pthr_root = t; | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| } | |
| static void | |
| replace_spin_keys (pthread_spinlock_t *old, pthread_spinlock_t new) | |
| { | |
| if (old == NULL) | |
| return; | |
| if (EPERM == pthread_spin_destroy (old)) | |
| { | |
| #define THREADERR "Error cleaning up spin_keys for thread " | |
| #define THREADERR_LEN ((sizeof (THREADERR) / sizeof (*THREADERR)) - 1) | |
| #define THREADID_LEN THREADERR_LEN + 66 + 1 + 1 | |
| int i; | |
| char thread_id[THREADID_LEN] = THREADERR; | |
| _ultoa ((unsigned long) GetCurrentThreadId (), &thread_id[THREADERR_LEN], 10); | |
| for (i = THREADERR_LEN; thread_id[i] != '\0' && i < THREADID_LEN - 1; i++) | |
| { | |
| } | |
| if (i < THREADID_LEN - 1) | |
| { | |
| thread_id[i] = '\n'; | |
| thread_id[i + 1] = '\0'; | |
| } | |
| #undef THREADERR | |
| #undef THREADERR_LEN | |
| #undef THREADID_LEN | |
| OutputDebugStringA (thread_id); | |
| abort (); | |
| } | |
| *old = new; | |
| } | |
| /* Hook for TLS-based deregistration/registration of thread. */ | |
| static BOOL WINAPI | |
| __dyn_tls_pthread (HANDLE hDllHandle, DWORD dwReason, LPVOID lpreserved) | |
| { | |
| _pthread_v *t = NULL; | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| if (dwReason == DLL_PROCESS_DETACH) | |
| { | |
| #if defined(USE_VEH_FOR_MSC_SETTHREADNAME) | |
| if (lpreserved == NULL && SetThreadName_VEH_handle != NULL) | |
| { | |
| RemoveVectoredExceptionHandler (SetThreadName_VEH_handle); | |
| SetThreadName_VEH_handle = NULL; | |
| } | |
| #endif | |
| free_pthread_mem (); | |
| } | |
| else if (dwReason == DLL_PROCESS_ATTACH) | |
| { | |
| #if defined(USE_VEH_FOR_MSC_SETTHREADNAME) | |
| SetThreadName_VEH_handle = AddVectoredExceptionHandler (1, &SetThreadName_VEH); | |
| /* Can't do anything on error anyway, check for NULL later */ | |
| #endif | |
| } | |
| else if (dwReason == DLL_THREAD_DETACH) | |
| { | |
| if (_pthread_tls != 0xffffffff) | |
| t = (_pthread_v *)TlsGetValue(_pthread_tls); | |
| if (t && t->thread_noposix != 0) | |
| { | |
| _pthread_cleanup_dest (t->x); | |
| if (t->h != NULL) | |
| { | |
| CloseHandle (t->h); | |
| if (t->evStart) | |
| CloseHandle (t->evStart); | |
| t->evStart = NULL; | |
| t->h = NULL; | |
| } | |
| pthread_mutex_destroy (&t->p_clock); | |
| replace_spin_keys (&t->spin_keys, new_spin_keys); | |
| push_pthread_mem (t); | |
| t = NULL; | |
| TlsSetValue (_pthread_tls, t); | |
| } | |
| else if (t && t->ended == 0) | |
| { | |
| if (t->evStart) | |
| CloseHandle(t->evStart); | |
| t->evStart = NULL; | |
| t->ended = 1; | |
| _pthread_cleanup_dest (t->x); | |
| if ((t->p_state & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED) | |
| { | |
| t->valid = DEAD_THREAD; | |
| if (t->h != NULL) | |
| CloseHandle (t->h); | |
| t->h = NULL; | |
| pthread_mutex_destroy(&t->p_clock); | |
| replace_spin_keys (&t->spin_keys, new_spin_keys); | |
| push_pthread_mem (t); | |
| t = NULL; | |
| TlsSetValue (_pthread_tls, t); | |
| return TRUE; | |
| } | |
| pthread_mutex_destroy(&t->p_clock); | |
| replace_spin_keys (&t->spin_keys, new_spin_keys); | |
| } | |
| else if (t) | |
| { | |
| if (t->evStart) | |
| CloseHandle (t->evStart); | |
| t->evStart = NULL; | |
| pthread_mutex_destroy (&t->p_clock); | |
| replace_spin_keys (&t->spin_keys, new_spin_keys); | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /* TLS-runtime section variable. */ | |
| #ifdef _MSC_VER | |
| #pragma section(".CRT$XLF", shared) | |
| #endif | |
| PIMAGE_TLS_CALLBACK WINPTHREADS_ATTRIBUTE((WINPTHREADS_SECTION(".CRT$XLF"))) __xl_f = (PIMAGE_TLS_CALLBACK) __dyn_tls_pthread; | |
| #ifdef _MSC_VER | |
| #pragma data_seg() | |
| #endif | |
| #ifdef WINPTHREAD_DBG | |
| static int print_state = 0; | |
| void thread_print_set (int state) | |
| { | |
| print_state = state; | |
| } | |
| void | |
| thread_print (volatile pthread_t t, char *txt) | |
| { | |
| if (!print_state) | |
| return; | |
| if (!t) | |
| printf("T%p %d %s\n",NULL,(int)GetCurrentThreadId(),txt); | |
| else | |
| { | |
| printf("T%p %d V=%0X H=%p %s\n", | |
| __pth_gpointer_locked (t), | |
| (int)GetCurrentThreadId(), | |
| (int) (__pth_gpointer_locked (t))->valid, | |
| (__pth_gpointer_locked (t))->h, | |
| txt | |
| ); | |
| } | |
| } | |
| #endif | |
| /* Internal collect-once structure. */ | |
| typedef struct collect_once_t { | |
| pthread_once_t *o; | |
| pthread_mutex_t m; | |
| int count; | |
| struct collect_once_t *next; | |
| } collect_once_t; | |
| static collect_once_t *once_obj = NULL; | |
| static pthread_spinlock_t once_global = PTHREAD_SPINLOCK_INITIALIZER; | |
| static collect_once_t * | |
| enterOnceObject (pthread_once_t *o) | |
| { | |
| collect_once_t *c, *p = NULL; | |
| pthread_spin_lock (&once_global); | |
| c = once_obj; | |
| while (c != NULL && c->o != o) | |
| { | |
| c = (p = c)->next; | |
| } | |
| if (!c) | |
| { | |
| c = (collect_once_t *) calloc(1,sizeof(collect_once_t)); | |
| c->o = o; | |
| c->count = 1; | |
| if (!p) | |
| once_obj = c; | |
| else | |
| p->next = c; | |
| pthread_mutex_init(&c->m, NULL); | |
| } | |
| else | |
| c->count += 1; | |
| pthread_spin_unlock (&once_global); | |
| return c; | |
| } | |
| static void | |
| leaveOnceObject (collect_once_t *c) | |
| { | |
| collect_once_t *h, *p = NULL; | |
| if (!c) | |
| return; | |
| pthread_spin_lock (&once_global); | |
| h = once_obj; | |
| while (h != NULL && c != h) | |
| h = (p = h)->next; | |
| if (h) | |
| { | |
| c->count -= 1; | |
| if (c->count == 0) | |
| { | |
| pthread_mutex_destroy(&c->m); | |
| if (!p) | |
| once_obj = c->next; | |
| else | |
| p->next = c->next; | |
| free (c); | |
| } | |
| } | |
| else | |
| fprintf(stderr, "%p not found?!?!\n", c); | |
| pthread_spin_unlock (&once_global); | |
| } | |
| static void | |
| _pthread_once_cleanup (void *o) | |
| { | |
| collect_once_t *co = (collect_once_t *) o; | |
| pthread_mutex_unlock (&co->m); | |
| leaveOnceObject (co); | |
| } | |
| static int | |
| _pthread_once_raw (pthread_once_t *o, void (*func)(void)) | |
| { | |
| collect_once_t *co; | |
| long state = *o; | |
| CHECK_PTR(o); | |
| CHECK_PTR(func); | |
| if (state == 1) | |
| return 0; | |
| co = enterOnceObject(o); | |
| pthread_mutex_lock(&co->m); | |
| if (*o == 0) | |
| { | |
| func(); | |
| *o = 1; | |
| } | |
| else if (*o != 1) | |
| fprintf (stderr," once %p is %d\n", o, (int) *o); | |
| pthread_mutex_unlock(&co->m); | |
| leaveOnceObject(co); | |
| /* Done */ | |
| return 0; | |
| } | |
| /* Unimplemented. */ | |
| void * | |
| pthread_timechange_handler_np(void *dummy) | |
| { | |
| return NULL; | |
| } | |
| /* Compatibility routine for pthread-win32. It waits for ellapse of | |
| interval and additionally checks for possible thread-cancelation. */ | |
| int | |
| pthread_delay_np (const struct timespec *interval) | |
| { | |
| DWORD to = (!interval ? 0 : dwMilliSecs (_pthread_time_in_ms_from_timespec (interval))); | |
| struct _pthread_v *s = __pthread_self_lite (); | |
| if (!to) | |
| { | |
| pthread_testcancel (); | |
| Sleep (0); | |
| pthread_testcancel (); | |
| return 0; | |
| } | |
| pthread_testcancel (); | |
| if (s->evStart) | |
| _pthread_wait_for_single_object (s->evStart, to); | |
| else | |
| Sleep (to); | |
| pthread_testcancel (); | |
| return 0; | |
| } | |
| int pthread_delay_np_ms (DWORD to); | |
| int | |
| pthread_delay_np_ms (DWORD to) | |
| { | |
| struct _pthread_v *s = __pthread_self_lite (); | |
| if (!to) | |
| { | |
| pthread_testcancel (); | |
| Sleep (0); | |
| pthread_testcancel (); | |
| return 0; | |
| } | |
| pthread_testcancel (); | |
| if (s->evStart) | |
| _pthread_wait_for_single_object (s->evStart, to); | |
| else | |
| Sleep (to); | |
| pthread_testcancel (); | |
| return 0; | |
| } | |
| /* Compatibility routine for pthread-win32. It returns the | |
| amount of available CPUs on system. */ | |
| int | |
| pthread_num_processors_np(void) | |
| { | |
| int r = 0; | |
| DWORD_PTR ProcessAffinityMask, SystemAffinityMask; | |
| if (GetProcessAffinityMask(GetCurrentProcess(), &ProcessAffinityMask, &SystemAffinityMask)) | |
| { | |
| for(; ProcessAffinityMask != 0; ProcessAffinityMask >>= 1) | |
| r += (ProcessAffinityMask & 1) != 0; | |
| } | |
| /* assume at least 1 */ | |
| return r ? r : 1; | |
| } | |
| /* Compatiblity routine for pthread-win32. Allows to set amount of used | |
| CPUs for process. */ | |
| int | |
| pthread_set_num_processors_np(int n) | |
| { | |
| DWORD_PTR ProcessAffinityMask, ProcessNewAffinityMask = 0, SystemAffinityMask; | |
| int r = 0; | |
| /* need at least 1 */ | |
| n = n ? n : 1; | |
| if (GetProcessAffinityMask (GetCurrentProcess (), &ProcessAffinityMask, &SystemAffinityMask)) | |
| { | |
| for (; ProcessAffinityMask != 0; ProcessAffinityMask >>= 1) | |
| { | |
| ProcessNewAffinityMask <<= 1; | |
| if ((ProcessAffinityMask & 1) != 0 && r < n) | |
| { | |
| ProcessNewAffinityMask |= 1; | |
| r++; | |
| } | |
| } | |
| SetProcessAffinityMask (GetCurrentProcess (),ProcessNewAffinityMask); | |
| } | |
| return r; | |
| } | |
| int | |
| pthread_once (pthread_once_t *o, void (*func)(void)) | |
| { | |
| collect_once_t *co; | |
| long state = *o; | |
| CHECK_PTR(o); | |
| CHECK_PTR(func); | |
| if (state == 1) | |
| return 0; | |
| co = enterOnceObject(o); | |
| pthread_mutex_lock(&co->m); | |
| if (*o == 0) | |
| { | |
| pthread_cleanup_push(_pthread_once_cleanup, co); | |
| func(); | |
| pthread_cleanup_pop(0); | |
| *o = 1; | |
| } | |
| else if (*o != 1) | |
| fprintf (stderr," once %p is %d\n", o, (int) *o); | |
| pthread_mutex_unlock(&co->m); | |
| leaveOnceObject(co); | |
| return 0; | |
| } | |
| int | |
| pthread_key_create (pthread_key_t *key, void (* dest)(void *)) | |
| { | |
| unsigned int i; | |
| long nmax; | |
| void (**d)(void *); | |
| if (!key) | |
| return EINVAL; | |
| pthread_rwlock_wrlock (&_pthread_key_lock); | |
| for (i = _pthread_key_sch; i < _pthread_key_max; i++) | |
| { | |
| if (!_pthread_key_dest[i]) | |
| { | |
| *key = i; | |
| if (dest) | |
| _pthread_key_dest[i] = dest; | |
| else | |
| _pthread_key_dest[i] = (void(*)(void *))1; | |
| pthread_rwlock_unlock (&_pthread_key_lock); | |
| return 0; | |
| } | |
| } | |
| for (i = 0; i < _pthread_key_sch; i++) | |
| { | |
| if (!_pthread_key_dest[i]) | |
| { | |
| *key = i; | |
| if (dest) | |
| _pthread_key_dest[i] = dest; | |
| else | |
| _pthread_key_dest[i] = (void(*)(void *))1; | |
| pthread_rwlock_unlock (&_pthread_key_lock); | |
| return 0; | |
| } | |
| } | |
| if (_pthread_key_max == PTHREAD_KEYS_MAX) | |
| { | |
| pthread_rwlock_unlock(&_pthread_key_lock); | |
| return ENOMEM; | |
| } | |
| nmax = _pthread_key_max * 2; | |
| if (nmax == 0) | |
| nmax = _pthread_key_max + 1; | |
| if (nmax > PTHREAD_KEYS_MAX) | |
| nmax = PTHREAD_KEYS_MAX; | |
| /* No spare room anywhere */ | |
| d = (void (__cdecl **)(void *))realloc(_pthread_key_dest, nmax * sizeof(*d)); | |
| if (!d) | |
| { | |
| pthread_rwlock_unlock (&_pthread_key_lock); | |
| return ENOMEM; | |
| } | |
| /* Clear new region */ | |
| memset ((void *) &d[_pthread_key_max], 0, (nmax-_pthread_key_max)*sizeof(void *)); | |
| /* Use new region */ | |
| _pthread_key_dest = d; | |
| _pthread_key_sch = _pthread_key_max + 1; | |
| *key = _pthread_key_max; | |
| _pthread_key_max = nmax; | |
| if (dest) | |
| _pthread_key_dest[*key] = dest; | |
| else | |
| _pthread_key_dest[*key] = (void(*)(void *))1; | |
| pthread_rwlock_unlock (&_pthread_key_lock); | |
| return 0; | |
| } | |
| int | |
| pthread_key_delete (pthread_key_t key) | |
| { | |
| if (key >= _pthread_key_max || !_pthread_key_dest) | |
| return EINVAL; | |
| pthread_rwlock_wrlock (&_pthread_key_lock); | |
| _pthread_key_dest[key] = NULL; | |
| /* Start next search from our location */ | |
| if (_pthread_key_sch > key) | |
| _pthread_key_sch = key; | |
| /* So now we need to walk the complete list of threads | |
| and remove key's reference for it. */ | |
| __pth_remove_use_for_key (key); | |
| pthread_rwlock_unlock (&_pthread_key_lock); | |
| return 0; | |
| } | |
| void * | |
| pthread_getspecific (pthread_key_t key) | |
| { | |
| DWORD lasterr = GetLastError (); | |
| void *r; | |
| _pthread_v *t = __pthread_self_lite (); | |
| pthread_spin_lock (&t->spin_keys); | |
| r = (key >= t->keymax || t->keyval_set[key] == 0 ? NULL : t->keyval[key]); | |
| pthread_spin_unlock (&t->spin_keys); | |
| SetLastError (lasterr); | |
| return r; | |
| } | |
| int | |
| pthread_setspecific (pthread_key_t key, const void *value) | |
| { | |
| DWORD lasterr = GetLastError (); | |
| _pthread_v *t = __pthread_self_lite (); | |
| pthread_spin_lock (&t->spin_keys); | |
| if (key >= t->keymax) | |
| { | |
| int keymax = (key + 1); | |
| void **kv; | |
| unsigned char *kv_set; | |
| kv = (void **) realloc (t->keyval, keymax * sizeof (void *)); | |
| if (!kv) | |
| { | |
| pthread_spin_unlock (&t->spin_keys); | |
| return ENOMEM; | |
| } | |
| kv_set = (unsigned char *) realloc (t->keyval_set, keymax); | |
| if (!kv_set) | |
| { | |
| pthread_spin_unlock (&t->spin_keys); | |
| return ENOMEM; | |
| } | |
| /* Clear new region */ | |
| memset (&kv[t->keymax], 0, (keymax - t->keymax)*sizeof(void *)); | |
| memset (&kv_set[t->keymax], 0, (keymax - t->keymax)); | |
| t->keyval = kv; | |
| t->keyval_set = kv_set; | |
| t->keymax = keymax; | |
| } | |
| t->keyval[key] = (void *) value; | |
| t->keyval_set[key] = 1; | |
| pthread_spin_unlock (&t->spin_keys); | |
| SetLastError (lasterr); | |
| return 0; | |
| } | |
| int | |
| pthread_equal (pthread_t t1, pthread_t t2) | |
| { | |
| return (t1 == t2); | |
| } | |
| void | |
| pthread_tls_init (void) | |
| { | |
| _pthread_tls = TlsAlloc(); | |
| /* Cannot continue if out of indexes */ | |
| if (_pthread_tls == TLS_OUT_OF_INDEXES) | |
| abort(); | |
| } | |
| void | |
| _pthread_cleanup_dest (pthread_t t) | |
| { | |
| _pthread_v *tv; | |
| unsigned int i, j; | |
| if (!t) | |
| return; | |
| tv = __pth_gpointer_locked (t); | |
| if (!tv) | |
| return; | |
| for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) | |
| { | |
| int flag = 0; | |
| pthread_spin_lock (&tv->spin_keys); | |
| for (i = 0; i < tv->keymax; i++) | |
| { | |
| void *val = tv->keyval[i]; | |
| if (tv->keyval_set[i]) | |
| { | |
| pthread_rwlock_rdlock (&_pthread_key_lock); | |
| if ((uintptr_t) _pthread_key_dest[i] > 1) | |
| { | |
| /* Call destructor */ | |
| tv->keyval[i] = NULL; | |
| tv->keyval_set[i] = 0; | |
| pthread_spin_unlock (&tv->spin_keys); | |
| _pthread_key_dest[i](val); | |
| pthread_spin_lock (&tv->spin_keys); | |
| flag = 1; | |
| } | |
| else | |
| { | |
| tv->keyval[i] = NULL; | |
| tv->keyval_set[i] = 0; | |
| } | |
| pthread_rwlock_unlock(&_pthread_key_lock); | |
| } | |
| } | |
| pthread_spin_unlock (&tv->spin_keys); | |
| /* Nothing to do? */ | |
| if (!flag) | |
| return; | |
| } | |
| } | |
| static _pthread_v * | |
| __pthread_self_lite (void) | |
| { | |
| _pthread_v *t; | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| _pthread_once_raw (&_pthread_tls_once, pthread_tls_init); | |
| t = (_pthread_v *) TlsGetValue (_pthread_tls); | |
| if (t) | |
| return t; | |
| /* Main thread? */ | |
| t = (struct _pthread_v *) pop_pthread_mem (); | |
| /* If cannot initialize main thread, then the only thing we can do is return null pthread_t */ | |
| if (!__xl_f || !t) | |
| return 0; | |
| t->p_state = PTHREAD_DEFAULT_ATTR /*| PTHREAD_CREATE_DETACHED*/; | |
| t->tid = GetCurrentThreadId(); | |
| t->evStart = CreateEvent (NULL, 1, 0, NULL); | |
| t->p_clock = PTHREAD_MUTEX_INITIALIZER; | |
| replace_spin_keys (&t->spin_keys, new_spin_keys); | |
| t->sched_pol = SCHED_OTHER; | |
| t->h = NULL; //GetCurrentThread(); | |
| if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &t->h, 0, FALSE, DUPLICATE_SAME_ACCESS)) | |
| abort (); | |
| t->sched.sched_priority = GetThreadPriority(t->h); | |
| t->ended = 0; | |
| t->thread_noposix = 1; | |
| /* Save for later */ | |
| if (!TlsSetValue(_pthread_tls, t)) | |
| abort (); | |
| return t; | |
| } | |
| pthread_t | |
| pthread_self (void) | |
| { | |
| _pthread_v *t = __pthread_self_lite (); | |
| if (!t) | |
| return 0; | |
| return t->x; | |
| } | |
| /* Internal helper for getting event handle of thread T. */ | |
| void * | |
| pthread_getevent () | |
| { | |
| _pthread_v *t = __pthread_self_lite (); | |
| return (!t ? NULL : t->evStart); | |
| } | |
| /* Internal helper for getting thread handle of thread T. */ | |
| void * | |
| pthread_gethandle (pthread_t t) | |
| { | |
| struct _pthread_v *tv = __pth_gpointer_locked (t); | |
| return (!tv ? NULL : tv->h); | |
| } | |
| /* Internal helper for getting pointer of clean of current thread. */ | |
| struct _pthread_cleanup ** | |
| pthread_getclean (void) | |
| { | |
| struct _pthread_v *t = __pthread_self_lite (); | |
| if (!t) return NULL; | |
| return &t->clean; | |
| } | |
| int | |
| pthread_get_concurrency (int *val) | |
| { | |
| *val = _pthread_concur; | |
| return 0; | |
| } | |
| int | |
| pthread_set_concurrency (int val) | |
| { | |
| _pthread_concur = val; | |
| return 0; | |
| } | |
| void | |
| pthread_exit (void *res) | |
| { | |
| _pthread_v *t = NULL; | |
| unsigned rslt = (unsigned) ((intptr_t) res); | |
| struct _pthread_v *id = __pthread_self_lite (); | |
| id->ret_arg = res; | |
| _pthread_cleanup_dest (id->x); | |
| if (id->thread_noposix == 0) | |
| longjmp(id->jb, 1); | |
| /* Make sure we free ourselves if we are detached */ | |
| if ((t = (_pthread_v *)TlsGetValue(_pthread_tls)) != NULL) | |
| { | |
| if (!t->h) | |
| { | |
| t->valid = DEAD_THREAD; | |
| if (t->evStart) | |
| CloseHandle (t->evStart); | |
| t->evStart = NULL; | |
| rslt = (unsigned) (size_t) t->ret_arg; | |
| push_pthread_mem(t); | |
| t = NULL; | |
| TlsSetValue (_pthread_tls, t); | |
| } | |
| else | |
| { | |
| rslt = (unsigned) (size_t) t->ret_arg; | |
| t->ended = 1; | |
| if (t->evStart) | |
| CloseHandle (t->evStart); | |
| t->evStart = NULL; | |
| if ((t->p_state & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED) | |
| { | |
| t->valid = DEAD_THREAD; | |
| CloseHandle (t->h); | |
| t->h = NULL; | |
| push_pthread_mem(t); | |
| t = NULL; | |
| TlsSetValue(_pthread_tls, t); | |
| } | |
| } | |
| } | |
| /* Time to die */ | |
| _endthreadex(rslt); | |
| } | |
| void | |
| _pthread_invoke_cancel (void) | |
| { | |
| _pthread_cleanup *pcup; | |
| struct _pthread_v *se = __pthread_self_lite (); | |
| se->in_cancel = 1; | |
| _pthread_setnobreak (1); | |
| InterlockedDecrement(&_pthread_cancelling); | |
| /* Call cancel queue */ | |
| for (pcup = se->clean; pcup; pcup = pcup->next) | |
| { | |
| pcup->func((pthread_once_t *)pcup->arg); | |
| } | |
| _pthread_setnobreak (0); | |
| pthread_exit(PTHREAD_CANCELED); | |
| } | |
| int | |
| __pthread_shallcancel (void) | |
| { | |
| struct _pthread_v *t; | |
| if (!_pthread_cancelling) | |
| return 0; | |
| t = __pthread_self_lite (); | |
| if (t == NULL) | |
| return 0; | |
| if (t->nobreak <= 0 && t->cancelled && (t->p_state & PTHREAD_CANCEL_ENABLE)) | |
| return 1; | |
| return 0; | |
| } | |
| void | |
| _pthread_setnobreak (int v) | |
| { | |
| struct _pthread_v *t = __pthread_self_lite (); | |
| if (t == NULL) | |
| return; | |
| if (v > 0) | |
| InterlockedIncrement ((long*)&t->nobreak); | |
| else | |
| InterlockedDecrement((long*)&t->nobreak); | |
| } | |
| void | |
| pthread_testcancel (void) | |
| { | |
| struct _pthread_v *self = __pthread_self_lite (); | |
| if (!self || self->in_cancel) | |
| return; | |
| if (!_pthread_cancelling) | |
| return; | |
| pthread_mutex_lock (&self->p_clock); | |
| if (self->cancelled && (self->p_state & PTHREAD_CANCEL_ENABLE) && self->nobreak <= 0) | |
| { | |
| self->in_cancel = 1; | |
| self->p_state &= ~PTHREAD_CANCEL_ENABLE; | |
| if (self->evStart) | |
| ResetEvent (self->evStart); | |
| pthread_mutex_unlock (&self->p_clock); | |
| _pthread_invoke_cancel (); | |
| } | |
| pthread_mutex_unlock (&self->p_clock); | |
| } | |
| int | |
| pthread_cancel (pthread_t t) | |
| { | |
| struct _pthread_v *tv = __pth_gpointer_locked (t); | |
| if (tv == NULL) | |
| return ESRCH; | |
| CHECK_OBJECT(tv, ESRCH); | |
| /*if (tv->ended) return ESRCH;*/ | |
| pthread_mutex_lock(&tv->p_clock); | |
| if (pthread_equal(pthread_self(), t)) | |
| { | |
| if(tv->cancelled) | |
| { | |
| pthread_mutex_unlock(&tv->p_clock); | |
| return (tv->in_cancel ? ESRCH : 0); | |
| } | |
| tv->cancelled = 1; | |
| InterlockedIncrement(&_pthread_cancelling); | |
| if(tv->evStart) SetEvent(tv->evStart); | |
| if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) != 0 && (tv->p_state & PTHREAD_CANCEL_ENABLE) != 0) | |
| { | |
| tv->p_state &= ~PTHREAD_CANCEL_ENABLE; | |
| tv->in_cancel = 1; | |
| pthread_mutex_unlock(&tv->p_clock); | |
| _pthread_invoke_cancel(); | |
| } | |
| else | |
| pthread_mutex_unlock(&tv->p_clock); | |
| return 0; | |
| } | |
| if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) != 0 && (tv->p_state & PTHREAD_CANCEL_ENABLE) != 0) | |
| { | |
| /* Dangerous asynchronous cancelling */ | |
| CONTEXT ctxt; | |
| if(tv->in_cancel) | |
| { | |
| pthread_mutex_unlock(&tv->p_clock); | |
| return (tv->in_cancel ? ESRCH : 0); | |
| } | |
| /* Already done? */ | |
| if(tv->cancelled || tv->in_cancel) | |
| { | |
| /* ??? pthread_mutex_unlock (&tv->p_clock); */ | |
| return ESRCH; | |
| } | |
| ctxt.ContextFlags = CONTEXT_CONTROL; | |
| SuspendThread (tv->h); | |
| if (WaitForSingleObject (tv->h, 0) == WAIT_TIMEOUT) | |
| { | |
| GetThreadContext(tv->h, &ctxt); | |
| #ifdef _M_X64 | |
| ctxt.Rip = (uintptr_t) _pthread_invoke_cancel; | |
| #elif defined(_M_IX86) | |
| ctxt.Eip = (uintptr_t) _pthread_invoke_cancel; | |
| #elif defined(_M_ARM) || defined(_M_ARM64) | |
| ctxt.Pc = (uintptr_t) _pthread_invoke_cancel; | |
| #else | |
| #error Unsupported architecture | |
| #endif | |
| #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) | |
| SetThreadContext (tv->h, &ctxt); | |
| #endif | |
| /* Also try deferred Cancelling */ | |
| tv->cancelled = 1; | |
| tv->p_state &= ~PTHREAD_CANCEL_ENABLE; | |
| tv->in_cancel = 1; | |
| /* Notify everyone to look */ | |
| InterlockedIncrement (&_pthread_cancelling); | |
| if (tv->evStart) | |
| SetEvent (tv->evStart); | |
| pthread_mutex_unlock (&tv->p_clock); | |
| ResumeThread (tv->h); | |
| } | |
| } | |
| else | |
| { | |
| if (tv->cancelled == 0) | |
| { | |
| /* Safe deferred Cancelling */ | |
| tv->cancelled = 1; | |
| /* Notify everyone to look */ | |
| InterlockedIncrement (&_pthread_cancelling); | |
| if (tv->evStart) | |
| SetEvent (tv->evStart); | |
| } | |
| else | |
| { | |
| pthread_mutex_unlock (&tv->p_clock); | |
| return (tv->in_cancel ? ESRCH : 0); | |
| } | |
| } | |
| pthread_mutex_unlock (&tv->p_clock); | |
| return 0; | |
| } | |
| /* half-stubbed version as we don't really well support signals */ | |
| int | |
| pthread_kill (pthread_t t, int sig) | |
| { | |
| struct _pthread_v *tv; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| tv = __pthread_get_pointer (t); | |
| if (!tv || t != tv->x || tv->in_cancel || tv->ended || tv->h == NULL | |
| || tv->h == INVALID_HANDLE_VALUE) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return ESRCH; | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| if (!sig) | |
| return 0; | |
| if (sig < SIGINT || sig > NSIG) | |
| return EINVAL; | |
| return pthread_cancel(t); | |
| } | |
| unsigned | |
| _pthread_get_state (const pthread_attr_t *attr, unsigned flag) | |
| { | |
| return (attr->p_state & flag); | |
| } | |
| int | |
| _pthread_set_state (pthread_attr_t *attr, unsigned flag, unsigned val) | |
| { | |
| if (~flag & val) | |
| return EINVAL; | |
| attr->p_state &= ~flag; | |
| attr->p_state |= val; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_init (pthread_attr_t *attr) | |
| { | |
| memset (attr, 0, sizeof (pthread_attr_t)); | |
| attr->p_state = PTHREAD_DEFAULT_ATTR; | |
| attr->stack = NULL; | |
| attr->s_size = 0; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_destroy (pthread_attr_t *attr) | |
| { | |
| /* No need to do anything */ | |
| memset (attr, 0, sizeof(pthread_attr_t)); | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setdetachstate (pthread_attr_t *a, int flag) | |
| { | |
| return _pthread_set_state(a, PTHREAD_CREATE_DETACHED, flag); | |
| } | |
| int | |
| pthread_attr_getdetachstate (const pthread_attr_t *a, int *flag) | |
| { | |
| *flag = _pthread_get_state(a, PTHREAD_CREATE_DETACHED); | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setinheritsched (pthread_attr_t *a, int flag) | |
| { | |
| if (!a || (flag != PTHREAD_INHERIT_SCHED && flag != PTHREAD_EXPLICIT_SCHED)) | |
| return EINVAL; | |
| return _pthread_set_state(a, PTHREAD_INHERIT_SCHED, flag); | |
| } | |
| int | |
| pthread_attr_getinheritsched (const pthread_attr_t *a, int *flag) | |
| { | |
| *flag = _pthread_get_state(a, PTHREAD_INHERIT_SCHED); | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setscope (pthread_attr_t *a, int flag) | |
| { | |
| return _pthread_set_state(a, PTHREAD_SCOPE_SYSTEM, flag); | |
| } | |
| int | |
| pthread_attr_getscope (const pthread_attr_t *a, int *flag) | |
| { | |
| *flag = _pthread_get_state(a, PTHREAD_SCOPE_SYSTEM); | |
| return 0; | |
| } | |
| int | |
| pthread_attr_getstack (const pthread_attr_t *attr, void **stack, size_t *size) | |
| { | |
| *stack = (char *) attr->stack - attr->s_size; | |
| *size = attr->s_size; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setstack (pthread_attr_t *attr, void *stack, size_t size) | |
| { | |
| attr->s_size = size; | |
| attr->stack = (char *) stack + size; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_getstackaddr (const pthread_attr_t *attr, void **stack) | |
| { | |
| *stack = attr->stack; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setstackaddr (pthread_attr_t *attr, void *stack) | |
| { | |
| attr->stack = stack; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_getstacksize (const pthread_attr_t *attr, size_t *size) | |
| { | |
| *size = attr->s_size; | |
| return 0; | |
| } | |
| int | |
| pthread_attr_setstacksize (pthread_attr_t *attr, size_t size) | |
| { | |
| attr->s_size = size; | |
| return 0; | |
| } | |
| static void | |
| test_cancel_locked (pthread_t t) | |
| { | |
| struct _pthread_v *tv = __pth_gpointer_locked (t); | |
| if (!tv || tv->in_cancel || tv->ended != 0 || (tv->p_state & PTHREAD_CANCEL_ENABLE) == 0) | |
| return; | |
| if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) == 0) | |
| return; | |
| if (WaitForSingleObject(tv->evStart, 0) != WAIT_OBJECT_0) | |
| return; | |
| pthread_mutex_unlock (&tv->p_clock); | |
| _pthread_invoke_cancel(); | |
| } | |
| int | |
| pthread_setcancelstate (int state, int *oldstate) | |
| { | |
| _pthread_v *t = __pthread_self_lite (); | |
| if (!t || (state & PTHREAD_CANCEL_ENABLE) != state) | |
| return EINVAL; | |
| pthread_mutex_lock (&t->p_clock); | |
| if (oldstate) | |
| *oldstate = t->p_state & PTHREAD_CANCEL_ENABLE; | |
| t->p_state &= ~PTHREAD_CANCEL_ENABLE; | |
| t->p_state |= state; | |
| test_cancel_locked (t->x); | |
| pthread_mutex_unlock (&t->p_clock); | |
| return 0; | |
| } | |
| int | |
| pthread_setcanceltype (int type, int *oldtype) | |
| { | |
| _pthread_v *t = __pthread_self_lite (); | |
| if (!t || (type & PTHREAD_CANCEL_ASYNCHRONOUS) != type) | |
| return EINVAL; | |
| pthread_mutex_lock (&t->p_clock); | |
| if (oldtype) | |
| *oldtype = t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS; | |
| t->p_state &= ~PTHREAD_CANCEL_ASYNCHRONOUS; | |
| t->p_state |= type; | |
| test_cancel_locked (t->x); | |
| pthread_mutex_unlock (&t->p_clock); | |
| return 0; | |
| } | |
| void _fpreset (void); | |
| #if defined(__i386__) | |
| /* Align ESP on 16-byte boundaries. */ | |
| # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2) | |
| __attribute__((force_align_arg_pointer)) | |
| # endif | |
| #endif | |
| int | |
| pthread_create_wrapper (void *args) | |
| { | |
| unsigned rslt = 0; | |
| struct _pthread_v *tv = (struct _pthread_v *)args; | |
| _fpreset(); | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| pthread_mutex_lock (&tv->p_clock); | |
| _pthread_once_raw(&_pthread_tls_once, pthread_tls_init); | |
| TlsSetValue(_pthread_tls, tv); | |
| tv->tid = GetCurrentThreadId(); | |
| pthread_mutex_unlock (&tv->p_clock); | |
| if (!setjmp(tv->jb)) | |
| { | |
| intptr_t trslt = (intptr_t) 128; | |
| /* Provide to this thread a default exception handler. */ | |
| #ifdef __SEH__ | |
| asm ("\t.tl_start:\n" | |
| "\t.seh_handler __C_specific_handler, @except\n" | |
| "\t.seh_handlerdata\n" | |
| "\t.long 1\n" | |
| "\t.rva .tl_start, .tl_end, _gnu_exception_handler ,.tl_end\n" | |
| "\t.text" | |
| ); | |
| #endif /* Call function and save return value */ | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| if (tv->func) | |
| trslt = (intptr_t) tv->func(tv->ret_arg); | |
| #ifdef __SEH__ | |
| asm ("\tnop\n\t.tl_end: nop\n"); | |
| #endif | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| tv->ret_arg = (void*) trslt; | |
| /* Clean up destructors */ | |
| _pthread_cleanup_dest(tv->x); | |
| } | |
| else | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| pthread_mutex_lock (&tv->p_clock); | |
| rslt = (unsigned) (size_t) tv->ret_arg; | |
| /* Make sure we free ourselves if we are detached */ | |
| if (tv->evStart) | |
| CloseHandle (tv->evStart); | |
| tv->evStart = NULL; | |
| if (!tv->h) | |
| { | |
| tv->valid = DEAD_THREAD; | |
| pthread_mutex_unlock (&tv->p_clock); | |
| pthread_mutex_destroy (&tv->p_clock); | |
| push_pthread_mem (tv); | |
| tv = NULL; | |
| TlsSetValue (_pthread_tls, tv); | |
| } | |
| else | |
| { | |
| pthread_mutex_unlock (&tv->p_clock); | |
| pthread_mutex_destroy (&tv->p_clock); | |
| /* Reinitialise p_clock, since there may be attempts at | |
| destroying it again in __dyn_tls_thread later on. */ | |
| tv->p_clock = PTHREAD_MUTEX_INITIALIZER; | |
| tv->ended = 1; | |
| } | |
| while (pthread_mutex_unlock (&mtx_pthr_locked) == 0) | |
| Sleep (0); | |
| _endthreadex (rslt); | |
| return rslt; | |
| } | |
| int | |
| pthread_create (pthread_t *th, const pthread_attr_t *attr, void *(* func)(void *), void *arg) | |
| { | |
| HANDLE thrd = NULL; | |
| int redo = 0; | |
| struct _pthread_v *tv; | |
| size_t ssize = 0; | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| if ((tv = pop_pthread_mem ()) == NULL) | |
| return EAGAIN; | |
| if (th) | |
| *th = tv->x; | |
| /* Save data in pthread_t */ | |
| tv->ended = 0; | |
| tv->ret_arg = arg; | |
| tv->func = func; | |
| tv->p_state = PTHREAD_DEFAULT_ATTR; | |
| tv->h = INVALID_HANDLE_VALUE; | |
| /* We retry it here a few times, as events are a limited resource ... */ | |
| do | |
| { | |
| tv->evStart = CreateEvent (NULL, 1, 0, NULL); | |
| if (tv->evStart != NULL) | |
| break; | |
| Sleep ((!redo ? 0 : 20)); | |
| } | |
| while (++redo <= 4); | |
| tv->p_clock = PTHREAD_MUTEX_INITIALIZER; | |
| replace_spin_keys (&tv->spin_keys, new_spin_keys); | |
| tv->valid = LIFE_THREAD; | |
| tv->sched.sched_priority = THREAD_PRIORITY_NORMAL; | |
| tv->sched_pol = SCHED_OTHER; | |
| if (tv->evStart == NULL) | |
| { | |
| if (th) | |
| memset (th, 0, sizeof (pthread_t)); | |
| push_pthread_mem (tv); | |
| return EAGAIN; | |
| } | |
| if (attr) | |
| { | |
| int inh = 0; | |
| tv->p_state = attr->p_state; | |
| ssize = attr->s_size; | |
| pthread_attr_getinheritsched (attr, &inh); | |
| if (inh) | |
| { | |
| tv->sched.sched_priority = __pthread_self_lite ()->sched.sched_priority; | |
| } | |
| else | |
| tv->sched.sched_priority = attr->param.sched_priority; | |
| } | |
| /* Make sure tv->h has value of INVALID_HANDLE_VALUE */ | |
| _ReadWriteBarrier(); | |
| thrd = (HANDLE) _beginthreadex(NULL, ssize, (unsigned int (__stdcall *)(void *))pthread_create_wrapper, tv, 0x4/*CREATE_SUSPEND*/, NULL); | |
| if (thrd == INVALID_HANDLE_VALUE) | |
| thrd = 0; | |
| /* Failed */ | |
| if (!thrd) | |
| { | |
| if (tv->evStart) | |
| CloseHandle (tv->evStart); | |
| pthread_mutex_destroy (&tv->p_clock); | |
| replace_spin_keys (&tv->spin_keys, new_spin_keys); | |
| tv->evStart = NULL; | |
| tv->h = 0; | |
| if (th) | |
| memset (th, 0, sizeof (pthread_t)); | |
| push_pthread_mem (tv); | |
| return EAGAIN; | |
| } | |
| { | |
| int pr = tv->sched.sched_priority; | |
| if (pr <= THREAD_PRIORITY_IDLE) { | |
| pr = THREAD_PRIORITY_IDLE; | |
| } else if (pr <= THREAD_PRIORITY_LOWEST) { | |
| pr = THREAD_PRIORITY_LOWEST; | |
| } else if (pr >= THREAD_PRIORITY_TIME_CRITICAL) { | |
| pr = THREAD_PRIORITY_TIME_CRITICAL; | |
| } else if (pr >= THREAD_PRIORITY_HIGHEST) { | |
| pr = THREAD_PRIORITY_HIGHEST; | |
| } | |
| SetThreadPriority (thrd, pr); | |
| } | |
| ResetEvent (tv->evStart); | |
| if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0) | |
| { | |
| tv->h = 0; | |
| ResumeThread (thrd); | |
| CloseHandle (thrd); | |
| } | |
| else | |
| { | |
| tv->h = thrd; | |
| ResumeThread (thrd); | |
| } | |
| Sleep (0); | |
| return 0; | |
| } | |
| int | |
| pthread_join (pthread_t t, void **res) | |
| { | |
| DWORD dwFlags; | |
| struct _pthread_v *tv = __pth_gpointer_locked (t); | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags)) | |
| return ESRCH; | |
| if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0) | |
| return EINVAL; | |
| if (pthread_equal(pthread_self(), t)) | |
| return EDEADLK; | |
| /* pthread_testcancel (); */ | |
| if (tv->ended == 0 || (tv->h != NULL && tv->h != INVALID_HANDLE_VALUE)) | |
| WaitForSingleObject (tv->h, INFINITE); | |
| CloseHandle (tv->h); | |
| if (tv->evStart) | |
| CloseHandle (tv->evStart); | |
| tv->evStart = NULL; | |
| /* Obtain return value */ | |
| if (res) | |
| *res = tv->ret_arg; | |
| pthread_mutex_destroy (&tv->p_clock); | |
| replace_spin_keys (&tv->spin_keys, new_spin_keys); | |
| push_pthread_mem (tv); | |
| return 0; | |
| } | |
| int | |
| _pthread_tryjoin (pthread_t t, void **res) | |
| { | |
| DWORD dwFlags; | |
| struct _pthread_v *tv; | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| tv = __pthread_get_pointer (t); | |
| if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags)) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return ESRCH; | |
| } | |
| if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return EINVAL; | |
| } | |
| if (pthread_equal(pthread_self(), t)) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return EDEADLK; | |
| } | |
| if(tv->ended == 0 && WaitForSingleObject(tv->h, 0)) | |
| { | |
| if (tv->ended == 0) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| /* pthread_testcancel (); */ | |
| return EBUSY; | |
| } | |
| } | |
| CloseHandle (tv->h); | |
| if (tv->evStart) | |
| CloseHandle (tv->evStart); | |
| tv->evStart = NULL; | |
| /* Obtain return value */ | |
| if (res) | |
| *res = tv->ret_arg; | |
| pthread_mutex_destroy (&tv->p_clock); | |
| replace_spin_keys (&tv->spin_keys, new_spin_keys); | |
| push_pthread_mem (tv); | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| /* pthread_testcancel (); */ | |
| return 0; | |
| } | |
| int | |
| pthread_detach (pthread_t t) | |
| { | |
| int r = 0; | |
| DWORD dwFlags; | |
| struct _pthread_v *tv = __pth_gpointer_locked (t); | |
| HANDLE dw; | |
| pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER; | |
| pthread_mutex_lock (&mtx_pthr_locked); | |
| if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags)) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return ESRCH; | |
| } | |
| if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0) | |
| { | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return EINVAL; | |
| } | |
| /* if (tv->ended) r = ESRCH; */ | |
| dw = tv->h; | |
| tv->h = 0; | |
| tv->p_state |= PTHREAD_CREATE_DETACHED; | |
| _ReadWriteBarrier(); | |
| if (dw) | |
| { | |
| CloseHandle (dw); | |
| if (tv->ended) | |
| { | |
| if (tv->evStart) | |
| CloseHandle (tv->evStart); | |
| tv->evStart = NULL; | |
| pthread_mutex_destroy (&tv->p_clock); | |
| replace_spin_keys (&tv->spin_keys, new_spin_keys); | |
| push_pthread_mem (tv); | |
| } | |
| } | |
| pthread_mutex_unlock (&mtx_pthr_locked); | |
| return r; | |
| } | |
| static int dummy_concurrency_level = 0; | |
| int | |
| pthread_getconcurrency (void) | |
| { | |
| return dummy_concurrency_level; | |
| } | |
| int | |
| pthread_setconcurrency (int new_level) | |
| { | |
| dummy_concurrency_level = new_level; | |
| return 0; | |
| } | |
| int | |
| pthread_setname_np (pthread_t thread, const char *name) | |
| { | |
| struct _pthread_v *tv; | |
| char *stored_name; | |
| if (name == NULL) | |
| return EINVAL; | |
| tv = __pth_gpointer_locked (thread); | |
| if (!tv || thread != tv->x || tv->in_cancel || tv->ended || tv->h == NULL | |
| || tv->h == INVALID_HANDLE_VALUE) | |
| return ESRCH; | |
| stored_name = strdup (name); | |
| if (stored_name == NULL) | |
| return ENOMEM; | |
| if (tv->thread_name != NULL) | |
| free (tv->thread_name); | |
| tv->thread_name = stored_name; | |
| SetThreadName (tv->tid, name); | |
| return 0; | |
| } | |
| int | |
| pthread_getname_np (pthread_t thread, char *name, size_t len) | |
| { | |
| HRESULT result; | |
| struct _pthread_v *tv; | |
| if (name == NULL) | |
| return EINVAL; | |
| tv = __pth_gpointer_locked (thread); | |
| if (!tv || thread != tv->x || tv->in_cancel || tv->ended || tv->h == NULL | |
| || tv->h == INVALID_HANDLE_VALUE) | |
| return ESRCH; | |
| if (len < 1) | |
| return ERANGE; | |
| if (tv->thread_name == NULL) | |
| { | |
| name[0] = '\0'; | |
| return 0; | |
| } | |
| if (strlen (tv->thread_name) >= len) | |
| return ERANGE; | |
| result = StringCchCopyNA (name, len, tv->thread_name, len - 1); | |
| if (SUCCEEDED (result)) | |
| return 0; | |
| return ERANGE; | |
| } |