diff options
Diffstat (limited to 'libjava/win32-threads.cc')
-rw-r--r-- | libjava/win32-threads.cc | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/libjava/win32-threads.cc b/libjava/win32-threads.cc new file mode 100644 index 000000000..f2dbaaf94 --- /dev/null +++ b/libjava/win32-threads.cc @@ -0,0 +1,567 @@ +// win32-threads.cc - interface between libjava and Win32 threads. + +/* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2006 Free Software + Foundation, Inc. + + This file is part of libgcj. + +This software is copyrighted work licensed under the terms of the +Libgcj License. Please consult the file "LIBGCJ_LICENSE" for +details. */ + +#include <config.h> + +// If we're using the Boehm GC, then we need to override some of the +// thread primitives. This is fairly gross. +#ifdef HAVE_BOEHM_GC +extern "C" +{ +#include <gc.h> +// <windows.h> #define's STRICT, which conflicts with Modifier.h +#undef STRICT +}; +#endif /* HAVE_BOEHM_GC */ + +#include <gcj/cni.h> +#include <jvm.h> +#include <java/lang/Thread.h> +#include <java/lang/System.h> + +#include <errno.h> + +#ifndef ETIMEDOUT +#define ETIMEDOUT 116 +#endif + +// This is used to implement thread startup. +struct starter +{ + _Jv_ThreadStartFunc *method; + _Jv_Thread_t *data; +}; + +// Controls access to the variable below +static HANDLE daemon_mutex; +static HANDLE daemon_cond; +// Number of non-daemon threads - _Jv_ThreadWait returns when this is 0 +static int non_daemon_count; + +// TLS key get Java object representing the thread +DWORD _Jv_ThreadKey; +// TLS key to get _Jv_Thread_t* representing the thread +DWORD _Jv_ThreadDataKey; + +// +// These are the flags that can appear in _Jv_Thread_t. +// + +// Thread started. +#define FLAG_START 0x01 +// Thread is daemon. +#define FLAG_DAEMON 0x02 + +// +// Helper +// +inline bool +compare_and_exchange(LONG volatile* dest, LONG cmp, LONG xchg) +{ + return InterlockedCompareExchange((LONG*) dest, xchg, cmp) == cmp; + // Seems like a bug in the MinGW headers that we have to do this cast. +} + +// +// Condition variables. +// + +// we do lazy creation of Events since CreateEvent() is insanely +// expensive, and because the rest of libgcj will call _Jv_CondInit +// when only a mutex is needed. + +inline void +ensure_condvar_initialized(_Jv_ConditionVariable_t *cv) +{ + if (cv->ev[0] == 0) + { + cv->ev[0] = CreateEvent (NULL, 0, 0, NULL); + if (cv->ev[0] == 0) JvFail("CreateEvent() failed"); + + cv->ev[1] = CreateEvent (NULL, 1, 0, NULL); + if (cv->ev[1] == 0) JvFail("CreateEvent() failed"); + } +} + +inline void +ensure_interrupt_event_initialized(HANDLE& rhEvent) +{ + if (!rhEvent) + { + rhEvent = CreateEvent (NULL, 0, 0, NULL); + if (!rhEvent) JvFail("CreateEvent() failed"); + } +} + +// Reimplementation of the general algorithm described at +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html (isomorphic to +// 3.2, not a cut-and-paste). + +int +_Jv_CondWait(_Jv_ConditionVariable_t *cv, _Jv_Mutex_t *mu, jlong millis, jint nanos) +{ + if (mu->owner != GetCurrentThreadId ( )) + return _JV_NOT_OWNER; + + _Jv_Thread_t *current = _Jv_ThreadCurrentData (); + java::lang::Thread *current_obj = _Jv_ThreadCurrent (); + + // Now that we hold the interrupt mutex, check if this thread has been + // interrupted already. + EnterCriticalSection (¤t->interrupt_mutex); + ensure_interrupt_event_initialized (current->interrupt_event); + jboolean interrupted = current_obj->interrupt_flag; + LeaveCriticalSection (¤t->interrupt_mutex); + + if (interrupted) + { + return _JV_INTERRUPTED; + } + + EnterCriticalSection (&cv->count_mutex); + ensure_condvar_initialized (cv); + cv->blocked_count++; + LeaveCriticalSection (&cv->count_mutex); + + DWORD time; + if ((millis == 0) && (nanos > 0)) time = 1; + else if (millis == 0) time = INFINITE; + else time = millis; + + // Record the current lock depth, so it can be restored + // when we reacquire it. + int count = mu->refcount; + int curcount = count; + + // Call _Jv_MutexUnlock repeatedly until this thread + // has completely released the monitor. + while (curcount > 0) + { + _Jv_MutexUnlock (mu); + --curcount; + } + + // Set up our array of three events: + // - the auto-reset event (for notify()) + // - the manual-reset event (for notifyAll()) + // - the interrupt event (for interrupt()) + // We wait for any one of these to be signaled. + HANDLE arh[3]; + arh[0] = cv->ev[0]; + arh[1] = cv->ev[1]; + arh[2] = current->interrupt_event; + DWORD rval = WaitForMultipleObjects (3, arh, 0, time); + + EnterCriticalSection (¤t->interrupt_mutex); + + // If we were unblocked by the third event (our thread's interrupt + // event), set the thread's interrupt flag. I think this sanity + // check guards against someone resetting our interrupt flag + // in the time between when interrupt_mutex is released in + // _Jv_ThreadInterrupt and the interval of time between the + // WaitForMultipleObjects call we just made and our acquisition + // of interrupt_mutex. + if (rval == (WAIT_OBJECT_0 + 2)) + current_obj->interrupt_flag = true; + + interrupted = current_obj->interrupt_flag; + LeaveCriticalSection (¤t->interrupt_mutex); + + EnterCriticalSection(&cv->count_mutex); + cv->blocked_count--; + // If we were unblocked by the second event (the broadcast one) + // and nobody is left, then reset the event. + int last_waiter = (rval == (WAIT_OBJECT_0 + 1)) && (cv->blocked_count == 0); + LeaveCriticalSection(&cv->count_mutex); + + if (last_waiter) + ResetEvent (cv->ev[1]); + + // Call _Jv_MutexLock repeatedly until the mutex's refcount is the + // same as before we originally released it. + while (curcount < count) + { + _Jv_MutexLock (mu); + ++curcount; + } + + return interrupted ? _JV_INTERRUPTED : 0; +} + +void +_Jv_CondInit (_Jv_ConditionVariable_t *cv) +{ + // we do lazy creation of Events since CreateEvent() is insanely expensive + cv->ev[0] = 0; + InitializeCriticalSection (&cv->count_mutex); + cv->blocked_count = 0; +} + +void +_Jv_CondDestroy (_Jv_ConditionVariable_t *cv) +{ + if (cv->ev[0] != 0) + { + CloseHandle (cv->ev[0]); + CloseHandle (cv->ev[1]); + + cv->ev[0] = 0; + } + + DeleteCriticalSection (&cv->count_mutex); +} + +int +_Jv_CondNotify (_Jv_ConditionVariable_t *cv, _Jv_Mutex_t *mu) +{ + if (mu->owner != GetCurrentThreadId ( )) + return _JV_NOT_OWNER; + + EnterCriticalSection (&cv->count_mutex); + ensure_condvar_initialized (cv); + int somebody_is_blocked = cv->blocked_count > 0; + LeaveCriticalSection (&cv->count_mutex); + + if (somebody_is_blocked) + SetEvent (cv->ev[0]); + + return 0; +} + +int +_Jv_CondNotifyAll (_Jv_ConditionVariable_t *cv, _Jv_Mutex_t *mu) +{ + if (mu->owner != GetCurrentThreadId ( )) + return _JV_NOT_OWNER; + + EnterCriticalSection (&cv->count_mutex); + ensure_condvar_initialized (cv); + int somebody_is_blocked = cv->blocked_count > 0; + LeaveCriticalSection (&cv->count_mutex); + + if (somebody_is_blocked) + SetEvent (cv->ev[1]); + + return 0; +} + +// +// Threads. +// + +void +_Jv_InitThreads (void) +{ + _Jv_ThreadKey = TlsAlloc(); + _Jv_ThreadDataKey = TlsAlloc(); + daemon_mutex = CreateMutex (NULL, 0, NULL); + daemon_cond = CreateEvent (NULL, 1, 0, NULL); + non_daemon_count = 0; +} + +_Jv_Thread_t * +_Jv_ThreadInitData (java::lang::Thread* obj) +{ + _Jv_Thread_t *data = (_Jv_Thread_t*)_Jv_Malloc(sizeof(_Jv_Thread_t)); + data->flags = 0; + data->handle = 0; + data->thread_obj = obj; + data->interrupt_event = 0; + InitializeCriticalSection (&data->interrupt_mutex); + + return data; +} + +void +_Jv_ThreadDestroyData (_Jv_Thread_t *data) +{ + DeleteCriticalSection (&data->interrupt_mutex); + if (data->interrupt_event) + CloseHandle(data->interrupt_event); + CloseHandle(data->handle); + _Jv_Free(data); +} + +void +_Jv_ThreadSetPriority (_Jv_Thread_t *data, jint prio) +{ + int actual = THREAD_PRIORITY_NORMAL; + + if (data->flags & FLAG_START) + { + switch (prio) + { + case 10: + actual = THREAD_PRIORITY_TIME_CRITICAL; + break; + case 9: + actual = THREAD_PRIORITY_HIGHEST; + break; + case 8: + case 7: + actual = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case 6: + case 5: + actual = THREAD_PRIORITY_NORMAL; + break; + case 4: + case 3: + actual = THREAD_PRIORITY_BELOW_NORMAL; + break; + case 2: + actual = THREAD_PRIORITY_LOWEST; + break; + case 1: + actual = THREAD_PRIORITY_IDLE; + break; + } + SetThreadPriority(data->handle, actual); + } +} + +void +_Jv_ThreadRegister (_Jv_Thread_t *data) +{ + TlsSetValue (_Jv_ThreadKey, data->thread_obj); + TlsSetValue (_Jv_ThreadDataKey, data); +} + +void +_Jv_ThreadUnRegister () +{ + TlsSetValue (_Jv_ThreadKey, NULL); + TlsSetValue (_Jv_ThreadDataKey, NULL); +} + +// This function is called when a thread is started. We don't arrange +// to call the `run' method directly, because this function must +// return a value. +static DWORD WINAPI +really_start (void* x) +{ + struct starter *info = (struct starter *) x; + + _Jv_ThreadRegister (info->data); + + info->method (info->data->thread_obj); + + if (! (info->data->flags & FLAG_DAEMON)) + { + WaitForSingleObject (daemon_mutex, INFINITE); + non_daemon_count--; + if (! non_daemon_count) + SetEvent (daemon_cond); + ReleaseMutex (daemon_mutex); + } + + return 0; +} + +void +_Jv_ThreadStart (java::lang::Thread *thread, _Jv_Thread_t *data, _Jv_ThreadStartFunc *meth) +{ + DWORD id; + struct starter *info; + + // Do nothing if thread has already started + if (data->flags & FLAG_START) + return; + data->flags |= FLAG_START; + + info = (struct starter *) _Jv_AllocBytes (sizeof (struct starter)); + info->method = meth; + info->data = data; + + if (! thread->isDaemon ()) + { + WaitForSingleObject (daemon_mutex, INFINITE); + non_daemon_count++; + ReleaseMutex (daemon_mutex); + } + else + data->flags |= FLAG_DAEMON; + + data->handle = GC_CreateThread(NULL, 0, really_start, info, 0, &id); + _Jv_ThreadSetPriority(data, thread->getPriority()); +} + +void +_Jv_ThreadWait (void) +{ + WaitForSingleObject (daemon_mutex, INFINITE); + if (non_daemon_count) + { + ReleaseMutex (daemon_mutex); + WaitForSingleObject (daemon_cond, INFINITE); + } +} + +// +// Interrupt support +// + +HANDLE +_Jv_Win32GetInterruptEvent (void) +{ + _Jv_Thread_t *current = _Jv_ThreadCurrentData (); + EnterCriticalSection (¤t->interrupt_mutex); + ensure_interrupt_event_initialized (current->interrupt_event); + HANDLE hEvent = current->interrupt_event; + LeaveCriticalSection (¤t->interrupt_mutex); + return hEvent; +} + +void +_Jv_ThreadInterrupt (_Jv_Thread_t *data) +{ + EnterCriticalSection (&data->interrupt_mutex); + ensure_interrupt_event_initialized (data->interrupt_event); + data->thread_obj->interrupt_flag = true; + SetEvent (data->interrupt_event); + LeaveCriticalSection (&data->interrupt_mutex); +} + +// park() / unpark() support + +void +ParkHelper::init () +{ + // We initialize our critical section, but not our event. + InitializeCriticalSection (&cs); + event = NULL; +} + +void +ParkHelper::init_event() +{ + EnterCriticalSection (&cs); + if (!event) + { + // Create an auto-reset event. + event = CreateEvent(NULL, 0, 0, NULL); + if (!event) JvFail("CreateEvent() failed"); + } + LeaveCriticalSection (&cs); +} + +void +ParkHelper::deactivate () +{ + permit = ::java::lang::Thread::THREAD_PARK_DEAD; +} + +void +ParkHelper::destroy() +{ + if (event) CloseHandle (event); + DeleteCriticalSection (&cs); +} + +/** + * Releases the block on a thread created by _Jv_ThreadPark(). This + * method can also be used to terminate a blockage caused by a prior + * call to park. This operation is unsafe, as the thread must be + * guaranteed to be live. + * + * @param thread the thread to unblock. + */ +void +ParkHelper::unpark () +{ + using namespace ::java::lang; + LONG volatile* ptr = &permit; + + // If this thread is in state RUNNING, give it a permit and return + // immediately. + if (compare_and_exchange + (ptr, Thread::THREAD_PARK_RUNNING, Thread::THREAD_PARK_PERMIT)) + return; + + // If this thread is parked, put it into state RUNNING and send it a + // signal. + if (compare_and_exchange + (ptr, Thread::THREAD_PARK_PARKED, Thread::THREAD_PARK_RUNNING)) + { + init_event (); + SetEvent (event); + } +} + +/** + * Blocks the thread until a matching _Jv_ThreadUnpark() occurs, the + * thread is interrupted or the optional timeout expires. If an + * unpark call has already occurred, this also counts. A timeout + * value of zero is defined as no timeout. When isAbsolute is true, + * the timeout is in milliseconds relative to the epoch. Otherwise, + * the value is the number of nanoseconds which must occur before + * timeout. This call may also return spuriously (i.e. for no + * apparent reason). + * + * @param isAbsolute true if the timeout is specified in milliseconds from + * the epoch. + * @param time either the number of nanoseconds to wait, or a time in + * milliseconds from the epoch to wait for. + */ +void +ParkHelper::park (jboolean isAbsolute, jlong time) +{ + using namespace ::java::lang; + LONG volatile* ptr = &permit; + + // If we have a permit, return immediately. + if (compare_and_exchange + (ptr, Thread::THREAD_PARK_PERMIT, Thread::THREAD_PARK_RUNNING)) + return; + + // Determine the number of milliseconds to wait. + jlong millis = 0, nanos = 0; + + if (time) + { + if (isAbsolute) + { + millis = time - ::java::lang::System::currentTimeMillis(); + nanos = 0; + } + else + { + millis = 0; + nanos = time; + } + + if (nanos) + { + millis += nanos / 1000000; + if (millis == 0) + millis = 1; + // ...otherwise, we'll block indefinitely. + } + } + + if (millis < 0) return; + // Can this ever happen? + + if (compare_and_exchange + (ptr, Thread::THREAD_PARK_RUNNING, Thread::THREAD_PARK_PARKED)) + { + init_event(); + + DWORD timeout = millis==0 ? INFINITE : (DWORD) millis; + WaitForSingleObject (event, timeout); + + // If we were unparked by some other thread, this will already + // be in state THREAD_PARK_RUNNING. If we timed out, we have to + // do it ourself. + compare_and_exchange + (ptr, Thread::THREAD_PARK_PARKED, Thread::THREAD_PARK_RUNNING); + } +} |