summaryrefslogtreecommitdiff
path: root/src/mutex/mut_win32.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mutex/mut_win32.c')
-rw-r--r--src/mutex/mut_win32.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/src/mutex/mut_win32.c b/src/mutex/mut_win32.c
new file mode 100644
index 00000000..07d5a8dd
--- /dev/null
+++ b/src/mutex/mut_win32.c
@@ -0,0 +1,589 @@
+/*
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 2002, 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * $Id$
+ */
+
+#include "db_config.h"
+
+#define LOAD_ACTUAL_MUTEX_CODE
+#include "db_int.h"
+
+#include "dbinc/atomic.h"
+/*
+ * This is where we load in the actual mutex declarations.
+ */
+#include "dbinc/mutex_int.h"
+
+/*
+ * Common code to get an event handle. This is executed whenever a mutex
+ * blocks, or when unlocking a mutex that a thread is waiting on. We can't
+ * keep these handles around, since the mutex structure is in shared memory,
+ * and each process gets its own handle value.
+ *
+ * We pass security attributes so that the created event is accessible by all
+ * users, in case a Windows service is sharing an environment with a local
+ * process run as a different user.
+ */
+static _TCHAR hex_digits[] = _T("0123456789abcdef");
+
+static __inline int get_handle(env, mutexp, eventp)
+ ENV *env;
+ DB_MUTEX *mutexp;
+ HANDLE *eventp;
+{
+ _TCHAR idbuf[] = _T("db.m00000000");
+ _TCHAR *p = idbuf + 12;
+ int ret = 0;
+ u_int32_t id;
+
+ for (id = (mutexp)->id; id != 0; id >>= 4)
+ *--p = hex_digits[id & 0xf];
+
+#ifndef DB_WINCE
+ if (DB_GLOBAL(win_sec_attr) == NULL) {
+ InitializeSecurityDescriptor(&DB_GLOBAL(win_default_sec_desc),
+ SECURITY_DESCRIPTOR_REVISION);
+ SetSecurityDescriptorDacl(&DB_GLOBAL(win_default_sec_desc),
+ TRUE, 0, FALSE);
+ DB_GLOBAL(win_default_sec_attr).nLength =
+ sizeof(SECURITY_ATTRIBUTES);
+ DB_GLOBAL(win_default_sec_attr).bInheritHandle = FALSE;
+ DB_GLOBAL(win_default_sec_attr).lpSecurityDescriptor =
+ &DB_GLOBAL(win_default_sec_desc);
+ DB_GLOBAL(win_sec_attr) = &DB_GLOBAL(win_default_sec_attr);
+ }
+#endif
+
+ if ((*eventp = CreateEvent(DB_GLOBAL(win_sec_attr),
+ FALSE, FALSE, idbuf)) == NULL) {
+ ret = __os_get_syserr();
+ __db_syserr(env, ret, DB_STR("2002",
+ "Win32 create event failed"));
+ }
+
+ return (ret);
+}
+
+/*
+ * __db_win32_mutex_lock_int
+ * Internal function to lock a win32 mutex
+ *
+ * If the wait parameter is 0, this function will return DB_LOCK_NOTGRANTED
+ * rather than wait.
+ *
+ */
+static __inline int
+__db_win32_mutex_lock_int(env, mutex, timeout, wait)
+ ENV *env;
+ db_mutex_t mutex;
+ db_timeout_t timeout;
+ int wait;
+{
+ DB_ENV *dbenv;
+ DB_MUTEX *mutexp;
+ DB_MUTEXMGR *mtxmgr;
+ DB_MUTEXREGION *mtxregion;
+ DB_THREAD_INFO *ip;
+ HANDLE event;
+ u_int32_t ms, nspins;
+ db_timespec now, tempspec, timeoutspec;
+ db_timeout_t time_left;
+ int ret;
+#ifdef MUTEX_DIAG
+ LARGE_INTEGER now;
+#endif
+ dbenv = env->dbenv;
+
+ if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING))
+ return (0);
+
+ mtxmgr = env->mutex_handle;
+ mtxregion = mtxmgr->reginfo.primary;
+ mutexp = MUTEXP_SET(env, mutex);
+
+ CHECK_MTX_THREAD(env, mutexp);
+
+ if (timeout != 0) {
+ timespecclear(&timeoutspec);
+ __clock_set_expires(env, &timeoutspec, timeout);
+ }
+
+ /*
+ * See WINCE_ATOMIC_MAGIC definition for details.
+ * Use sharecount, because the value just needs to be a db_atomic_t
+ * memory mapped onto the same page as those being Interlocked*.
+ */
+ WINCE_ATOMIC_MAGIC(&mutexp->sharecount);
+
+ event = NULL;
+ ms = 50;
+ ret = 0;
+
+ /*
+ * Only check the thread state once, by initializing the thread
+ * control block pointer to null. If it is not the failchk
+ * thread, then ip will have a valid value subsequent times
+ * in the loop.
+ */
+ ip = NULL;
+
+loop: /* Attempt to acquire the mutex mutex_tas_spins times, if waiting. */
+ for (nspins =
+ mtxregion->stat.st_mutex_tas_spins; nspins > 0; --nspins) {
+ /*
+ * We can avoid the (expensive) interlocked instructions if
+ * the mutex is already busy.
+ */
+ if (MUTEXP_IS_BUSY(mutexp) || !MUTEXP_ACQUIRE(mutexp)) {
+ if (F_ISSET(dbenv, DB_ENV_FAILCHK) &&
+ ip == NULL && dbenv->is_alive(dbenv,
+ mutexp->pid, mutexp->tid, 0) == 0) {
+ ret = __env_set_state(env, &ip, THREAD_VERIFY);
+ if (ret != 0 ||
+ ip->dbth_state == THREAD_FAILCHK)
+ return (DB_RUNRECOVERY);
+ }
+ if (!wait)
+ return (DB_LOCK_NOTGRANTED);
+ /*
+ * Some systems (notably those with newer Intel CPUs)
+ * need a small pause before retrying. [#6975]
+ */
+ MUTEX_PAUSE
+ continue;
+ }
+
+#ifdef DIAGNOSTIC
+ if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) {
+ char buf[DB_THREADID_STRLEN];
+ __db_errx(env, DB_STR_A("2003",
+ "Win32 lock failed: mutex already locked by %s",
+ "%s"), dbenv->thread_id_string(dbenv,
+ mutexp->pid, mutexp->tid, buf));
+ return (__env_panic(env, EACCES));
+ }
+#endif
+ F_SET(mutexp, DB_MUTEX_LOCKED);
+ dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid);
+
+#ifdef HAVE_STATISTICS
+ if (event == NULL)
+ ++mutexp->mutex_set_nowait;
+ else
+ ++mutexp->mutex_set_wait;
+#endif
+ if (event != NULL) {
+ CloseHandle(event);
+ InterlockedDecrement(&mutexp->nwaiters);
+#ifdef MUTEX_DIAG
+ if (ret != WAIT_OBJECT_0) {
+ QueryPerformanceCounter(&diag_now);
+ printf(DB_STR_A("2004",
+ "[%I64d]: Lost signal on mutex %p, "
+ "id %d, ms %d\n", "%I64d %p %d %d"),
+ diag_now.QuadPart, mutexp, mutexp->id, ms);
+ }
+#endif
+ }
+
+#ifdef DIAGNOSTIC
+ /*
+ * We want to switch threads as often as possible. Yield
+ * every time we get a mutex to ensure contention.
+ */
+ if (F_ISSET(dbenv, DB_ENV_YIELDCPU))
+ __os_yield(env, 0, 0);
+#endif
+
+ return (0);
+ }
+
+ /*
+ * Yield the processor; wait 50 ms initially, up to 1 second. This
+ * loop is needed to work around a race where the signal from the
+ * unlocking thread gets lost. We start at 50 ms because it's unlikely
+ * to happen often and we want to avoid wasting CPU.
+ */
+ if (timeout != 0) {
+ timespecclear(&now);
+ if (__clock_expired(env, &now, &timeoutspec)) {
+ if (event != NULL) {
+ CloseHandle(event);
+ InterlockedDecrement(&mutexp->nwaiters);
+ }
+ return (DB_TIMEOUT);
+ }
+ /* Reduce the event wait if the timeout would happen first. */
+ tempspec = timeoutspec;
+ timespecsub(&tempspec, &now);
+ DB_TIMESPEC_TO_TIMEOUT(time_left, &tempspec, 0);
+ time_left /= US_PER_MS;
+ if (ms > time_left)
+ ms = time_left;
+ }
+ if (event == NULL) {
+#ifdef MUTEX_DIAG
+ QueryPerformanceCounter(&diag_now);
+ printf(DB_STR_A("2005",
+ "[%I64d]: Waiting on mutex %p, id %d\n",
+ "%I64d %p %d"), diag_now.QuadPart, mutexp, mutexp->id);
+#endif
+ InterlockedIncrement(&mutexp->nwaiters);
+ if ((ret = get_handle(env, mutexp, &event)) != 0)
+ goto err;
+ }
+ if ((ret = WaitForSingleObject(event, ms)) == WAIT_FAILED) {
+ ret = __os_get_syserr();
+ goto err;
+ }
+ if ((ms <<= 1) > MS_PER_SEC)
+ ms = MS_PER_SEC;
+
+ PANIC_CHECK(env);
+ goto loop;
+
+err: __db_syserr(env, ret, DB_STR("2006", "Win32 lock failed"));
+ return (__env_panic(env, __os_posix_err(ret)));
+}
+
+/*
+ * __db_win32_mutex_init --
+ * Initialize a Win32 mutex.
+ *
+ * PUBLIC: int __db_win32_mutex_init __P((ENV *, db_mutex_t, u_int32_t));
+ */
+int
+__db_win32_mutex_init(env, mutex, flags)
+ ENV *env;
+ db_mutex_t mutex;
+ u_int32_t flags;
+{
+ DB_MUTEX *mutexp;
+
+ mutexp = MUTEXP_SET(env, mutex);
+ mutexp->id = ((getpid() & 0xffff) << 16) ^ P_TO_UINT32(mutexp);
+ F_SET(mutexp, flags);
+
+ return (0);
+}
+
+/*
+ * __db_win32_mutex_lock
+ * Lock on a mutex, blocking if necessary.
+ *
+ * PUBLIC: int __db_win32_mutex_lock __P((ENV *, db_mutex_t, db_timeout_t));
+ */
+int
+__db_win32_mutex_lock(env, mutex, timeout)
+ ENV *env;
+ db_mutex_t mutex;
+ db_timeout_t timeout;
+{
+ return (__db_win32_mutex_lock_int(env, mutex, timeout, 1));
+}
+
+/*
+ * __db_win32_mutex_trylock
+ * Try to lock a mutex, returning without waiting if it is busy
+ *
+ * PUBLIC: int __db_win32_mutex_trylock __P((ENV *, db_mutex_t));
+ */
+int
+__db_win32_mutex_trylock(env, mutex)
+ ENV *env;
+ db_mutex_t mutex;
+{
+ return (__db_win32_mutex_lock_int(env, mutex, 0));
+}
+
+#if defined(HAVE_SHARED_LATCHES)
+/*
+ * __db_win32_mutex_readlock_int
+ * Try to lock a mutex, possibly waiting if requested and necessary.
+ */
+int
+__db_win32_mutex_readlock_int(env, mutex, nowait)
+ ENV *env;
+ db_mutex_t mutex;
+ int nowait;
+{
+ DB_ENV *dbenv;
+ DB_MUTEX *mutexp;
+ DB_MUTEXMGR *mtxmgr;
+ DB_MUTEXREGION *mtxregion;
+ HANDLE event;
+ u_int32_t nspins;
+ int ms, ret;
+ long exch_ret, mtx_val;
+#ifdef MUTEX_DIAG
+ LARGE_INTEGER diag_now;
+#endif
+ dbenv = env->dbenv;
+
+ if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING))
+ return (0);
+
+ mtxmgr = env->mutex_handle;
+ mtxregion = mtxmgr->reginfo.primary;
+ mutexp = MUTEXP_SET(env, mutex);
+
+ CHECK_MTX_THREAD(env, mutexp);
+
+ /*
+ * See WINCE_ATOMIC_MAGIC definition for details.
+ * Use sharecount, because the value just needs to be a db_atomic_t
+ * memory mapped onto the same page as those being Interlocked*.
+ */
+ WINCE_ATOMIC_MAGIC(&mutexp->sharecount);
+
+ event = NULL;
+ ms = 50;
+ ret = 0;
+ /*
+ * This needs to be initialized, since if mutexp->tas
+ * is write locked on the first pass, it needs a value.
+ */
+ exch_ret = 0;
+
+loop: /* Attempt to acquire the resource for N spins. */
+ for (nspins =
+ mtxregion->stat.st_mutex_tas_spins; nspins > 0; --nspins) {
+ /*
+ * We can avoid the (expensive) interlocked instructions if
+ * the mutex is already "set".
+ */
+retry: mtx_val = atomic_read(&mutexp->sharecount);
+ if (mtx_val == MUTEX_SHARE_ISEXCLUSIVE) {
+ if (nowait)
+ return (DB_LOCK_NOTGRANTED);
+
+ continue;
+ } else if (!atomic_compare_exchange(env, &mutexp->sharecount,
+ mtx_val, mtx_val + 1)) {
+ /*
+ * Some systems (notably those with newer Intel CPUs)
+ * need a small pause here. [#6975]
+ */
+ MUTEX_PAUSE
+ goto retry;
+ }
+
+#ifdef HAVE_STATISTICS
+ if (event == NULL)
+ ++mutexp->mutex_set_rd_nowait;
+ else
+ ++mutexp->mutex_set_rd_wait;
+#endif
+ if (event != NULL) {
+ CloseHandle(event);
+ InterlockedDecrement(&mutexp->nwaiters);
+#ifdef MUTEX_DIAG
+ if (ret != WAIT_OBJECT_0) {
+ QueryPerformanceCounter(&diag_now);
+ printf(DB_STR_A("2007",
+ "[%I64d]: Lost signal on mutex %p, "
+ "id %d, ms %d\n", "%I64d %p %d %d"),
+ diag_now.QuadPart, mutexp, mutexp->id, ms);
+ }
+#endif
+ }
+
+#ifdef DIAGNOSTIC
+ /*
+ * We want to switch threads as often as possible. Yield
+ * every time we get a mutex to ensure contention.
+ */
+ if (F_ISSET(dbenv, DB_ENV_YIELDCPU))
+ __os_yield(env, 0, 0);
+#endif
+
+ return (0);
+ }
+
+ /*
+ * Yield the processor; wait 50 ms initially, up to 1 second. This
+ * loop is needed to work around a race where the signal from the
+ * unlocking thread gets lost. We start at 50 ms because it's unlikely
+ * to happen often and we want to avoid wasting CPU.
+ */
+ if (event == NULL) {
+#ifdef MUTEX_DIAG
+ QueryPerformanceCounter(&diag_now);
+ printf(DB_STR_A("2008",
+ "[%I64d]: Waiting on mutex %p, id %d\n",
+ "%I64d %p %d"), diag_now.QuadPart, mutexp, mutexp->id);
+#endif
+ InterlockedIncrement(&mutexp->nwaiters);
+ if ((ret = get_handle(env, mutexp, &event)) != 0)
+ goto err;
+ }
+ if ((ret = WaitForSingleObject(event, ms)) == WAIT_FAILED) {
+ ret = __os_get_syserr();
+ goto err;
+ }
+ if ((ms <<= 1) > MS_PER_SEC)
+ ms = MS_PER_SEC;
+
+ PANIC_CHECK(env);
+ goto loop;
+
+err: __db_syserr(env, ret, DB_STR("2009",
+ "Win32 read lock failed"));
+ return (__env_panic(env, __os_posix_err(ret)));
+}
+
+/*
+ * __db_win32_mutex_readlock
+ * Get a shared lock on a latch
+ *
+ * PUBLIC: #if defined(HAVE_SHARED_LATCHES)
+ * PUBLIC: int __db_win32_mutex_readlock __P((ENV *, db_mutex_t));
+ * PUBLIC: #endif
+ */
+int
+__db_win32_mutex_readlock(env, mutex)
+ ENV *env;
+ db_mutex_t mutex;
+{
+ return (__db_win32_mutex_readlock_int(env, mutex, 0));
+}
+
+/*
+ * __db_win32_mutex_tryreadlock
+ * Try to a shared lock on a latch
+ *
+ * PUBLIC: #if defined(HAVE_SHARED_LATCHES)
+ * PUBLIC: int __db_win32_mutex_tryreadlock __P((ENV *, db_mutex_t));
+ * PUBLIC: #endif
+ */
+int
+__db_win32_mutex_tryreadlock(env, mutex)
+ ENV *env;
+ db_mutex_t mutex;
+{
+ return (__db_win32_mutex_readlock_int(env, mutex, 1));
+}
+#endif
+
+/*
+ * __db_win32_mutex_unlock --
+ * Release a mutex.
+ *
+ * PUBLIC: int __db_win32_mutex_unlock __P((ENV *, db_mutex_t));
+ */
+int
+__db_win32_mutex_unlock(env, mutex)
+ ENV *env;
+ db_mutex_t mutex;
+{
+ DB_ENV *dbenv;
+ DB_MUTEX *mutexp;
+ HANDLE event;
+ int ret;
+#ifdef MUTEX_DIAG
+ LARGE_INTEGER diag_now;
+#endif
+ dbenv = env->dbenv;
+
+ if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING))
+ return (0);
+
+ mutexp = MUTEXP_SET(env, mutex);
+
+#ifdef DIAGNOSTIC
+ if (!MUTEXP_IS_BUSY(mutexp) || !(F_ISSET(mutexp, DB_MUTEX_SHARED) ||
+ F_ISSET(mutexp, DB_MUTEX_LOCKED))) {
+ __db_errx(env, DB_STR_A("2010",
+ "Win32 unlock failed: lock already unlocked: mutex %d busy %d",
+ "%d %d"), mutex, MUTEXP_BUSY_FIELD(mutexp));
+ return (__env_panic(env, EACCES));
+ }
+#endif
+ /*
+ * If we have a shared latch, and a read lock (DB_MUTEX_LOCKED is only
+ * set for write locks), then decrement the latch. If the readlock is
+ * still held by other threads, just return. Otherwise go ahead and
+ * notify any waiting threads.
+ */
+#ifdef HAVE_SHARED_LATCHES
+ if (F_ISSET(mutexp, DB_MUTEX_SHARED)) {
+ if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) {
+ F_CLR(mutexp, DB_MUTEX_LOCKED);
+ if ((ret = InterlockedExchange(
+ (interlocked_val)(&atomic_read(
+ &mutexp->sharecount)), 0)) !=
+ MUTEX_SHARE_ISEXCLUSIVE) {
+ ret = DB_RUNRECOVERY;
+ goto err;
+ }
+ } else if (InterlockedDecrement(
+ (interlocked_val)(&atomic_read(&mutexp->sharecount))) > 0)
+ return (0);
+ } else
+#endif
+ {
+ F_CLR(mutexp, DB_MUTEX_LOCKED);
+ MUTEX_UNSET(&mutexp->tas);
+ }
+
+ if (mutexp->nwaiters > 0) {
+ if ((ret = get_handle(env, mutexp, &event)) != 0)
+ goto err;
+
+#ifdef MUTEX_DIAG
+ QueryPerformanceCounter(&diag_now);
+ printf(DB_STR_A("2011",
+ "[%I64d]: Signalling mutex %p, id %d\n",
+ "%I64d %p %d"), diag_now.QuadPart, mutexp, mutexp->id);
+#endif
+ if (!PulseEvent(event)) {
+ ret = __os_get_syserr();
+ CloseHandle(event);
+ goto err;
+ }
+
+ CloseHandle(event);
+ }
+
+ return (0);
+
+err: __db_syserr(env, ret, DB_STR("2012", "Win32 unlock failed"));
+ return (__env_panic(env, __os_posix_err(ret)));
+}
+
+/*
+ * __db_win32_mutex_destroy --
+ * Destroy a mutex.
+ *
+ * PUBLIC: int __db_win32_mutex_destroy __P((ENV *, db_mutex_t));
+ */
+int
+__db_win32_mutex_destroy(env, mutex)
+ ENV *env;
+ db_mutex_t mutex;
+{
+ return (0);
+}
+
+#ifndef DB_WINCE
+/*
+ * db_env_set_win_security
+ *
+ * Set the SECURITY_ATTRIBUTES to be used by BDB on Windows.
+ * It should not be called while any BDB mutexes are locked.
+ *
+ * EXTERN: #if defined(DB_WIN32) && !defined(DB_WINCE)
+ * EXTERN: int db_env_set_win_security __P((SECURITY_ATTRIBUTES *sa));
+ * EXTERN: #endif
+ */
+int
+db_env_set_win_security(sa)
+ SECURITY_ATTRIBUTES *sa;
+{
+ DB_GLOBAL(win_sec_attr) = sa;
+ return (0);
+}
+#endif