X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=glib%2Fgthread-win32.c;h=58e244ebedaad5e795089117f9fdaec596fdbe40;hb=073339230bc0c4b42387f786c6b0d7aaca3f1a81;hp=cadd0c7838d332160b5634dfae339a841ee130cf;hpb=42af8eb39d2744af68a470bcd9f7aae3c8f43602;p=platform%2Fupstream%2Fglib.git diff --git a/glib/gthread-win32.c b/glib/gthread-win32.c index cadd0c7..58e244e 100644 --- a/glib/gthread-win32.c +++ b/glib/gthread-win32.c @@ -5,10 +5,12 @@ * Copyright 1998-2001 Sebastian Wilhelmi; University of Karlsruhe * Copyright 2001 Hans Breuer * + * SPDX-License-Identifier: LGPL-2.1-or-later + * * This library 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 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library; if not, see . */ /* @@ -42,8 +42,11 @@ #include "config.h" +#include "glib.h" +#include "glib-init.h" #include "gthread.h" #include "gthreadprivate.h" +#include "gslice.h" #include @@ -57,12 +60,12 @@ g_thread_abort (gint status, { fprintf (stderr, "GLib (gthread-win32.c): Unexpected error from C library during '%s': %s. Aborting.\n", strerror (status), function); - abort (); + g_abort (); } /* Starting with Vista and Windows 2008, we have access to the - * CONDITION_VARIABLE and SRWLock primatives on Windows, which are - * pretty reasonable approximations of the primatives specified in + * CONDITION_VARIABLE and SRWLock primitives on Windows, which are + * pretty reasonable approximations of the primitives specified in * POSIX 2001 (pthread_cond_t and pthread_mutex_t respectively). * * Both of these types are structs containing a single pointer. That @@ -70,839 +73,667 @@ g_thread_abort (gint status, * that only get the kernel involved in cases of contention (similar * to how futex()-based mutexes work on Linux). The biggest advantage * of these new types is that they can be statically initialised to - * zero. This allows us to use them directly and still support: - * - * GMutex mutex = G_MUTEX_INIT; - * - * and - * - * GCond cond = G_COND_INIT; - * - * Unfortunately, Windows XP lacks these facilities and GLib still - * needs to support Windows XP. Our approach here is as follows: - * - * - avoid depending on structure declarations at compile-time by - * declaring our own GMutex and GCond strutures to be - * ABI-compatible with SRWLock and CONDITION_VARIABLE and using - * those instead - * - * - avoid a hard dependency on the symbols used to manipulate these - * structures by doing a dynamic lookup of those symbols at - * runtime - * - * - if the symbols are not available, emulate them using other - * primatives - * - * Using this approach also allows us to easily build a GLib that lacks - * support for Windows XP or to remove this code entirely when XP is no - * longer supported (end of line is currently April 8, 2014). + * zero. That means that they are completely ABI compatible with our + * GMutex and GCond APIs. */ -typedef struct -{ - void (__stdcall * CallThisOnThreadExit) (void); /* fake */ - - void (__stdcall * InitializeSRWLock) (gpointer lock); - void (__stdcall * DeleteSRWLock) (gpointer lock); /* fake */ - void (__stdcall * AcquireSRWLockExclusive) (gpointer lock); - BOOLEAN (__stdcall * TryAcquireSRWLockExclusive) (gpointer lock); - void (__stdcall * ReleaseSRWLockExclusive) (gpointer lock); - void (__stdcall * AcquireSRWLockShared) (gpointer lock); - BOOLEAN (__stdcall * TryAcquireSRWLockShared) (gpointer lock); - void (__stdcall * ReleaseSRWLockShared) (gpointer lock); - - void (__stdcall * InitializeConditionVariable) (gpointer cond); - void (__stdcall * DeleteConditionVariable) (gpointer cond); /* fake */ - BOOL (__stdcall * SleepConditionVariableSRW) (gpointer cond, - gpointer lock, - DWORD timeout, - ULONG flags); - void (__stdcall * WakeAllConditionVariable) (gpointer cond); - void (__stdcall * WakeConditionVariable) (gpointer cond); -} GThreadImplVtable; - -static GThreadImplVtable g_thread_impl_vtable; /* {{{1 GMutex */ void g_mutex_init (GMutex *mutex) { - g_thread_impl_vtable.InitializeSRWLock (mutex); + InitializeSRWLock ((gpointer) mutex); } void g_mutex_clear (GMutex *mutex) { - if (g_thread_impl_vtable.DeleteSRWLock != NULL) - g_thread_impl_vtable.DeleteSRWLock (mutex); } void g_mutex_lock (GMutex *mutex) { - g_thread_impl_vtable.AcquireSRWLockExclusive (mutex); + AcquireSRWLockExclusive ((gpointer) mutex); } gboolean g_mutex_trylock (GMutex *mutex) { - return g_thread_impl_vtable.TryAcquireSRWLockExclusive (mutex); + return TryAcquireSRWLockExclusive ((gpointer) mutex); } void g_mutex_unlock (GMutex *mutex) { - g_thread_impl_vtable.ReleaseSRWLockExclusive (mutex); + ReleaseSRWLockExclusive ((gpointer) mutex); } -/* {{{1 GCond */ -void -g_cond_init (GCond *cond) +/* {{{1 GRecMutex */ + +static CRITICAL_SECTION * +g_rec_mutex_impl_new (void) { - g_thread_impl_vtable.InitializeConditionVariable (cond); + CRITICAL_SECTION *cs; + + cs = g_slice_new (CRITICAL_SECTION); + InitializeCriticalSection (cs); + + return cs; } -void -g_cond_clear (GCond *cond) +static void +g_rec_mutex_impl_free (CRITICAL_SECTION *cs) { - if (g_thread_impl_vtable.DeleteConditionVariable) - g_thread_impl_vtable.DeleteConditionVariable (cond); + DeleteCriticalSection (cs); + g_slice_free (CRITICAL_SECTION, cs); } -void -g_cond_signal (GCond *cond) +static CRITICAL_SECTION * +g_rec_mutex_get_impl (GRecMutex *mutex) { - g_thread_impl_vtable.WakeConditionVariable (cond); + CRITICAL_SECTION *impl = mutex->p; + + if G_UNLIKELY (mutex->p == NULL) + { + impl = g_rec_mutex_impl_new (); + if (InterlockedCompareExchangePointer (&mutex->p, impl, NULL) != NULL) + g_rec_mutex_impl_free (impl); + impl = mutex->p; + } + + return impl; } void -g_cond_broadcast (GCond *cond) +g_rec_mutex_init (GRecMutex *mutex) { - g_thread_impl_vtable.WakeAllConditionVariable (cond); + mutex->p = g_rec_mutex_impl_new (); } void -g_cond_wait (GCond *cond, - GMutex *entered_mutex) +g_rec_mutex_clear (GRecMutex *mutex) { - g_thread_impl_vtable.SleepConditionVariableSRW (cond, entered_mutex, INFINITE, 0); + g_rec_mutex_impl_free (mutex->p); } -gboolean -g_cond_timedwait (GCond *cond, - GMutex *entered_mutex, - gint64 abs_time) +void +g_rec_mutex_lock (GRecMutex *mutex) { - gint64 span; - FILETIME ft; - gint64 now; - - GetSystemTimeAsFileTime (&ft); - memmove (&now, &ft, sizeof (FILETIME)); - - now -= G_GINT64_CONSTANT (116444736000000000); - now /= 10; - - span = abs_time - now; - - if G_UNLIKELY (span < 0) - span = 0; - - if G_UNLIKELY (span > G_GINT64_CONSTANT (1000) * G_MAXINT32) - span = INFINITE; + EnterCriticalSection (g_rec_mutex_get_impl (mutex)); +} - return g_thread_impl_vtable.SleepConditionVariableSRW (cond, entered_mutex, span / 1000, 0); +void +g_rec_mutex_unlock (GRecMutex *mutex) +{ + LeaveCriticalSection (mutex->p); } gboolean -g_cond_timed_wait (GCond *cond, - GMutex *entered_mutex, - GTimeVal *abs_time) +g_rec_mutex_trylock (GRecMutex *mutex) { - if (abs_time) - { - gint64 micros; - - micros = abs_time->tv_sec; - micros *= 1000000; - micros += abs_time->tv_usec; - - return g_cond_timedwait (cond, entered_mutex, micros); - } - else - { - g_cond_wait (cond, entered_mutex); - return TRUE; - } + return TryEnterCriticalSection (g_rec_mutex_get_impl (mutex)); } -/* {{{1 GPrivate */ +/* {{{1 GRWLock */ -typedef struct _GPrivateDestructor GPrivateDestructor; - -struct _GPrivateDestructor +void +g_rw_lock_init (GRWLock *lock) { - DWORD index; - GDestroyNotify notify; - GPrivateDestructor *next; -}; - -static GPrivateDestructor * volatile g_private_destructors; + InitializeSRWLock ((gpointer) lock); +} void -g_private_init (GPrivate *key, - GDestroyNotify notify) +g_rw_lock_clear (GRWLock *lock) { - GPrivateDestructor *destructor; - - key->index = TlsAlloc (); - - destructor = malloc (sizeof (GPrivateDestructor)); - if G_UNLIKELY (destructor == NULL) - g_thread_abort (errno, "malloc"); - destructor->index = key->index; - destructor->notify = notify; - - do - destructor->next = g_private_destructors; - while (InterlockedCompareExchangePointer (&g_private_destructors, destructor->next, destructor) != destructor->next); - - key->ready = TRUE; } -gpointer -g_private_get (GPrivate *key) +void +g_rw_lock_writer_lock (GRWLock *lock) { - if (!key->ready) - return key->single_value; + AcquireSRWLockExclusive ((gpointer) lock); +} - return TlsGetValue (key->index); +gboolean +g_rw_lock_writer_trylock (GRWLock *lock) +{ + return TryAcquireSRWLockExclusive ((gpointer) lock); } void -g_private_set (GPrivate *key, - gpointer value) +g_rw_lock_writer_unlock (GRWLock *lock) { - if (!key->ready) - { - key->single_value = value; - return; - } - - TlsSetValue (key->index, value); + ReleaseSRWLockExclusive ((gpointer) lock); } -/* {{{1 GThread */ - -#include "glib.h" -#include "gthreadprivate.h" - -#define win32_check_for_error(what) G_STMT_START{ \ - if (!(what)) \ - g_error ("file %s: line %d (%s): error %s during %s", \ - __FILE__, __LINE__, G_STRFUNC, \ - g_win32_error_message (GetLastError ()), #what); \ - }G_STMT_END - -#define G_MUTEX_SIZE (sizeof (gpointer)) - -static DWORD g_thread_self_tls; -static DWORD g_private_tls; - -typedef BOOL (__stdcall *GTryEnterCriticalSectionFunc) (CRITICAL_SECTION *); +void +g_rw_lock_reader_lock (GRWLock *lock) +{ + AcquireSRWLockShared ((gpointer) lock); +} -typedef struct _GThreadData GThreadData; -struct _GThreadData +gboolean +g_rw_lock_reader_trylock (GRWLock *lock) { - GThreadFunc func; - gpointer data; - HANDLE thread; - gboolean joinable; -}; + return TryAcquireSRWLockShared ((gpointer) lock); +} void -g_system_thread_self (gpointer thread) +g_rw_lock_reader_unlock (GRWLock *lock) { - GThreadData *self = TlsGetValue (g_thread_self_tls); - - if (!self) - { - /* This should only happen for the main thread! */ - HANDLE handle = GetCurrentThread (); - HANDLE process = GetCurrentProcess (); - self = g_new (GThreadData, 1); - win32_check_for_error (DuplicateHandle (process, handle, process, - &self->thread, 0, FALSE, - DUPLICATE_SAME_ACCESS)); - win32_check_for_error (TlsSetValue (g_thread_self_tls, self)); - self->func = NULL; - self->data = NULL; - self->joinable = FALSE; - } - - *(GThreadData **)thread = self; + ReleaseSRWLockShared ((gpointer) lock); } +/* {{{1 GCond */ void -g_system_thread_exit (void) +g_cond_init (GCond *cond) { - GThreadData *self = TlsGetValue (g_thread_self_tls); - gboolean dtors_called; - - do - { - GPrivateDestructor *dtor; - - /* We go by the POSIX book on this one. - * - * If we call a destructor then there is a chance that some new - * TLS variables got set by code called in that destructor. - * - * Loop until nothing is left. - */ - dtors_called = FALSE; - - for (dtor = g_private_destructors; dtor; dtor = dtor->next) - { - gpointer value; - - value = TlsGetValue (dtor->index); - if (value != NULL && dtor->notify != NULL) - { - /* POSIX says to clear this before the call */ - TlsSetValue (dtor->index, NULL); - dtor->notify (value); - dtors_called = TRUE; - } - } - } - while (dtors_called); - - if (self) - { - if (!self->joinable) - { - win32_check_for_error (CloseHandle (self->thread)); - g_free (self); - } - win32_check_for_error (TlsSetValue (g_thread_self_tls, NULL)); - } - - if (g_thread_impl_vtable.CallThisOnThreadExit) - g_thread_impl_vtable.CallThisOnThreadExit (); - - _endthreadex (0); + InitializeConditionVariable ((gpointer) cond); } -static guint __stdcall -g_thread_proxy (gpointer data) +void +g_cond_clear (GCond *cond) { - GThreadData *self = (GThreadData*) data; - - win32_check_for_error (TlsSetValue (g_thread_self_tls, self)); - - self->func (self->data); - - g_system_thread_exit (); - - g_assert_not_reached (); - - return 0; } void -g_system_thread_create (GThreadFunc func, - gpointer data, - gulong stack_size, - gboolean joinable, - gpointer thread, - GError **error) +g_cond_signal (GCond *cond) { - guint ignore; - GThreadData *retval; - - g_return_if_fail (func); - - retval = g_new(GThreadData, 1); - retval->func = func; - retval->data = data; - - retval->joinable = joinable; - - retval->thread = (HANDLE) _beginthreadex (NULL, stack_size, g_thread_proxy, - retval, 0, &ignore); - - if (retval->thread == NULL) - { - gchar *win_error = g_win32_error_message (GetLastError ()); - g_set_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, - "Error creating thread: %s", win_error); - g_free (retval); - g_free (win_error); - return; - } - - *(GThreadData **)thread = retval; + WakeConditionVariable ((gpointer) cond); } void -g_thread_yield (void) +g_cond_broadcast (GCond *cond) { - Sleep(0); + WakeAllConditionVariable ((gpointer) cond); } void -g_system_thread_join (gpointer thread) +g_cond_wait (GCond *cond, + GMutex *entered_mutex) { - GThreadData *target = *(GThreadData **)thread; - - g_return_if_fail (target->joinable); - - win32_check_for_error (WAIT_FAILED != - WaitForSingleObject (target->thread, INFINITE)); - - win32_check_for_error (CloseHandle (target->thread)); - g_free (target); + SleepConditionVariableSRW ((gpointer) cond, (gpointer) entered_mutex, INFINITE, 0); } gboolean -g_system_thread_equal (gpointer thread1, - gpointer thread2) +g_cond_wait_until (GCond *cond, + GMutex *entered_mutex, + gint64 end_time) { - return ((GSystemThread*)thread1)->dummy_pointer == ((GSystemThread*)thread2)->dummy_pointer; + gint64 span, start_time; + DWORD span_millis; + gboolean signalled; + + start_time = g_get_monotonic_time (); + do + { + span = end_time - start_time; + + if G_UNLIKELY (span < 0) + span_millis = 0; + else if G_UNLIKELY (span > G_GINT64_CONSTANT (1000) * (DWORD) INFINITE) + span_millis = INFINITE; + else + /* Round up so we don't time out too early */ + span_millis = (span + 1000 - 1) / 1000; + + /* We never want to wait infinitely */ + if (span_millis >= INFINITE) + span_millis = INFINITE - 1; + + signalled = SleepConditionVariableSRW ((gpointer) cond, (gpointer) entered_mutex, span_millis, 0); + if (signalled) + break; + + /* In case we didn't wait long enough after a timeout, wait again for the + * remaining time */ + start_time = g_get_monotonic_time (); + } + while (start_time < end_time); + + return signalled; } -/* {{{1 SRWLock and CONDITION_VARIABLE emulation (for Windows XP) */ +/* {{{1 GPrivate */ -static CRITICAL_SECTION g_thread_xp_lock; -static DWORD g_thread_xp_waiter_tls; +typedef struct _GPrivateDestructor GPrivateDestructor; -/* {{{2 GThreadWaiter utility class for CONDITION_VARIABLE emulation */ -typedef struct _GThreadXpWaiter GThreadXpWaiter; -struct _GThreadXpWaiter +struct _GPrivateDestructor { - HANDLE event; - volatile GThreadXpWaiter *next; + DWORD index; + GDestroyNotify notify; + GPrivateDestructor *next; }; -static GThreadXpWaiter * -g_thread_xp_waiter_get (void) -{ - GThreadXpWaiter *waiter; +static GPrivateDestructor *g_private_destructors; /* (atomic) prepend-only */ +static CRITICAL_SECTION g_private_lock; - waiter = TlsGetValue (g_thread_xp_waiter_tls); +static DWORD +g_private_get_impl (GPrivate *key) +{ + DWORD impl = (DWORD) GPOINTER_TO_UINT(key->p); - if G_UNLIKELY (waiter == NULL) + if G_UNLIKELY (impl == 0) { - waiter = malloc (sizeof (GThreadXpWaiter)); - if (waiter == NULL) - g_thread_abort (GetLastError (), "malloc"); - waiter->event = CreateEvent (0, FALSE, FALSE, NULL); - if (waiter->event == NULL) - g_thread_abort (GetLastError (), "CreateEvent"); - - TlsSetValue (g_thread_xp_waiter_tls, waiter); - } + EnterCriticalSection (&g_private_lock); + impl = (UINT_PTR) key->p; + if (impl == 0) + { + GPrivateDestructor *destructor; - return waiter; -} + impl = TlsAlloc (); -static void __stdcall -g_thread_xp_CallThisOnThreadExit (void) -{ - GThreadXpWaiter *waiter; + if G_UNLIKELY (impl == 0) + { + /* Ignore TLS index 0 temporarily (as 0 is the indicator that we + * haven't allocated TLS yet) and alloc again; + * See https://gitlab.gnome.org/GNOME/glib/-/issues/2058 */ + DWORD impl2 = TlsAlloc (); + TlsFree (impl); + impl = impl2; + } - waiter = TlsGetValue (g_thread_xp_waiter_tls); + if (impl == TLS_OUT_OF_INDEXES || impl == 0) + g_thread_abort (0, "TlsAlloc"); - if (waiter != NULL) - { - TlsSetValue (g_thread_xp_waiter_tls, NULL); - CloseHandle (waiter->event); - free (waiter); + if (key->notify != NULL) + { + destructor = malloc (sizeof (GPrivateDestructor)); + if G_UNLIKELY (destructor == NULL) + g_thread_abort (errno, "malloc"); + destructor->index = impl; + destructor->notify = key->notify; + destructor->next = g_atomic_pointer_get (&g_private_destructors); + + /* We need to do an atomic store due to the unlocked + * access to the destructor list from the thread exit + * function. + * + * It can double as a sanity check... + */ + if (!g_atomic_pointer_compare_and_exchange (&g_private_destructors, + destructor->next, + destructor)) + g_thread_abort (0, "g_private_get_impl(1)"); + } + + /* Ditto, due to the unlocked access on the fast path */ + if (!g_atomic_pointer_compare_and_exchange (&key->p, NULL, GUINT_TO_POINTER (impl))) + g_thread_abort (0, "g_private_get_impl(2)"); + } + LeaveCriticalSection (&g_private_lock); } + + return impl; } -/* {{{2 SRWLock emulation */ -typedef struct +gpointer +g_private_get (GPrivate *key) { - CRITICAL_SECTION writer_lock; - gboolean ever_shared; /* protected by writer_lock */ - - /* below is only ever touched if ever_shared becomes true */ - CRITICAL_SECTION atomicity; - GThreadXpWaiter *queued_writer; /* protected by atomicity lock */ - gint num_readers; /* protected by atomicity lock */ -} GThreadSRWLock; + return TlsGetValue (g_private_get_impl (key)); +} -static void __stdcall -g_thread_xp_InitializeSRWLock (gpointer mutex) +void +g_private_set (GPrivate *key, + gpointer value) { - *(GThreadSRWLock * volatile *) mutex = NULL; + TlsSetValue (g_private_get_impl (key), value); } -static void __stdcall -g_thread_xp_DeleteSRWLock (gpointer mutex) +void +g_private_replace (GPrivate *key, + gpointer value) { - GThreadSRWLock *lock = *(GThreadSRWLock * volatile *) mutex; - - if (lock) - { - if (lock->ever_shared) - DeleteCriticalSection (&lock->atomicity); + DWORD impl = g_private_get_impl (key); + gpointer old; - DeleteCriticalSection (&lock->writer_lock); - free (lock); - } + old = TlsGetValue (impl); + TlsSetValue (impl, value); + if (old && key->notify) + key->notify (old); } -static GThreadSRWLock * __stdcall -g_thread_xp_get_srwlock (GThreadSRWLock * volatile *lock) -{ - GThreadSRWLock *result; +/* {{{1 GThread */ - /* It looks like we're missing some barriers here, but this code only - * ever runs on Windows XP, which in turn only ever runs on hardware - * with a relatively rigid memory model. The 'volatile' will take - * care of the compiler. - */ - result = *lock; +#define win32_check_for_error(what) G_STMT_START{ \ + if (!(what)) \ + g_error ("file %s: line %d (%s): error %s during %s", \ + __FILE__, __LINE__, G_STRFUNC, \ + g_win32_error_message (GetLastError ()), #what); \ + }G_STMT_END - if G_UNLIKELY (result == NULL) - { - EnterCriticalSection (&g_thread_xp_lock); +#define G_MUTEX_SIZE (sizeof (gpointer)) - result = malloc (sizeof (GThreadSRWLock)); +typedef BOOL (__stdcall *GTryEnterCriticalSectionFunc) (CRITICAL_SECTION *); - if (result == NULL) - g_thread_abort (errno, "malloc"); +typedef struct +{ + GRealThread thread; - InitializeCriticalSection (&result->writer_lock); - result->ever_shared = FALSE; - *lock = result; + GThreadFunc proxy; + HANDLE handle; +} GThreadWin32; - LeaveCriticalSection (&g_thread_xp_lock); - } +void +g_system_thread_free (GRealThread *thread) +{ + GThreadWin32 *wt = (GThreadWin32 *) thread; - return result; + win32_check_for_error (CloseHandle (wt->handle)); + g_slice_free (GThreadWin32, wt); } -static void __stdcall -g_thread_xp_AcquireSRWLockExclusive (gpointer mutex) +void +g_system_thread_exit (void) { - GThreadSRWLock *lock = g_thread_xp_get_srwlock (mutex); - - EnterCriticalSection (&lock->writer_lock); - - if (lock->ever_shared) - { - GThreadXpWaiter *waiter = NULL; - - EnterCriticalSection (&lock->atomicity); - if (lock->num_readers > 0) - lock->queued_writer = waiter = g_thread_xp_waiter_get (); - LeaveCriticalSection (&lock->atomicity); - - if (waiter != NULL) - WaitForSingleObject (waiter->event, INFINITE); + /* In static compilation, DllMain doesn't exist and so DLL_THREAD_DETACH + * case is never called and thread destroy notifications are not triggered. + * To ensure that notifications are correctly triggered in static + * compilation mode, we call directly the "detach" function here right + * before terminating the thread. + * As all win32 threads initialized through the glib API are run through + * the same proxy function g_thread_win32_proxy() which calls systematically + * g_system_thread_exit() when finishing, we obtain the same behavior as + * with dynamic compilation. + * + * WARNING: unfortunately this mechanism cannot work with threads created + * directly from the Windows API using CreateThread() or _beginthread/ex(). + * It only works with threads created by using the glib API with + * g_system_thread_new(). If users need absolutely to use a thread NOT + * created with glib API under Windows and in static compilation mode, they + * should not use glib functions within their thread or they may encounter + * memory leaks when the thread finishes. + */ +#ifdef GLIB_STATIC_COMPILATION + g_thread_win32_thread_detach (); +#endif - lock->queued_writer = NULL; - } + _endthreadex (0); } -static BOOLEAN __stdcall -g_thread_xp_TryAcquireSRWLockExclusive (gpointer mutex) +static guint __stdcall +g_thread_win32_proxy (gpointer data) { - GThreadSRWLock *lock = g_thread_xp_get_srwlock (mutex); - - if (!TryEnterCriticalSection (&lock->writer_lock)) - return FALSE; + GThreadWin32 *self = data; - if (lock->ever_shared) - { - gboolean available; + self->proxy (self); - EnterCriticalSection (&lock->atomicity); - available = lock->num_readers == 0; - LeaveCriticalSection (&lock->atomicity); + g_system_thread_exit (); - if (!available) - { - LeaveCriticalSection (&lock->writer_lock); - return FALSE; - } - } + g_assert_not_reached (); - return TRUE; + return 0; } -static void __stdcall -g_thread_xp_ReleaseSRWLockExclusive (gpointer mutex) +GRealThread * +g_system_thread_new (GThreadFunc proxy, + gulong stack_size, + const char *name, + GThreadFunc func, + gpointer data, + GError **error) { - GThreadSRWLock *lock = *(GThreadSRWLock * volatile *) mutex; + GThreadWin32 *thread; + GRealThread *base_thread; + guint ignore; + const gchar *message = NULL; + int thread_prio; + + thread = g_slice_new0 (GThreadWin32); + thread->proxy = proxy; + thread->handle = (HANDLE) NULL; + base_thread = (GRealThread*)thread; + base_thread->ref_count = 2; + base_thread->ours = TRUE; + base_thread->thread.joinable = TRUE; + base_thread->thread.func = func; + base_thread->thread.data = data; + base_thread->name = g_strdup (name); + + thread->handle = (HANDLE) _beginthreadex (NULL, stack_size, g_thread_win32_proxy, thread, + CREATE_SUSPENDED, &ignore); + + if (thread->handle == NULL) + { + message = "Error creating thread"; + goto error; + } - /* We need this until we fix some weird parts of GLib that try to - * unlock freshly-allocated mutexes. + /* For thread priority inheritance we need to manually set the thread + * priority of the new thread to the priority of the current thread. We + * also have to start the thread suspended and resume it after actually + * setting the priority here. + * + * On Windows, by default all new threads are created with NORMAL thread + * priority. */ - if (lock != NULL) - LeaveCriticalSection (&lock->writer_lock); -} + { + HANDLE current_thread = GetCurrentThread (); + thread_prio = GetThreadPriority (current_thread); + } -static void -g_thread_xp_srwlock_become_reader (GThreadSRWLock *lock) -{ - if G_UNLIKELY (!lock->ever_shared) + if (thread_prio == THREAD_PRIORITY_ERROR_RETURN) { - InitializeCriticalSection (&lock->atomicity); - lock->queued_writer = NULL; - lock->num_readers = 0; - - lock->ever_shared = TRUE; + message = "Error getting current thread priority"; + goto error; } - EnterCriticalSection (&lock->atomicity); - lock->num_readers++; - LeaveCriticalSection (&lock->atomicity); -} - -static void __stdcall -g_thread_xp_AcquireSRWLockShared (gpointer mutex) -{ - GThreadSRWLock *lock = g_thread_xp_get_srwlock (mutex); + if (SetThreadPriority (thread->handle, thread_prio) == 0) + { + message = "Error setting new thread priority"; + goto error; + } - EnterCriticalSection (&lock->writer_lock); + if (ResumeThread (thread->handle) == (DWORD) -1) + { + message = "Error resuming new thread"; + goto error; + } - g_thread_xp_srwlock_become_reader (lock); + return (GRealThread *) thread; - LeaveCriticalSection (&lock->writer_lock); +error: + { + gchar *win_error = g_win32_error_message (GetLastError ()); + g_set_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, + "%s: %s", message, win_error); + g_free (win_error); + if (thread->handle) + CloseHandle (thread->handle); + g_slice_free (GThreadWin32, thread); + return NULL; + } } -static BOOLEAN __stdcall -g_thread_xp_TryAcquireSRWLockShared (gpointer mutex) +void +g_thread_yield (void) { - GThreadSRWLock *lock = g_thread_xp_get_srwlock (mutex); - - if (!TryEnterCriticalSection (&lock->writer_lock)) - return FALSE; - - g_thread_xp_srwlock_become_reader (lock); - - LeaveCriticalSection (&lock->writer_lock); - - return TRUE; + Sleep(0); } -static void __stdcall -g_thread_xp_ReleaseSRWLockShared (gpointer mutex) +void +g_system_thread_wait (GRealThread *thread) { - GThreadSRWLock *lock = g_thread_xp_get_srwlock (mutex); - - EnterCriticalSection (&lock->atomicity); + GThreadWin32 *wt = (GThreadWin32 *) thread; - lock->num_readers--; - - if (lock->num_readers == 0 && lock->queued_writer) - SetEvent (lock->queued_writer->event); - - LeaveCriticalSection (&lock->atomicity); + win32_check_for_error (WAIT_FAILED != WaitForSingleObject (wt->handle, INFINITE)); } -/* {{{2 CONDITION_VARIABLE emulation */ -typedef struct -{ - volatile GThreadXpWaiter *first; - volatile GThreadXpWaiter **last_ptr; -} GThreadXpCONDITION_VARIABLE; +#define EXCEPTION_SET_THREAD_NAME ((DWORD) 0x406D1388) -static void __stdcall -g_thread_xp_InitializeConditionVariable (gpointer cond) -{ - *(GThreadXpCONDITION_VARIABLE * volatile *) cond = NULL; -} +#ifndef _MSC_VER +static void *SetThreadName_VEH_handle = NULL; -static void __stdcall -g_thread_xp_DeleteConditionVariable (gpointer cond) +static LONG __stdcall +SetThreadName_VEH (PEXCEPTION_POINTERS ExceptionInfo) { - GThreadXpCONDITION_VARIABLE *cv = *(GThreadXpCONDITION_VARIABLE * volatile *) cond; + if (ExceptionInfo->ExceptionRecord != NULL && + ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SET_THREAD_NAME) + return EXCEPTION_CONTINUE_EXECUTION; - if (cv) - free (cv); + return EXCEPTION_CONTINUE_SEARCH; } +#endif -static GThreadXpCONDITION_VARIABLE * __stdcall -g_thread_xp_get_condition_variable (GThreadXpCONDITION_VARIABLE * volatile *cond) +typedef struct _THREADNAME_INFO { - GThreadXpCONDITION_VARIABLE *result; + 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; - /* It looks like we're missing some barriers here, but this code only - * ever runs on Windows XP, which in turn only ever runs on hardware - * with a relatively rigid memory model. The 'volatile' will take - * care of the compiler. - */ - result = *cond; +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 (DWORD); + +#ifdef _MSC_VER + __try + { + RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize, + (const 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 ((!IsDebuggerPresent ()) && (SetThreadName_VEH_handle == NULL)) + return; + + RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize, (const ULONG_PTR *) &info); +#endif +} + +typedef HRESULT (WINAPI *pSetThreadDescription) (HANDLE hThread, + PCWSTR lpThreadDescription); +static pSetThreadDescription SetThreadDescriptionFunc = NULL; +static HMODULE kernel32_module = NULL; - if G_UNLIKELY (result == NULL) +static gboolean +g_thread_win32_load_library (void) +{ + /* FIXME: Add support for UWP app */ +#if !defined(G_WINAPI_ONLY_APP) + static gsize _init_once = 0; + if (g_once_init_enter (&_init_once)) { - result = malloc (sizeof (GThreadXpCONDITION_VARIABLE)); - - if (result == NULL) - g_thread_abort (errno, "malloc"); - - result->first = NULL; - result->last_ptr = &result->first; - - if (InterlockedCompareExchangePointer (cond, result, NULL) != NULL) + kernel32_module = LoadLibraryW (L"kernel32.dll"); + if (kernel32_module) { - free (result); - result = *cond; + SetThreadDescriptionFunc = + (pSetThreadDescription) GetProcAddress (kernel32_module, + "SetThreadDescription"); + if (!SetThreadDescriptionFunc) + FreeLibrary (kernel32_module); } + g_once_init_leave (&_init_once, 1); } +#endif - return result; + return !!SetThreadDescriptionFunc; } -static BOOL __stdcall -g_thread_xp_SleepConditionVariableSRW (gpointer cond, - gpointer mutex, - DWORD timeout, - ULONG flags) +static gboolean +g_thread_win32_set_thread_desc (const gchar *name) { - GThreadXpCONDITION_VARIABLE *cv = g_thread_xp_get_condition_variable (cond); - GThreadXpWaiter *waiter = g_thread_xp_waiter_get (); - DWORD status; - - waiter->next = NULL; + HRESULT hr; + wchar_t *namew; - EnterCriticalSection (&g_thread_xp_lock); - *cv->last_ptr = waiter; - cv->last_ptr = &waiter->next; - LeaveCriticalSection (&g_thread_xp_lock); - - g_mutex_unlock (mutex); - status = WaitForSingleObject (waiter->event, timeout); + if (!g_thread_win32_load_library () || !name) + return FALSE; - if (status != WAIT_TIMEOUT && status != WAIT_OBJECT_0) - g_thread_abort (GetLastError (), "WaitForSingleObject"); + namew = g_utf8_to_utf16 (name, -1, NULL, NULL, NULL); + if (!namew) + return FALSE; - g_mutex_lock (mutex); + hr = SetThreadDescriptionFunc (GetCurrentThread (), namew); - return status == WAIT_OBJECT_0; + g_free (namew); + return SUCCEEDED (hr); } -static void __stdcall -g_thread_xp_WakeConditionVariable (gpointer cond) +void +g_system_thread_set_name (const gchar *name) { - GThreadXpCONDITION_VARIABLE *cv = g_thread_xp_get_condition_variable (cond); - volatile GThreadXpWaiter *waiter; - - EnterCriticalSection (&g_thread_xp_lock); - waiter = cv->first; - if (waiter != NULL) - { - cv->first = waiter->next; - if (cv->first == NULL) - cv->last_ptr = &cv->first; - } - LeaveCriticalSection (&g_thread_xp_lock); - - if (waiter != NULL) - SetEvent (waiter->event); + /* Prefer SetThreadDescription over exception based way if available, + * since thread description set by SetThreadDescription will be preserved + * in dump file */ + if (!g_thread_win32_set_thread_desc (name)) + SetThreadName ((DWORD) -1, name); } -static void __stdcall -g_thread_xp_WakeAllConditionVariable (gpointer cond) -{ - GThreadXpCONDITION_VARIABLE *cv = g_thread_xp_get_condition_variable (cond); - volatile GThreadXpWaiter *waiter; +/* {{{1 Epilogue */ - EnterCriticalSection (&g_thread_xp_lock); - waiter = cv->first; - cv->first = NULL; - cv->last_ptr = &cv->first; - LeaveCriticalSection (&g_thread_xp_lock); +void +g_thread_win32_init (void) +{ + InitializeCriticalSection (&g_private_lock); - while (waiter != NULL) +#ifndef _MSC_VER + SetThreadName_VEH_handle = AddVectoredExceptionHandler (1, &SetThreadName_VEH); + if (SetThreadName_VEH_handle == NULL) { - volatile GThreadXpWaiter *next; - - next = waiter->next; - SetEvent (waiter->event); - waiter = next; + /* This is bad, but what can we do? */ } +#endif } -/* {{{2 XP Setup */ -static void -g_thread_xp_init (void) -{ - static const GThreadImplVtable g_thread_xp_impl_vtable = { - g_thread_xp_CallThisOnThreadExit, - g_thread_xp_InitializeSRWLock, - g_thread_xp_DeleteSRWLock, - g_thread_xp_AcquireSRWLockExclusive, - g_thread_xp_TryAcquireSRWLockExclusive, - g_thread_xp_ReleaseSRWLockExclusive, - g_thread_xp_AcquireSRWLockShared, - g_thread_xp_TryAcquireSRWLockShared, - g_thread_xp_ReleaseSRWLockShared, - g_thread_xp_InitializeConditionVariable, - g_thread_xp_DeleteConditionVariable, - g_thread_xp_SleepConditionVariableSRW, - g_thread_xp_WakeAllConditionVariable, - g_thread_xp_WakeConditionVariable - }; - - InitializeCriticalSection (&g_thread_xp_lock); - g_thread_xp_waiter_tls = TlsAlloc (); - - g_thread_impl_vtable = g_thread_xp_impl_vtable; -} - -/* {{{1 Epilogue */ - -static gboolean -g_thread_lookup_native_funcs (void) +void +g_thread_win32_thread_detach (void) { - GThreadImplVtable native_vtable = { 0, }; - HMODULE kernel32; - - kernel32 = GetModuleHandle ("KERNEL32.DLL"); - - if (kernel32 == NULL) - return FALSE; + gboolean dtors_called; -#define GET_FUNC(name) if ((native_vtable.name = (void *) GetProcAddress (kernel32, #name)) == NULL) return FALSE - GET_FUNC(InitializeSRWLock); - GET_FUNC(AcquireSRWLockExclusive); - GET_FUNC(TryAcquireSRWLockExclusive); - GET_FUNC(ReleaseSRWLockExclusive); - GET_FUNC(AcquireSRWLockShared); - GET_FUNC(TryAcquireSRWLockShared); - GET_FUNC(ReleaseSRWLockShared); + do + { + GPrivateDestructor *dtor; - GET_FUNC(InitializeConditionVariable); - GET_FUNC(SleepConditionVariableSRW); - GET_FUNC(WakeAllConditionVariable); - GET_FUNC(WakeConditionVariable); -#undef GET_FUNC + /* We go by the POSIX book on this one. + * + * If we call a destructor then there is a chance that some new + * TLS variables got set by code called in that destructor. + * + * Loop until nothing is left. + */ + dtors_called = FALSE; - g_thread_impl_vtable = native_vtable; + for (dtor = g_atomic_pointer_get (&g_private_destructors); dtor; dtor = dtor->next) + { + gpointer value; - return TRUE; + value = TlsGetValue (dtor->index); + if (value != NULL && dtor->notify != NULL) + { + /* POSIX says to clear this before the call */ + TlsSetValue (dtor->index, NULL); + dtor->notify (value); + dtors_called = TRUE; + } + } + } + while (dtors_called); } -G_GNUC_INTERNAL void -g_thread_DllMain (void) +void +g_thread_win32_process_detach (void) { - if (g_thread_lookup_native_funcs ()) - fprintf (stderr, "(debug) GThread using native mode\n"); - else +#ifndef _MSC_VER + if (SetThreadName_VEH_handle != NULL) { - fprintf (stderr, "(debug) GThread using Windows XP mode\n"); - g_thread_xp_init (); + RemoveVectoredExceptionHandler (SetThreadName_VEH_handle); + SetThreadName_VEH_handle = NULL; } - - win32_check_for_error (TLS_OUT_OF_INDEXES != (g_thread_self_tls = TlsAlloc ())); - win32_check_for_error (TLS_OUT_OF_INDEXES != (g_private_tls = TlsAlloc ())); +#endif } /* vim:set foldmethod=marker: */ -