diff options
author | Jesse Morgan <jesse@jesterpm.net> | 2016-12-17 21:28:53 -0800 |
---|---|---|
committer | Jesse Morgan <jesse@jesterpm.net> | 2016-12-17 21:28:53 -0800 |
commit | 54df2afaa61c6a03cbb4a33c9b90fa572b6d07b8 (patch) | |
tree | 18147b92b969d25ffbe61935fb63035cac820dd0 /db-4.8.30/mutex |
Berkeley DB 4.8 with rust build script for linux.
Diffstat (limited to 'db-4.8.30/mutex')
-rw-r--r-- | db-4.8.30/mutex/README | 110 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_alloc.c | 237 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_failchk.c | 69 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_fcntl.c | 232 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_method.c | 434 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_pthread.c | 638 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_region.c | 407 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_stat.c | 521 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_stub.c | 233 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_tas.c | 560 | ||||
-rw-r--r-- | db-4.8.30/mutex/mut_win32.c | 540 | ||||
-rw-r--r-- | db-4.8.30/mutex/test_mutex.c | 1051 | ||||
-rw-r--r-- | db-4.8.30/mutex/uts4_cc.s | 26 |
13 files changed, 5058 insertions, 0 deletions
diff --git a/db-4.8.30/mutex/README b/db-4.8.30/mutex/README new file mode 100644 index 0000000..6e95c5f --- /dev/null +++ b/db-4.8.30/mutex/README @@ -0,0 +1,110 @@ +# $Id$ + +Note: this only applies to locking using test-and-set and fcntl calls, +pthreads were added after this was written. + +Resource locking routines: lock based on a DB_MUTEX. All this gunk +(including trying to make assembly code portable), is necessary because +System V semaphores require system calls for uncontested locks and we +don't want to make two system calls per resource lock. + +First, this is how it works. The DB_MUTEX structure contains a resource +test-and-set lock (tsl), a file offset, a pid for debugging and statistics +information. + +If HAVE_MUTEX_FCNTL is NOT defined (that is, we know how to do +test-and-sets for this compiler/architecture combination), we try and +lock the resource tsl some number of times (based on the number of +processors). If we can't acquire the mutex that way, we use a system +call to sleep for 1ms, 2ms, 4ms, etc. (The time is bounded at 10ms for +mutexes backing logical locks and 25 ms for data structures, just in +case.) Using the timer backoff means that there are two assumptions: +that mutexes are held for brief periods (never over system calls or I/O) +and mutexes are not hotly contested. + +If HAVE_MUTEX_FCNTL is defined, we use a file descriptor to do byte +locking on a file at a specified offset. In this case, ALL of the +locking is done in the kernel. Because file descriptors are allocated +per process, we have to provide the file descriptor as part of the lock +call. We still have to do timer backoff because we need to be able to +block ourselves, that is, the lock manager causes processes to wait by +having the process acquire a mutex and then attempting to re-acquire the +mutex. There's no way to use kernel locking to block yourself, that is, +if you hold a lock and attempt to re-acquire it, the attempt will +succeed. + +Next, let's talk about why it doesn't work the way a reasonable person +would think it should work. + +Ideally, we'd have the ability to try to lock the resource tsl, and if +that fails, increment a counter of waiting processes, then block in the +kernel until the tsl is released. The process holding the resource tsl +would see the wait counter when it went to release the resource tsl, and +would wake any waiting processes up after releasing the lock. This would +actually require both another tsl (call it the mutex tsl) and +synchronization between the call that blocks in the kernel and the actual +resource tsl. The mutex tsl would be used to protect accesses to the +DB_MUTEX itself. Locking the mutex tsl would be done by a busy loop, +which is safe because processes would never block holding that tsl (all +they would do is try to obtain the resource tsl and set/check the wait +count). The problem in this model is that the blocking call into the +kernel requires a blocking semaphore, i.e. one whose normal state is +locked. + +The only portable forms of locking under UNIX are fcntl(2) on a file +descriptor/offset, and System V semaphores. Neither of these locking +methods are sufficient to solve the problem. + +The problem with fcntl locking is that only the process that obtained the +lock can release it. Remember, we want the normal state of the kernel +semaphore to be locked. So, if the creator of the DB_MUTEX were to +initialize the lock to "locked", then a second process locks the resource +tsl, and then a third process needs to block, waiting for the resource +tsl, when the second process wants to wake up the third process, it can't +because it's not the holder of the lock! For the second process to be +the holder of the lock, we would have to make a system call per +uncontested lock, which is what we were trying to get away from in the +first place. + +There are some hybrid schemes, such as signaling the holder of the lock, +or using a different blocking offset depending on which process is +holding the lock, but it gets complicated fairly quickly. I'm open to +suggestions, but I'm not holding my breath. + +Regardless, we use this form of locking when we don't have any other +choice, because it doesn't have the limitations found in System V +semaphores, and because the normal state of the kernel object in that +case is unlocked, so the process releasing the lock is also the holder +of the lock. + +The System V semaphore design has a number of other limitations that make +it inappropriate for this task. Namely: + +First, the semaphore key name space is separate from the file system name +space (although there exist methods for using file names to create +semaphore keys). If we use a well-known key, there's no reason to believe +that any particular key will not already be in use, either by another +instance of the DB application or some other application, in which case +the DB application will fail. If we create a key, then we have to use a +file system name to rendezvous and pass around the key. + +Second, System V semaphores traditionally have compile-time, system-wide +limits on the number of semaphore keys that you can have. Typically, that +number is far too low for any practical purpose. Since the semaphores +permit more than a single slot per semaphore key, we could try and get +around that limit by using multiple slots, but that means that the file +that we're using for rendezvous is going to have to contain slot +information as well as semaphore key information, and we're going to be +reading/writing it on every db_mutex_t init or destroy operation. Anyhow, +similar compile-time, system-wide limits on the numbers of slots per +semaphore key kick in, and you're right back where you started. + +My fantasy is that once POSIX.1 standard mutexes are in wide-spread use, +we can switch to them. My guess is that it won't happen, because the +POSIX semaphores are only required to work for threads within a process, +and not independent processes. + +Note: there are races in the statistics code, but since it's just that, +I didn't bother fixing them. (The fix requires a mutex tsl, so, when/if +this code is fixed to do rational locking (see above), then change the +statistics update code to acquire/release the mutex tsl. diff --git a/db-4.8.30/mutex/mut_alloc.c b/db-4.8.30/mutex/mut_alloc.c new file mode 100644 index 0000000..c25e3a2 --- /dev/null +++ b/db-4.8.30/mutex/mut_alloc.c @@ -0,0 +1,237 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1999-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * __mutex_alloc -- + * Allocate a mutex from the mutex region. + * + * PUBLIC: int __mutex_alloc __P((ENV *, int, u_int32_t, db_mutex_t *)); + */ +int +__mutex_alloc(env, alloc_id, flags, indxp) + ENV *env; + int alloc_id; + u_int32_t flags; + db_mutex_t *indxp; +{ + int ret; + + /* The caller may depend on us to initialize. */ + *indxp = MUTEX_INVALID; + + /* + * If this is not an application lock, and we've turned off locking, + * or the ENV handle isn't thread-safe, and this is a thread lock + * or the environment isn't multi-process by definition, there's no + * need to mutex at all. + */ + if (alloc_id != MTX_APPLICATION && + (F_ISSET(env->dbenv, DB_ENV_NOLOCKING) || + (!F_ISSET(env, ENV_THREAD) && + (LF_ISSET(DB_MUTEX_PROCESS_ONLY) || + F_ISSET(env, ENV_PRIVATE))))) + return (0); + + /* Private environments never share mutexes. */ + if (F_ISSET(env, ENV_PRIVATE)) + LF_SET(DB_MUTEX_PROCESS_ONLY); + + /* + * If we have a region in which to allocate the mutexes, lock it and + * do the allocation. + */ + if (MUTEX_ON(env)) + return (__mutex_alloc_int(env, 1, alloc_id, flags, indxp)); + + /* + * We have to allocate some number of mutexes before we have a region + * in which to allocate them. We handle this by saving up the list of + * flags and allocating them as soon as we have a handle. + * + * The list of mutexes to alloc is maintained in pairs: first the + * alloc_id argument, second the flags passed in by the caller. + */ + if (env->mutex_iq == NULL) { + env->mutex_iq_max = 50; + if ((ret = __os_calloc(env, env->mutex_iq_max, + sizeof(env->mutex_iq[0]), &env->mutex_iq)) != 0) + return (ret); + } else if (env->mutex_iq_next == env->mutex_iq_max - 1) { + env->mutex_iq_max *= 2; + if ((ret = __os_realloc(env, + env->mutex_iq_max * sizeof(env->mutex_iq[0]), + &env->mutex_iq)) != 0) + return (ret); + } + *indxp = env->mutex_iq_next + 1; /* Correct for MUTEX_INVALID. */ + env->mutex_iq[env->mutex_iq_next].alloc_id = alloc_id; + env->mutex_iq[env->mutex_iq_next].flags = flags; + ++env->mutex_iq_next; + + return (0); +} + +/* + * __mutex_alloc_int -- + * Internal routine to allocate a mutex. + * + * PUBLIC: int __mutex_alloc_int + * PUBLIC: __P((ENV *, int, int, u_int32_t, db_mutex_t *)); + */ +int +__mutex_alloc_int(env, locksys, alloc_id, flags, indxp) + ENV *env; + int locksys, alloc_id; + u_int32_t flags; + db_mutex_t *indxp; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + int ret; + + dbenv = env->dbenv; + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + ret = 0; + + /* + * If we're not initializing the mutex region, then lock the region to + * allocate new mutexes. Drop the lock before initializing the mutex, + * mutex initialization may require a system call. + */ + if (locksys) + MUTEX_SYSTEM_LOCK(env); + + if (mtxregion->mutex_next == MUTEX_INVALID) { + __db_errx(env, + "unable to allocate memory for mutex; resize mutex region"); + if (locksys) + MUTEX_SYSTEM_UNLOCK(env); + return (ENOMEM); + } + + *indxp = mtxregion->mutex_next; + mutexp = MUTEXP_SET(mtxmgr, *indxp); + DB_ASSERT(env, + ((uintptr_t)mutexp & (dbenv->mutex_align - 1)) == 0); + mtxregion->mutex_next = mutexp->mutex_next_link; + + --mtxregion->stat.st_mutex_free; + ++mtxregion->stat.st_mutex_inuse; + if (mtxregion->stat.st_mutex_inuse > mtxregion->stat.st_mutex_inuse_max) + mtxregion->stat.st_mutex_inuse_max = + mtxregion->stat.st_mutex_inuse; + if (locksys) + MUTEX_SYSTEM_UNLOCK(env); + + /* Initialize the mutex. */ + memset(mutexp, 0, sizeof(*mutexp)); + F_SET(mutexp, DB_MUTEX_ALLOCATED | + LF_ISSET(DB_MUTEX_LOGICAL_LOCK | + DB_MUTEX_PROCESS_ONLY | DB_MUTEX_SHARED)); + + /* + * If the mutex is associated with a single process, set the process + * ID. If the application ever calls DbEnv::failchk, we'll need the + * process ID to know if the mutex is still in use. + */ + if (LF_ISSET(DB_MUTEX_PROCESS_ONLY)) + dbenv->thread_id(dbenv, &mutexp->pid, NULL); + +#ifdef HAVE_STATISTICS + mutexp->alloc_id = alloc_id; +#else + COMPQUIET(alloc_id, 0); +#endif + + if ((ret = __mutex_init(env, *indxp, flags)) != 0) + (void)__mutex_free_int(env, locksys, indxp); + + return (ret); +} + +/* + * __mutex_free -- + * Free a mutex. + * + * PUBLIC: int __mutex_free __P((ENV *, db_mutex_t *)); + */ +int +__mutex_free(env, indxp) + ENV *env; + db_mutex_t *indxp; +{ + /* + * There is no explicit ordering in how the regions are cleaned up + * up and/or discarded when an environment is destroyed (either a + * private environment is closed or a public environment is removed). + * The way we deal with mutexes is to clean up all remaining mutexes + * when we close the mutex environment (because we have to be able to + * do that anyway, after a crash), which means we don't have to deal + * with region cleanup ordering on normal environment destruction. + * All that said, what it really means is we can get here without a + * mpool region. It's OK, the mutex has been, or will be, destroyed. + * + * If the mutex has never been configured, we're done. + */ + if (!MUTEX_ON(env) || *indxp == MUTEX_INVALID) + return (0); + + return (__mutex_free_int(env, 1, indxp)); +} + +/* + * __mutex_free_int -- + * Internal routine to free a mutex. + * + * PUBLIC: int __mutex_free_int __P((ENV *, int, db_mutex_t *)); + */ +int +__mutex_free_int(env, locksys, indxp) + ENV *env; + int locksys; + db_mutex_t *indxp; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + db_mutex_t mutex; + int ret; + + mutex = *indxp; + *indxp = MUTEX_INVALID; + + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + DB_ASSERT(env, F_ISSET(mutexp, DB_MUTEX_ALLOCATED)); + F_CLR(mutexp, DB_MUTEX_ALLOCATED); + + ret = __mutex_destroy(env, mutex); + + if (locksys) + MUTEX_SYSTEM_LOCK(env); + + /* Link the mutex on the head of the free list. */ + mutexp->mutex_next_link = mtxregion->mutex_next; + mtxregion->mutex_next = mutex; + ++mtxregion->stat.st_mutex_free; + --mtxregion->stat.st_mutex_inuse; + + if (locksys) + MUTEX_SYSTEM_UNLOCK(env); + + return (ret); +} diff --git a/db-4.8.30/mutex/mut_failchk.c b/db-4.8.30/mutex/mut_failchk.c new file mode 100644 index 0000000..6fbebde --- /dev/null +++ b/db-4.8.30/mutex/mut_failchk.c @@ -0,0 +1,69 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 2005-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * __mut_failchk -- + * Check for mutexes held by dead processes. + * + * PUBLIC: int __mut_failchk __P((ENV *)); + */ +int +__mut_failchk(env) + ENV *env; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + db_mutex_t i; + int ret; + char buf[DB_THREADID_STRLEN]; + + dbenv = env->dbenv; + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + ret = 0; + + MUTEX_SYSTEM_LOCK(env); + for (i = 1; i <= mtxregion->stat.st_mutex_cnt; ++i, ++mutexp) { + mutexp = MUTEXP_SET(mtxmgr, i); + + /* + * We're looking for per-process mutexes where the process + * has died. + */ + if (!F_ISSET(mutexp, DB_MUTEX_ALLOCATED) || + !F_ISSET(mutexp, DB_MUTEX_PROCESS_ONLY)) + continue; + + /* + * The thread that allocated the mutex may have exited, but + * we cannot reclaim the mutex if the process is still alive. + */ + if (dbenv->is_alive( + dbenv, mutexp->pid, 0, DB_MUTEX_PROCESS_ONLY)) + continue; + + __db_msg(env, "Freeing mutex for process: %s", + dbenv->thread_id_string(dbenv, mutexp->pid, 0, buf)); + + /* Unlock and free the mutex. */ + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + MUTEX_UNLOCK(env, i); + + if ((ret = __mutex_free_int(env, 0, &i)) != 0) + break; + } + MUTEX_SYSTEM_UNLOCK(env); + + return (ret); +} diff --git a/db-4.8.30/mutex/mut_fcntl.c b/db-4.8.30/mutex/mut_fcntl.c new file mode 100644 index 0000000..d1b896f --- /dev/null +++ b/db-4.8.30/mutex/mut_fcntl.c @@ -0,0 +1,232 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +static inline int __db_fcntl_mutex_lock_int __P((ENV *, db_mutex_t, int)); + +/* + * __db_fcntl_mutex_init -- + * Initialize a fcntl mutex. + * + * PUBLIC: int __db_fcntl_mutex_init __P((ENV *, db_mutex_t, u_int32_t)); + */ +int +__db_fcntl_mutex_init(env, mutex, flags) + ENV *env; + db_mutex_t mutex; + u_int32_t flags; +{ + COMPQUIET(env, NULL); + COMPQUIET(mutex, MUTEX_INVALID); + COMPQUIET(flags, 0); + + return (0); +} + +/* + * __db_fcntl_mutex_lock_int + * Internal function to lock a mutex, blocking only when requested + */ +inline int +__db_fcntl_mutex_lock_int(env, mutex, wait) + ENV *env; + db_mutex_t mutex; + int wait; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_THREAD_INFO *ip; + struct flock k_lock; + int locked, ms, ret; + + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + CHECK_MTX_THREAD(env, mutexp); + +#ifdef HAVE_STATISTICS + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + ++mutexp->mutex_set_wait; + else + ++mutexp->mutex_set_nowait; +#endif + + /* Initialize the lock. */ + k_lock.l_whence = SEEK_SET; + k_lock.l_start = mutex; + k_lock.l_len = 1; + + /* + * 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; + + for (locked = 0;;) { + /* + * Wait for the lock to become available; wait 1ms initially, + * up to 1 second. + */ + for (ms = 1; F_ISSET(mutexp, DB_MUTEX_LOCKED);) { + 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); + __os_yield(NULL, 0, ms * US_PER_MS); + if ((ms <<= 1) > MS_PER_SEC) + ms = MS_PER_SEC; + } + + /* Acquire an exclusive kernel lock on the byte. */ + k_lock.l_type = F_WRLCK; + if (fcntl(env->lockfhp->fd, F_SETLKW, &k_lock)) + goto err; + + /* If the resource is still available, it's ours. */ + if (!F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + locked = 1; + + F_SET(mutexp, DB_MUTEX_LOCKED); + dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid); + } + + /* Release the kernel lock. */ + k_lock.l_type = F_UNLCK; + if (fcntl(env->lockfhp->fd, F_SETLK, &k_lock)) + goto err; + + /* + * If we got the resource lock we're done. + * + * !!! + * We can't check to see if the lock is ours, because we may + * be trying to block ourselves in the lock manager, and so + * the holder of the lock that's preventing us from getting + * the lock may be us! (Seriously.) + */ + if (locked) + break; + } + +#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); + +err: ret = __os_get_syserr(); + __db_syserr(env, ret, "fcntl lock failed"); + return (__env_panic(env, __os_posix_err(ret))); +} + +/* + * __db_fcntl_mutex_lock + * Lock a mutex, blocking if necessary. + * + * PUBLIC: int __db_fcntl_mutex_lock __P((ENV *, db_mutex_t)); + */ +int +__db_fcntl_mutex_lock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_fcntl_mutex_lock_int(env, mutex, 1)); +} + +/* + * __db_fcntl_mutex_trylock + * Try to lock a mutex, without blocking when it is busy. + * + * PUBLIC: int __db_fcntl_mutex_trylock __P((ENV *, db_mutex_t)); + */ +int +__db_fcntl_mutex_trylock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_fcntl_mutex_lock_int(env, mutex, 0)); +} + +/* + * __db_fcntl_mutex_unlock -- + * Release a mutex. + * + * PUBLIC: int __db_fcntl_mutex_unlock __P((ENV *, db_mutex_t)); + */ +int +__db_fcntl_mutex_unlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + +#ifdef DIAGNOSTIC + if (!F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + __db_errx(env, "fcntl unlock failed: lock already unlocked"); + return (__env_panic(env, EACCES)); + } +#endif + + /* + * Release the resource. We don't have to acquire any locks because + * processes trying to acquire the lock are waiting for the flag to + * go to 0. Once that happens the waiters will serialize acquiring + * an exclusive kernel lock before locking the mutex. + */ + F_CLR(mutexp, DB_MUTEX_LOCKED); + + return (0); +} + +/* + * __db_fcntl_mutex_destroy -- + * Destroy a mutex. + * + * PUBLIC: int __db_fcntl_mutex_destroy __P((ENV *, db_mutex_t)); + */ +int +__db_fcntl_mutex_destroy(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + COMPQUIET(env, NULL); + COMPQUIET(mutex, MUTEX_INVALID); + + return (0); +} diff --git a/db-4.8.30/mutex/mut_method.c b/db-4.8.30/mutex/mut_method.c new file mode 100644 index 0000000..2588763 --- /dev/null +++ b/db-4.8.30/mutex/mut_method.c @@ -0,0 +1,434 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * __mutex_alloc_pp -- + * Allocate a mutex, application method. + * + * PUBLIC: int __mutex_alloc_pp __P((DB_ENV *, u_int32_t, db_mutex_t *)); + */ +int +__mutex_alloc_pp(dbenv, flags, indxp) + DB_ENV *dbenv; + u_int32_t flags; + db_mutex_t *indxp; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if ((ret = __db_fchk(env, "DB_ENV->mutex_alloc", + flags, DB_MUTEX_PROCESS_ONLY | DB_MUTEX_SELF_BLOCK)) != 0) + return (ret); + + ENV_ENTER(env, ip); + ret = __mutex_alloc(env, MTX_APPLICATION, flags, indxp); + ENV_LEAVE(env, ip); + + return (ret); +} + +/* + * __mutex_free_pp -- + * Destroy a mutex, application method. + * + * PUBLIC: int __mutex_free_pp __P((DB_ENV *, db_mutex_t)); + */ +int +__mutex_free_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if (indx == MUTEX_INVALID) + return (EINVAL); + + /* + * Internally Berkeley DB passes around the db_mutex_t address on + * free, because we want to make absolutely sure the slot gets + * overwritten with MUTEX_INVALID. We don't export MUTEX_INVALID, + * so we don't export that part of the API, either. + */ + ENV_ENTER(env, ip); + ret = __mutex_free(env, &indx); + ENV_LEAVE(env, ip); + + return (ret); +} + +/* + * __mutex_lock -- + * Lock a mutex, application method. + * + * PUBLIC: int __mutex_lock_pp __P((DB_ENV *, db_mutex_t)); + */ +int +__mutex_lock_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if (indx == MUTEX_INVALID) + return (EINVAL); + + ENV_ENTER(env, ip); + ret = __mutex_lock(env, indx); + ENV_LEAVE(env, ip); + return (ret); +} + +/* + * __mutex_unlock -- + * Unlock a mutex, application method. + * + * PUBLIC: int __mutex_unlock_pp __P((DB_ENV *, db_mutex_t)); + */ +int +__mutex_unlock_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if (indx == MUTEX_INVALID) + return (EINVAL); + + ENV_ENTER(env, ip); + ret = __mutex_unlock(env, indx); + ENV_LEAVE(env, ip); + return (ret); +} + +/* + * __mutex_get_align -- + * DB_ENV->mutex_get_align. + * + * PUBLIC: int __mutex_get_align __P((DB_ENV *, u_int32_t *)); + */ +int +__mutex_get_align(dbenv, alignp) + DB_ENV *dbenv; + u_int32_t *alignp; +{ + ENV *env; + + env = dbenv->env; + + if (MUTEX_ON(env)) { + /* Cannot be set after open, no lock required to read. */ + *alignp = ((DB_MUTEXREGION *) + env->mutex_handle->reginfo.primary)->stat.st_mutex_align; + } else + *alignp = dbenv->mutex_align; + return (0); +} + +/* + * __mutex_set_align -- + * DB_ENV->mutex_set_align. + * + * PUBLIC: int __mutex_set_align __P((DB_ENV *, u_int32_t)); + */ +int +__mutex_set_align(dbenv, align) + DB_ENV *dbenv; + u_int32_t align; +{ + ENV *env; + + env = dbenv->env; + + ENV_ILLEGAL_AFTER_OPEN(env, "DB_ENV->set_mutex_align"); + + if (align == 0 || !POWER_OF_TWO(align)) { + __db_errx(env, + "DB_ENV->mutex_set_align: alignment value must be a non-zero power-of-two"); + return (EINVAL); + } + + dbenv->mutex_align = align; + return (0); +} + +/* + * __mutex_get_increment -- + * DB_ENV->mutex_get_increment. + * + * PUBLIC: int __mutex_get_increment __P((DB_ENV *, u_int32_t *)); + */ +int +__mutex_get_increment(dbenv, incrementp) + DB_ENV *dbenv; + u_int32_t *incrementp; +{ + /* + * We don't maintain the increment in the region (it just makes + * no sense). Return whatever we have configured on this handle, + * nobody is ever going to notice. + */ + *incrementp = dbenv->mutex_inc; + return (0); +} + +/* + * __mutex_set_increment -- + * DB_ENV->mutex_set_increment. + * + * PUBLIC: int __mutex_set_increment __P((DB_ENV *, u_int32_t)); + */ +int +__mutex_set_increment(dbenv, increment) + DB_ENV *dbenv; + u_int32_t increment; +{ + ENV *env; + + env = dbenv->env; + + ENV_ILLEGAL_AFTER_OPEN(env, "DB_ENV->set_mutex_increment"); + + dbenv->mutex_cnt = 0; + dbenv->mutex_inc = increment; + return (0); +} + +/* + * __mutex_get_max -- + * DB_ENV->mutex_get_max. + * + * PUBLIC: int __mutex_get_max __P((DB_ENV *, u_int32_t *)); + */ +int +__mutex_get_max(dbenv, maxp) + DB_ENV *dbenv; + u_int32_t *maxp; +{ + ENV *env; + + env = dbenv->env; + + if (MUTEX_ON(env)) { + /* Cannot be set after open, no lock required to read. */ + *maxp = ((DB_MUTEXREGION *) + env->mutex_handle->reginfo.primary)->stat.st_mutex_cnt; + } else + *maxp = dbenv->mutex_cnt; + return (0); +} + +/* + * __mutex_set_max -- + * DB_ENV->mutex_set_max. + * + * PUBLIC: int __mutex_set_max __P((DB_ENV *, u_int32_t)); + */ +int +__mutex_set_max(dbenv, max) + DB_ENV *dbenv; + u_int32_t max; +{ + ENV *env; + + env = dbenv->env; + + ENV_ILLEGAL_AFTER_OPEN(env, "DB_ENV->set_mutex_max"); + + dbenv->mutex_cnt = max; + dbenv->mutex_inc = 0; + return (0); +} + +/* + * __mutex_get_tas_spins -- + * DB_ENV->mutex_get_tas_spins. + * + * PUBLIC: int __mutex_get_tas_spins __P((DB_ENV *, u_int32_t *)); + */ +int +__mutex_get_tas_spins(dbenv, tas_spinsp) + DB_ENV *dbenv; + u_int32_t *tas_spinsp; +{ + ENV *env; + + env = dbenv->env; + + if (MUTEX_ON(env)) { + /* Cannot be set after open, no lock required to read. */ + *tas_spinsp = ((DB_MUTEXREGION *)env-> + mutex_handle->reginfo.primary)->stat.st_mutex_tas_spins; + } else + *tas_spinsp = dbenv->mutex_tas_spins; + return (0); +} + +/* + * __mutex_set_tas_spins -- + * DB_ENV->mutex_set_tas_spins. + * + * PUBLIC: int __mutex_set_tas_spins __P((DB_ENV *, u_int32_t)); + */ +int +__mutex_set_tas_spins(dbenv, tas_spins) + DB_ENV *dbenv; + u_int32_t tas_spins; +{ + ENV *env; + + env = dbenv->env; + + /* + * Bound the value -- less than 1 makes no sense, greater than 1M + * makes no sense. + */ + if (tas_spins == 0) + tas_spins = 1; + else if (tas_spins > 1000000) + tas_spins = 1000000; + + /* + * There's a theoretical race here, but I'm not interested in locking + * the test-and-set spin count. The worst possibility is a thread + * reads out a bad spin count and spins until it gets the lock, but + * that's awfully unlikely. + */ + if (MUTEX_ON(env)) + ((DB_MUTEXREGION *)env->mutex_handle + ->reginfo.primary)->stat.st_mutex_tas_spins = tas_spins; + else + dbenv->mutex_tas_spins = tas_spins; + return (0); +} + +#if !defined(HAVE_ATOMIC_SUPPORT) && defined(HAVE_MUTEX_SUPPORT) +/* + * Provide atomic operations for platforms which have mutexes yet do not have + * native atomic operations configured. They are emulated by protected the + * operation with a mutex. The address of the atomic value selects which + * mutex to use. + */ +/* + * atomic_get_mutex - + * Map an address to the mutex to use to atomically modify it + */ +static inline db_mutex_t atomic_get_mutex(env, v) + ENV *env; + db_atomic_t *v; +{ + u_int index; + DB_MUTEXREGION *mtxreg; + + if (!MUTEX_ON(env)) + return (MUTEX_INVALID); + index = (u_int)(((uintptr_t) (v)) >> 6) % MAX_ATOMIC_MUTEXES; + mtxreg = (DB_MUTEXREGION *)env->mutex_handle->reginfo.primary; + return (mtxreg->mtx_atomic[index]); +} + +/* + * __atomic_inc + * Use a mutex to provide an atomic increment function + * + * PUBLIC: #if !defined(HAVE_ATOMIC_SUPPORT) && defined(HAVE_MUTEX_SUPPORT) + * PUBLIC: atomic_value_t __atomic_inc __P((ENV *, db_atomic_t *)); + * PUBLIC: #endif + */ +atomic_value_t +__atomic_inc(env, v) + ENV *env; + db_atomic_t *v; +{ + db_mutex_t mtx; + int ret; + + mtx = atomic_get_mutex(env, v); + MUTEX_LOCK(env, mtx); + ret = ++v->value; + MUTEX_UNLOCK(env, mtx); + + return (ret); +} + +/* + * __atomic_dec + * Use a mutex to provide an atomic decrement function + * + * PUBLIC: #if !defined(HAVE_ATOMIC_SUPPORT) && defined(HAVE_MUTEX_SUPPORT) + * PUBLIC: atomic_value_t __atomic_dec __P((ENV *, db_atomic_t *)); + * PUBLIC: #endif + */ +atomic_value_t +__atomic_dec(env, v) + ENV *env; + db_atomic_t *v; +{ + db_mutex_t mtx; + int ret; + + mtx = atomic_get_mutex(env, v); + MUTEX_LOCK(env, mtx); + ret = --v->value; + MUTEX_UNLOCK(env, mtx); + + return (ret); +} + +/* + * atomic_compare_exchange + * Use a mutex to provide an atomic decrement function + * + * PRIVATE: int atomic_compare_exchange + * PRIVATE: __P((ENV *, db_atomic_t *, atomic_value_t, atomic_value_t)); + * Returns 1 if the *v was equal to oldval, else 0 + * + * Side Effect: + * Sets the value to newval if and only if returning 1 + */ +int +atomic_compare_exchange(env, v, oldval, newval) + ENV *env; + db_atomic_t *v; + atomic_value_t oldval; + atomic_value_t newval; +{ + db_mutex_t mtx; + int ret; + + if (atomic_read(v) != oldval) + return (0); + + mtx = atomic_get_mutex(env, v); + MUTEX_LOCK(env, mtx); + ret = atomic_read(v) == oldval; + if (ret) + atomic_init(v, newval); + MUTEX_UNLOCK(env, mtx); + + return (ret); +} +#endif diff --git a/db-4.8.30/mutex/mut_pthread.c b/db-4.8.30/mutex/mut_pthread.c new file mode 100644 index 0000000..51497bb --- /dev/null +++ b/db-4.8.30/mutex/mut_pthread.c @@ -0,0 +1,638 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1999-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +/* + * This is where we load in architecture/compiler specific mutex code. + */ +#define LOAD_ACTUAL_MUTEX_CODE + +#ifdef HAVE_MUTEX_SOLARIS_LWP +#define pthread_cond_destroy(x) 0 +#define pthread_cond_signal _lwp_cond_signal +#define pthread_cond_broadcast _lwp_cond_broadcast +#define pthread_cond_wait _lwp_cond_wait +#define pthread_mutex_destroy(x) 0 +#define pthread_mutex_lock _lwp_mutex_lock +#define pthread_mutex_trylock _lwp_mutex_trylock +#define pthread_mutex_unlock _lwp_mutex_unlock +#endif +#ifdef HAVE_MUTEX_UI_THREADS +#define pthread_cond_destroy(x) cond_destroy +#define pthread_cond_broadcast cond_broadcast +#define pthread_cond_wait cond_wait +#define pthread_mutex_destroy mutex_destroy +#define pthread_mutex_lock mutex_lock +#define pthread_mutex_trylock mutex_trylock +#define pthread_mutex_unlock mutex_unlock +#endif + +/* + * According to HP-UX engineers contacted by Netscape, + * pthread_mutex_unlock() will occasionally return EFAULT for no good reason + * on mutexes in shared memory regions, and the correct caller behavior + * is to try again. Do so, up to EFAULT_RETRY_ATTEMPTS consecutive times. + * Note that we don't bother to restrict this to HP-UX; + * it should be harmless elsewhere. [#2471] + */ +#define EFAULT_RETRY_ATTEMPTS 5 +#define RETRY_ON_EFAULT(func_invocation, ret) do { \ + int i; \ + i = EFAULT_RETRY_ATTEMPTS; \ + do { \ + RET_SET((func_invocation), ret); \ + } while (ret == EFAULT && --i > 0); \ +} while (0) + +/* + * IBM's MVS pthread mutex implementation returns -1 and sets errno rather than + * returning errno itself. As -1 is not a valid errno value, assume functions + * returning -1 have set errno. If they haven't, return a random error value. + */ +#define RET_SET(f, ret) do { \ + if (((ret) = (f)) == -1 && ((ret) = errno) == 0) \ + (ret) = EAGAIN; \ +} while (0) + +/* + * __db_pthread_mutex_init -- + * Initialize a pthread mutex: either a native one or + * just the mutex for block/wakeup of a hybrid test-and-set mutex + * + * + * PUBLIC: int __db_pthread_mutex_init __P((ENV *, db_mutex_t, u_int32_t)); + */ +int +__db_pthread_mutex_init(env, mutex, flags) + ENV *env; + db_mutex_t mutex; + u_int32_t flags; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + int ret; + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + ret = 0; + +#ifndef HAVE_MUTEX_HYBRID + /* Can't have self-blocking shared latches. */ + DB_ASSERT(env, !LF_ISSET(DB_MUTEX_SELF_BLOCK) || + !LF_ISSET(DB_MUTEX_SHARED)); +#endif + +#ifdef HAVE_MUTEX_PTHREADS + { + pthread_condattr_t condattr, *condattrp = NULL; + pthread_mutexattr_t mutexattr, *mutexattrp = NULL; + +#ifndef HAVE_MUTEX_HYBRID + if (LF_ISSET(DB_MUTEX_SHARED)) { +#if defined(HAVE_SHARED_LATCHES) + pthread_rwlockattr_t rwlockattr, *rwlockattrp = NULL; +#ifndef HAVE_MUTEX_THREAD_ONLY + if (!LF_ISSET(DB_MUTEX_PROCESS_ONLY)) { + RET_SET((pthread_rwlockattr_init(&rwlockattr)), ret); + if (ret != 0) + goto err; + RET_SET((pthread_rwlockattr_setpshared( + &rwlockattr, PTHREAD_PROCESS_SHARED)), ret); + rwlockattrp = &rwlockattr; + } +#endif + + if (ret == 0) + RET_SET((pthread_rwlock_init(&mutexp->u.rwlock, + rwlockattrp)), ret); + if (rwlockattrp != NULL) + (void)pthread_rwlockattr_destroy(rwlockattrp); + + F_SET(mutexp, DB_MUTEX_SHARED); + /* For rwlocks, we're done - cannot use the mutex or cond */ + goto err; +#endif + } +#endif +#ifndef HAVE_MUTEX_THREAD_ONLY + if (!LF_ISSET(DB_MUTEX_PROCESS_ONLY)) { + RET_SET((pthread_mutexattr_init(&mutexattr)), ret); + if (ret != 0) + goto err; + RET_SET((pthread_mutexattr_setpshared( + &mutexattr, PTHREAD_PROCESS_SHARED)), ret); + mutexattrp = &mutexattr; + } +#endif + + if (ret == 0) + RET_SET( + (pthread_mutex_init(&mutexp->u.m.mutex, mutexattrp)), ret); + + if (mutexattrp != NULL) + (void)pthread_mutexattr_destroy(mutexattrp); + if (ret != 0) + goto err; + if (LF_ISSET(DB_MUTEX_SELF_BLOCK)) { +#ifndef HAVE_MUTEX_THREAD_ONLY + if (!LF_ISSET(DB_MUTEX_PROCESS_ONLY)) { + RET_SET((pthread_condattr_init(&condattr)), ret); + if (ret != 0) + goto err; + + condattrp = &condattr; + RET_SET((pthread_condattr_setpshared( + &condattr, PTHREAD_PROCESS_SHARED)), ret); + } +#endif + + if (ret == 0) + RET_SET((pthread_cond_init( + &mutexp->u.m.cond, condattrp)), ret); + + F_SET(mutexp, DB_MUTEX_SELF_BLOCK); + if (condattrp != NULL) + (void)pthread_condattr_destroy(condattrp); + } + + } +#endif +#ifdef HAVE_MUTEX_SOLARIS_LWP + /* + * XXX + * Gcc complains about missing braces in the static initializations of + * lwp_cond_t and lwp_mutex_t structures because the structures contain + * sub-structures/unions and the Solaris include file that defines the + * initialization values doesn't have surrounding braces. There's not + * much we can do. + */ + if (LF_ISSET(DB_MUTEX_PROCESS_ONLY)) { + static lwp_mutex_t mi = DEFAULTMUTEX; + + mutexp->mutex = mi; + } else { + static lwp_mutex_t mi = SHAREDMUTEX; + + mutexp->mutex = mi; + } + if (LF_ISSET(DB_MUTEX_SELF_BLOCK)) { + if (LF_ISSET(DB_MUTEX_PROCESS_ONLY)) { + static lwp_cond_t ci = DEFAULTCV; + + mutexp->cond = ci; + } else { + static lwp_cond_t ci = SHAREDCV; + + mutexp->cond = ci; + } + F_SET(mutexp, DB_MUTEX_SELF_BLOCK); + } +#endif +#ifdef HAVE_MUTEX_UI_THREADS + { + int type; + + type = LF_ISSET(DB_MUTEX_PROCESS_ONLY) ? USYNC_THREAD : USYNC_PROCESS; + + ret = mutex_init(&mutexp->mutex, type, NULL); + if (ret == 0 && LF_ISSET(DB_MUTEX_SELF_BLOCK)) { + ret = cond_init(&mutexp->cond, type, NULL); + + F_SET(mutexp, DB_MUTEX_SELF_BLOCK); + }} +#endif + +err: if (ret != 0) { + __db_err(env, ret, "unable to initialize mutex"); + } + return (ret); +} + +/* + * __db_pthread_mutex_lock + * Lock on a mutex, blocking if necessary. + * + * self-blocking shared latches are not supported + * + * PUBLIC: int __db_pthread_mutex_lock __P((ENV *, db_mutex_t)); + */ +int +__db_pthread_mutex_lock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_THREAD_INFO *ip; + int ret; + + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + CHECK_MTX_THREAD(env, mutexp); + +#if defined(HAVE_STATISTICS) && !defined(HAVE_MUTEX_HYBRID) + /* + * We want to know which mutexes are contentious, but don't want to + * do an interlocked test here -- that's slower when the underlying + * system has adaptive mutexes and can perform optimizations like + * spinning only if the thread holding the mutex is actually running + * on a CPU. Make a guess, using a normal load instruction. + */ + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + ++mutexp->mutex_set_wait; + else + ++mutexp->mutex_set_nowait; +#endif + + if (F_ISSET(dbenv, DB_ENV_FAILCHK)) { + for (;;) { + RET_SET_PTHREAD_TRYLOCK(mutexp, ret); + if (ret != EBUSY) + break; + if (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); + else { + /* + * Some thread other than the true + * FAILCHK thread in this process is + * asking for the mutex held by the + * dead process/thread. We will + * block here until someone else + * does the cleanup. Same behavior + * as if we hadnt gone down the 'if + * DB_ENV_FAILCHK' path to start with. + */ + RET_SET_PTHREAD_LOCK(mutexp, ret); + break; + } + } + } + } else + RET_SET_PTHREAD_LOCK(mutexp, ret); + if (ret != 0) + goto err; + + if (F_ISSET(mutexp, DB_MUTEX_SELF_BLOCK)) { + /* + * If we are using hybrid mutexes then the pthread mutexes + * are only used to wait after spinning on the TAS mutex. + * Set the wait flag before checking to see if the mutex + * is still locked. The holder will clear DB_MUTEX_LOCKED + * before checking the wait counter. + */ +#ifdef HAVE_MUTEX_HYBRID + mutexp->wait++; + MUTEX_MEMBAR(mutexp->wait); + while (F_ISSET(mutexp, DB_MUTEX_LOCKED)) { +#else + while (MUTEXP_IS_BUSY(mutexp)) { +#endif +#if defined(HAVE_MUTEX_HYBRID) + STAT(mutexp->hybrid_wait++); +#endif +#ifdef MUTEX_DIAG + printf("block %d %x wait busy %x count %d\n", + mutex, pthread_self(), + MUTEXP_BUSY_FIELD(mutexp), mutexp->wait); +#endif + + RET_SET((pthread_cond_wait( + &mutexp->u.m.cond, &mutexp->u.m.mutex)), ret); +#ifdef MUTEX_DIAG + printf("block %d %x wait returns %d busy %x\n", + mutex, pthread_self(), + ret, MUTEXP_BUSY_FIELD(mutexp)); +#endif + /* + * !!! + * Solaris bug workaround: + * pthread_cond_wait() sometimes returns ETIME -- out + * of sheer paranoia, check both ETIME and ETIMEDOUT. + * We believe this happens when the application uses + * SIGALRM for some purpose, e.g., the C library sleep + * call, and Solaris delivers the signal to the wrong + * LWP. + */ + if (ret != 0 && ret != EINTR && +#ifdef ETIME + ret != ETIME && +#endif + ret != ETIMEDOUT) { + (void)pthread_mutex_unlock(&mutexp->u.m.mutex); + goto err; + } +#ifdef HAVE_MUTEX_HYBRID + MUTEX_MEMBAR(mutexp->flags); +#endif + } + +#ifdef HAVE_MUTEX_HYBRID + mutexp->wait--; +#else + F_SET(mutexp, DB_MUTEX_LOCKED); + dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid); +#endif + + /* #2471: HP-UX can sporadically return EFAULT. See above */ + RETRY_ON_EFAULT(pthread_mutex_unlock(&mutexp->u.m.mutex), ret); + if (ret != 0) + goto err; + } else { +#ifdef DIAGNOSTIC + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + char buf[DB_THREADID_STRLEN]; + (void)dbenv->thread_id_string(dbenv, + mutexp->pid, mutexp->tid, buf); + __db_errx(env, + "pthread lock failed: lock currently in use: pid/tid: %s", + buf); + ret = EINVAL; + goto err; + } +#endif + F_SET(mutexp, DB_MUTEX_LOCKED); + dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid); + } + +#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); + +err: __db_err(env, ret, "pthread lock failed"); + return (__env_panic(env, ret)); +} + +#if defined(HAVE_SHARED_LATCHES) && !defined(HAVE_MUTEX_HYBRID) +/* + * __db_pthread_mutex_readlock + * Take a shared lock on a mutex, blocking if necessary. + * + * PUBLIC: #if defined(HAVE_SHARED_LATCHES) + * PUBLIC: int __db_pthread_mutex_readlock __P((ENV *, db_mutex_t)); + * PUBLIC: #endif + */ +int +__db_pthread_mutex_readlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + int ret; + + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + DB_ASSERT(env, F_ISSET(mutexp, DB_MUTEX_SHARED)); + + CHECK_MTX_THREAD(env, mutexp); + +#if defined(HAVE_STATISTICS) && !defined(HAVE_MUTEX_HYBRID) + /* + * We want to know which mutexes are contentious, but don't want to + * do an interlocked test here -- that's slower when the underlying + * system has adaptive mutexes and can perform optimizations like + * spinning only if the thread holding the mutex is actually running + * on a CPU. Make a guess, using a normal load instruction. + */ + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + ++mutexp->mutex_set_rd_wait; + else + ++mutexp->mutex_set_rd_nowait; +#endif + + RET_SET((pthread_rwlock_rdlock(&mutexp->u.rwlock)), ret); + DB_ASSERT(env, !F_ISSET(mutexp, DB_MUTEX_LOCKED)); + if (ret != 0) + goto err; + +#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); + +err: __db_err(env, ret, "pthread readlock failed"); + return (__env_panic(env, ret)); +} +#endif + +/* + * __db_pthread_mutex_unlock -- + * Release a mutex. + * + * PUBLIC: int __db_pthread_mutex_unlock __P((ENV *, db_mutex_t)); + */ +int +__db_pthread_mutex_unlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_THREAD_INFO *ip; + int ret; +#if defined(MUTEX_DIAG) && defined(HAVE_MUTEX_HYBRID) + int waiters; +#endif + + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); +#if defined(MUTEX_DIAG) && defined(HAVE_MUTEX_HYBRID) + waiters = mutexp->wait; +#endif + +#if !defined(HAVE_MUTEX_HYBRID) && !defined(HAVE_SHARED_LATCHES) && \ + defined(DIAGNOSTIC) + if (!F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + __db_errx( + env, "pthread unlock failed: lock already unlocked"); + return (__env_panic(env, EACCES)); + } +#endif + if (F_ISSET(mutexp, DB_MUTEX_SELF_BLOCK)) { + if (F_ISSET(dbenv, DB_ENV_FAILCHK)) { + RET_SET((pthread_mutex_trylock( + &mutexp->u.m.mutex)), ret); + while (ret == EBUSY) { + if (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); + else { + /* + * We are not the true + * failchk thread, so go + * ahead and block on mutex + * until someone else does the + * cleanup. This is the same + * behavior we would get if we + * hadnt gone down the 'if + * DB_ENV_FAILCHK' path. + */ + RET_SET((pthread_mutex_lock( + &mutexp->u.m.mutex)), ret); + break; + } + } + + RET_SET((pthread_mutex_trylock( + &mutexp->u.m.mutex)), ret); + } + } else + RET_SET((pthread_mutex_lock(&mutexp->u.m.mutex)), ret); + if (ret != 0) + goto err; + +#ifdef HAVE_MUTEX_HYBRID + STAT(mutexp->hybrid_wakeup++); +#else + F_CLR(mutexp, DB_MUTEX_LOCKED); /* nop if DB_MUTEX_SHARED */ +#endif + + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) + RET_SET( + (pthread_cond_broadcast(&mutexp->u.m.cond)), ret); + else + RET_SET((pthread_cond_signal(&mutexp->u.m.cond)), ret); + if (ret != 0) + goto err; + } else { +#ifndef HAVE_MUTEX_HYBRID + F_CLR(mutexp, DB_MUTEX_LOCKED); +#endif + } + + /* See comment above; workaround for [#2471]. */ +#if defined(HAVE_SHARED_LATCHES) && !defined(HAVE_MUTEX_HYBRID) + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) + RETRY_ON_EFAULT(pthread_rwlock_unlock(&mutexp->u.rwlock), ret); + else +#endif + RETRY_ON_EFAULT(pthread_mutex_unlock(&mutexp->u.m.mutex), ret); + +err: if (ret != 0) { + __db_err(env, ret, "pthread unlock failed"); + return (__env_panic(env, ret)); + } +#if defined(MUTEX_DIAG) && defined(HAVE_MUTEX_HYBRID) + if (!MUTEXP_IS_BUSY(mutexp) && mutexp->wait != 0) + printf("unlock %d %x busy %x waiters %d/%d\n", + mutex, pthread_self(), ret, + MUTEXP_BUSY_FIELD(mutexp), waiters, mutexp->wait); +#endif + return (ret); +} + +/* + * __db_pthread_mutex_destroy -- + * Destroy a mutex. + * If it is a native shared latch (not hybrid) then + * destroy only one half of the rwlock/mutex&cond union, + * depending whether it was allocated as shared + * + * PUBLIC: int __db_pthread_mutex_destroy __P((ENV *, db_mutex_t)); + */ +int +__db_pthread_mutex_destroy(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_THREAD_INFO *ip; + int ret, t_ret, failchk_thread; + + if (!MUTEX_ON(env)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + ret = 0; + failchk_thread = FALSE; + /* Get information to determine if we are really the failchk thread. */ + if (F_ISSET(env->dbenv, DB_ENV_FAILCHK)) { + ret = __env_set_state(env, &ip, THREAD_VERIFY); + if (ip != NULL && ip->dbth_state == THREAD_FAILCHK) + failchk_thread = TRUE; + } + +#ifndef HAVE_MUTEX_HYBRID + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) { +#if defined(HAVE_SHARED_LATCHES) + /* + * If there were dead processes waiting on the condition + * we may not be able to destroy it. Let failchk thread skip + * this. XXX What operating system resources might this leak? + */ + if (!failchk_thread) + RET_SET( + (pthread_rwlock_destroy(&mutexp->u.rwlock)), ret); + /* For rwlocks, we're done - must not destroy rest of union */ + return (ret); +#endif + } +#endif + if (F_ISSET(mutexp, DB_MUTEX_SELF_BLOCK)) { + /* + * If there were dead processes waiting on the condition + * we may not be able to destroy it. Let failchk thread + * skip this. + */ + if (!failchk_thread) + RET_SET((pthread_cond_destroy(&mutexp->u.m.cond)), ret); + if (ret != 0) + __db_err(env, ret, "unable to destroy cond"); + } + RET_SET((pthread_mutex_destroy(&mutexp->u.m.mutex)), t_ret); + if (t_ret != 0 && !failchk_thread) { + __db_err(env, t_ret, "unable to destroy mutex"); + if (ret == 0) + ret = t_ret; + } + return (ret); +} diff --git a/db-4.8.30/mutex/mut_region.c b/db-4.8.30/mutex/mut_region.c new file mode 100644 index 0000000..e985ac2 --- /dev/null +++ b/db-4.8.30/mutex/mut_region.c @@ -0,0 +1,407 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" +#include "dbinc/log.h" +#include "dbinc/lock.h" +#include "dbinc/mp.h" +#include "dbinc/txn.h" + +static size_t __mutex_align_size __P((ENV *)); +static int __mutex_region_init __P((ENV *, DB_MUTEXMGR *)); +static size_t __mutex_region_size __P((ENV *)); + +/* + * __mutex_open -- + * Open a mutex region. + * + * PUBLIC: int __mutex_open __P((ENV *, int)); + */ +int +__mutex_open(env, create_ok) + ENV *env; + int create_ok; +{ + DB_ENV *dbenv; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + db_mutex_t mutex; + u_int32_t cpu_count; + u_int i; + int ret; + + dbenv = env->dbenv; + + /* + * Initialize the ENV handle information if not already initialized. + * + * Align mutexes on the byte boundaries specified by the application. + */ + if (dbenv->mutex_align == 0) + dbenv->mutex_align = MUTEX_ALIGN; + if (dbenv->mutex_tas_spins == 0) { + cpu_count = __os_cpu_count(); + if ((ret = __mutex_set_tas_spins(dbenv, cpu_count == 1 ? + cpu_count : cpu_count * MUTEX_SPINS_PER_PROCESSOR)) != 0) + return (ret); + } + + /* + * If the user didn't set an absolute value on the number of mutexes + * we'll need, figure it out. We're conservative in our allocation, + * we need mutexes for DB handles, group-commit queues and other things + * applications allocate at run-time. The application may have kicked + * up our count to allocate its own mutexes, add that in. + */ + if (dbenv->mutex_cnt == 0) + dbenv->mutex_cnt = + __lock_region_mutex_count(env) + + __log_region_mutex_count(env) + + __memp_region_mutex_count(env) + + __txn_region_mutex_count(env) + + dbenv->mutex_inc + 100; + + /* Create/initialize the mutex manager structure. */ + if ((ret = __os_calloc(env, 1, sizeof(DB_MUTEXMGR), &mtxmgr)) != 0) + return (ret); + + /* Join/create the mutex region. */ + mtxmgr->reginfo.env = env; + mtxmgr->reginfo.type = REGION_TYPE_MUTEX; + mtxmgr->reginfo.id = INVALID_REGION_ID; + mtxmgr->reginfo.flags = REGION_JOIN_OK; + if (create_ok) + F_SET(&mtxmgr->reginfo, REGION_CREATE_OK); + if ((ret = __env_region_attach(env, + &mtxmgr->reginfo, __mutex_region_size(env))) != 0) + goto err; + + /* If we created the region, initialize it. */ + if (F_ISSET(&mtxmgr->reginfo, REGION_CREATE)) + if ((ret = __mutex_region_init(env, mtxmgr)) != 0) + goto err; + + /* Set the local addresses. */ + mtxregion = mtxmgr->reginfo.primary = + R_ADDR(&mtxmgr->reginfo, mtxmgr->reginfo.rp->primary); + mtxmgr->mutex_array = R_ADDR(&mtxmgr->reginfo, mtxregion->mutex_off); + + env->mutex_handle = mtxmgr; + + /* Allocate initial queue of mutexes. */ + if (env->mutex_iq != NULL) { + DB_ASSERT(env, F_ISSET(&mtxmgr->reginfo, REGION_CREATE)); + for (i = 0; i < env->mutex_iq_next; ++i) { + if ((ret = __mutex_alloc_int( + env, 0, env->mutex_iq[i].alloc_id, + env->mutex_iq[i].flags, &mutex)) != 0) + goto err; + /* + * Confirm we allocated the right index, correcting + * for avoiding slot 0 (MUTEX_INVALID). + */ + DB_ASSERT(env, mutex == i + 1); + } + __os_free(env, env->mutex_iq); + env->mutex_iq = NULL; +#ifndef HAVE_ATOMIC_SUPPORT + /* If necessary allocate the atomic emulation mutexes. */ + for (i = 0; i != MAX_ATOMIC_MUTEXES; i++) + if ((ret = __mutex_alloc_int( + env, 0, MTX_ATOMIC_EMULATION, + 0, &mtxregion->mtx_atomic[i])) != 0) + return (ret); +#endif + + /* + * This is the first place we can test mutexes and we need to + * know if they're working. (They CAN fail, for example on + * SunOS, when using fcntl(2) for locking and using an + * in-memory filesystem as the database environment directory. + * But you knew that, I'm sure -- it probably wasn't worth + * mentioning.) + */ + mutex = MUTEX_INVALID; + if ((ret = + __mutex_alloc(env, MTX_MUTEX_TEST, 0, &mutex) != 0) || + (ret = __mutex_lock(env, mutex)) != 0 || + (ret = __mutex_unlock(env, mutex)) != 0 || + (ret = __mutex_trylock(env, mutex)) != 0 || + (ret = __mutex_unlock(env, mutex)) != 0 || + (ret = __mutex_free(env, &mutex)) != 0) { + __db_errx(env, + "Unable to acquire/release a mutex; check configuration"); + goto err; + } +#ifdef HAVE_SHARED_LATCHES + if ((ret = + __mutex_alloc(env, + MTX_MUTEX_TEST, DB_MUTEX_SHARED, &mutex) != 0) || + (ret = __mutex_lock(env, mutex)) != 0 || + (ret = __mutex_unlock(env, mutex)) != 0 || + (ret = __mutex_rdlock(env, mutex)) != 0 || + (ret = __mutex_rdlock(env, mutex)) != 0 || + (ret = __mutex_unlock(env, mutex)) != 0 || + (ret = __mutex_unlock(env, mutex)) != 0 || + (ret = __mutex_free(env, &mutex)) != 0) { + __db_errx(env, + "Unable to acquire/release a shared latch; check configuration"); + goto err; + } +#endif + } + return (0); + +err: env->mutex_handle = NULL; + if (mtxmgr->reginfo.addr != NULL) + (void)__env_region_detach(env, &mtxmgr->reginfo, 0); + + __os_free(env, mtxmgr); + return (ret); +} + +/* + * __mutex_region_init -- + * Initialize a mutex region in shared memory. + */ +static int +__mutex_region_init(env, mtxmgr) + ENV *env; + DB_MUTEXMGR *mtxmgr; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXREGION *mtxregion; + db_mutex_t i; + int ret; + void *mutex_array; + + dbenv = env->dbenv; + + COMPQUIET(mutexp, NULL); + + if ((ret = __env_alloc(&mtxmgr->reginfo, + sizeof(DB_MUTEXREGION), &mtxmgr->reginfo.primary)) != 0) { + __db_errx(env, + "Unable to allocate memory for the mutex region"); + return (ret); + } + mtxmgr->reginfo.rp->primary = + R_OFFSET(&mtxmgr->reginfo, mtxmgr->reginfo.primary); + mtxregion = mtxmgr->reginfo.primary; + memset(mtxregion, 0, sizeof(*mtxregion)); + + if ((ret = __mutex_alloc( + env, MTX_MUTEX_REGION, 0, &mtxregion->mtx_region)) != 0) + return (ret); + mtxmgr->reginfo.mtx_alloc = mtxregion->mtx_region; + + mtxregion->mutex_size = __mutex_align_size(env); + + mtxregion->stat.st_mutex_align = dbenv->mutex_align; + mtxregion->stat.st_mutex_cnt = dbenv->mutex_cnt; + mtxregion->stat.st_mutex_tas_spins = dbenv->mutex_tas_spins; + + /* + * Get a chunk of memory to be used for the mutexes themselves. Each + * piece of the memory must be properly aligned, and that alignment + * may be more restrictive than the memory alignment returned by the + * underlying allocation code. We already know how much memory each + * mutex in the array will take up, but we need to offset the first + * mutex in the array so the array begins properly aligned. + * + * The OOB mutex (MUTEX_INVALID) is 0. To make this work, we ignore + * the first allocated slot when we build the free list. We have to + * correct the count by 1 here, though, otherwise our counter will be + * off by 1. + */ + if ((ret = __env_alloc(&mtxmgr->reginfo, + mtxregion->stat.st_mutex_align + + (mtxregion->stat.st_mutex_cnt + 1) * mtxregion->mutex_size, + &mutex_array)) != 0) { + __db_errx(env, + "Unable to allocate memory for mutexes from the region"); + return (ret); + } + + mtxregion->mutex_off_alloc = R_OFFSET(&mtxmgr->reginfo, mutex_array); + mutex_array = ALIGNP_INC(mutex_array, mtxregion->stat.st_mutex_align); + mtxregion->mutex_off = R_OFFSET(&mtxmgr->reginfo, mutex_array); + mtxmgr->mutex_array = mutex_array; + + /* + * Put the mutexes on a free list and clear the allocated flag. + * + * The OOB mutex (MUTEX_INVALID) is 0, skip it. + * + * The comparison is <, not <=, because we're looking ahead one + * in each link. + */ + for (i = 1; i < mtxregion->stat.st_mutex_cnt; ++i) { + mutexp = MUTEXP_SET(mtxmgr, i); + mutexp->flags = 0; + mutexp->mutex_next_link = i + 1; + } + mutexp = MUTEXP_SET(mtxmgr, i); + mutexp->flags = 0; + mutexp->mutex_next_link = MUTEX_INVALID; + mtxregion->mutex_next = 1; + mtxregion->stat.st_mutex_free = mtxregion->stat.st_mutex_cnt; + mtxregion->stat.st_mutex_inuse = mtxregion->stat.st_mutex_inuse_max = 0; + + return (0); +} + +/* + * __mutex_env_refresh -- + * Clean up after the mutex region on a close or failed open. + * + * PUBLIC: int __mutex_env_refresh __P((ENV *)); + */ +int +__mutex_env_refresh(env) + ENV *env; +{ + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + REGINFO *reginfo; + int ret; + + mtxmgr = env->mutex_handle; + reginfo = &mtxmgr->reginfo; + mtxregion = mtxmgr->reginfo.primary; + + /* + * If a private region, return the memory to the heap. Not needed for + * filesystem-backed or system shared memory regions, that memory isn't + * owned by any particular process. + */ + if (F_ISSET(env, ENV_PRIVATE)) { + reginfo->mtx_alloc = MUTEX_INVALID; + +#ifdef HAVE_MUTEX_SYSTEM_RESOURCES + /* + * If destroying the mutex region, return any system resources + * to the system. + */ + __mutex_resource_return(env, reginfo); +#endif + /* Discard the mutex array. */ + __env_alloc_free( + reginfo, R_ADDR(reginfo, mtxregion->mutex_off_alloc)); + } + + /* Detach from the region. */ + ret = __env_region_detach(env, reginfo, 0); + + __os_free(env, mtxmgr); + + env->mutex_handle = NULL; + + return (ret); +} + +/* + * __mutex_align_size -- + * Return how much memory each mutex will take up if an array of them + * are to be properly aligned, individually, within the array. + */ +static size_t +__mutex_align_size(env) + ENV *env; +{ + DB_ENV *dbenv; + + dbenv = env->dbenv; + + return ((size_t)DB_ALIGN(sizeof(DB_MUTEX), dbenv->mutex_align)); +} + +/* + * __mutex_region_size -- + * Return the amount of space needed for the mutex region. + */ +static size_t +__mutex_region_size(env) + ENV *env; +{ + DB_ENV *dbenv; + size_t s; + + dbenv = env->dbenv; + + s = sizeof(DB_MUTEXMGR) + 1024; + + /* We discard one mutex for the OOB slot. */ + s += __env_alloc_size( + (dbenv->mutex_cnt + 1) *__mutex_align_size(env)); + + return (s); +} + +#ifdef HAVE_MUTEX_SYSTEM_RESOURCES +/* + * __mutex_resource_return + * Return any system-allocated mutex resources to the system. + * + * PUBLIC: void __mutex_resource_return __P((ENV *, REGINFO *)); + */ +void +__mutex_resource_return(env, infop) + ENV *env; + REGINFO *infop; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr, mtxmgr_st; + DB_MUTEXREGION *mtxregion; + db_mutex_t i; + void *orig_handle; + + /* + * This routine is called in two cases: when discarding the regions + * from a previous Berkeley DB run, during recovery, and two, when + * discarding regions as we shut down the database environment. + * + * Walk the list of mutexes and destroy any live ones. + * + * This is just like joining a region -- the REGINFO we're handed is + * the same as the one returned by __env_region_attach(), all we have + * to do is fill in the links. + * + * !!! + * The region may be corrupted, of course. We're safe because the + * only things we look at are things that are initialized when the + * region is created, and never modified after that. + */ + memset(&mtxmgr_st, 0, sizeof(mtxmgr_st)); + mtxmgr = &mtxmgr_st; + mtxmgr->reginfo = *infop; + mtxregion = mtxmgr->reginfo.primary = + R_ADDR(&mtxmgr->reginfo, mtxmgr->reginfo.rp->primary); + mtxmgr->mutex_array = R_ADDR(&mtxmgr->reginfo, mtxregion->mutex_off); + + /* + * This is a little strange, but the mutex_handle is what all of the + * underlying mutex routines will use to determine if they should do + * any work and to find their information. Save/restore the handle + * around the work loop. + * + * The OOB mutex (MUTEX_INVALID) is 0, skip it. + */ + orig_handle = env->mutex_handle; + env->mutex_handle = mtxmgr; + for (i = 1; i <= mtxregion->stat.st_mutex_cnt; ++i, ++mutexp) { + mutexp = MUTEXP_SET(mtxmgr, i); + if (F_ISSET(mutexp, DB_MUTEX_ALLOCATED)) + (void)__mutex_destroy(env, i); + } + env->mutex_handle = orig_handle; +} +#endif diff --git a/db-4.8.30/mutex/mut_stat.c b/db-4.8.30/mutex/mut_stat.c new file mode 100644 index 0000000..ecf6a7b --- /dev/null +++ b/db-4.8.30/mutex/mut_stat.c @@ -0,0 +1,521 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" +#include "dbinc/db_page.h" +#include "dbinc/db_am.h" + +#ifdef HAVE_STATISTICS +static int __mutex_print_all __P((ENV *, u_int32_t)); +static const char *__mutex_print_id __P((int)); +static int __mutex_print_stats __P((ENV *, u_int32_t)); +static void __mutex_print_summary __P((ENV *)); +static int __mutex_stat __P((ENV *, DB_MUTEX_STAT **, u_int32_t)); + +/* + * __mutex_stat_pp -- + * ENV->mutex_stat pre/post processing. + * + * PUBLIC: int __mutex_stat_pp __P((DB_ENV *, DB_MUTEX_STAT **, u_int32_t)); + */ +int +__mutex_stat_pp(dbenv, statp, flags) + DB_ENV *dbenv; + DB_MUTEX_STAT **statp; + u_int32_t flags; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if ((ret = __db_fchk(env, + "DB_ENV->mutex_stat", flags, DB_STAT_CLEAR)) != 0) + return (ret); + + ENV_ENTER(env, ip); + REPLICATION_WRAP(env, (__mutex_stat(env, statp, flags)), 0, ret); + ENV_LEAVE(env, ip); + return (ret); +} + +/* + * __mutex_stat -- + * ENV->mutex_stat. + */ +static int +__mutex_stat(env, statp, flags) + ENV *env; + DB_MUTEX_STAT **statp; + u_int32_t flags; +{ + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + DB_MUTEX_STAT *stats; + int ret; + + *statp = NULL; + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + + if ((ret = __os_umalloc(env, sizeof(DB_MUTEX_STAT), &stats)) != 0) + return (ret); + + MUTEX_SYSTEM_LOCK(env); + + /* + * Most fields are maintained in the underlying region structure. + * Region size and region mutex are not. + */ + *stats = mtxregion->stat; + stats->st_regsize = mtxmgr->reginfo.rp->size; + __mutex_set_wait_info(env, mtxregion->mtx_region, + &stats->st_region_wait, &stats->st_region_nowait); + if (LF_ISSET(DB_STAT_CLEAR)) + __mutex_clear(env, mtxregion->mtx_region); + + MUTEX_SYSTEM_UNLOCK(env); + + *statp = stats; + return (0); +} + +/* + * __mutex_stat_print_pp -- + * ENV->mutex_stat_print pre/post processing. + * + * PUBLIC: int __mutex_stat_print_pp __P((DB_ENV *, u_int32_t)); + */ +int +__mutex_stat_print_pp(dbenv, flags) + DB_ENV *dbenv; + u_int32_t flags; +{ + DB_THREAD_INFO *ip; + ENV *env; + int ret; + + env = dbenv->env; + + if ((ret = __db_fchk(env, "DB_ENV->mutex_stat_print", + flags, DB_STAT_ALL | DB_STAT_CLEAR)) != 0) + return (ret); + + ENV_ENTER(env, ip); + REPLICATION_WRAP(env, (__mutex_stat_print(env, flags)), 0, ret); + ENV_LEAVE(env, ip); + return (ret); +} + +/* + * __mutex_stat_print + * ENV->mutex_stat_print method. + * + * PUBLIC: int __mutex_stat_print __P((ENV *, u_int32_t)); + */ +int +__mutex_stat_print(env, flags) + ENV *env; + u_int32_t flags; +{ + u_int32_t orig_flags; + int ret; + + orig_flags = flags; + LF_CLR(DB_STAT_CLEAR | DB_STAT_SUBSYSTEM); + if (flags == 0 || LF_ISSET(DB_STAT_ALL)) { + ret = __mutex_print_stats(env, orig_flags); + __mutex_print_summary(env); + if (flags == 0 || ret != 0) + return (ret); + } + + if (LF_ISSET(DB_STAT_ALL)) + ret = __mutex_print_all(env, orig_flags); + + return (0); +} + +static void +__mutex_print_summary(env) + ENV *env; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + db_mutex_t i; + u_int32_t counts[MTX_MAX_ENTRY + 2]; + int alloc_id; + + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + memset(counts, 0, sizeof(counts)); + + for (i = 1; i <= mtxregion->stat.st_mutex_cnt; ++i, ++mutexp) { + mutexp = MUTEXP_SET(mtxmgr, i); + + if (!F_ISSET(mutexp, DB_MUTEX_ALLOCATED)) + counts[0]++; + else if (mutexp->alloc_id > MTX_MAX_ENTRY) + counts[MTX_MAX_ENTRY + 1]++; + else + counts[mutexp->alloc_id]++; + } + __db_msg(env, "Mutex counts"); + __db_msg(env, "%d\tUnallocated", counts[0]); + for (alloc_id = 1; alloc_id <= MTX_TXN_REGION + 1; alloc_id++) + if (counts[alloc_id] != 0) + __db_msg(env, "%lu\t%s", + (u_long)counts[alloc_id], + __mutex_print_id(alloc_id)); + +} + +/* + * __mutex_print_stats -- + * Display default mutex region statistics. + */ +static int +__mutex_print_stats(env, flags) + ENV *env; + u_int32_t flags; +{ + DB_MUTEX_STAT *sp; + int ret; + + if ((ret = __mutex_stat(env, &sp, LF_ISSET(DB_STAT_CLEAR))) != 0) + return (ret); + + if (LF_ISSET(DB_STAT_ALL)) + __db_msg(env, "Default mutex region information:"); + + __db_dlbytes(env, "Mutex region size", + (u_long)0, (u_long)0, (u_long)sp->st_regsize); + __db_dl_pct(env, + "The number of region locks that required waiting", + (u_long)sp->st_region_wait, DB_PCT(sp->st_region_wait, + sp->st_region_wait + sp->st_region_nowait), NULL); + STAT_ULONG("Mutex alignment", sp->st_mutex_align); + STAT_ULONG("Mutex test-and-set spins", sp->st_mutex_tas_spins); + STAT_ULONG("Mutex total count", sp->st_mutex_cnt); + STAT_ULONG("Mutex free count", sp->st_mutex_free); + STAT_ULONG("Mutex in-use count", sp->st_mutex_inuse); + STAT_ULONG("Mutex maximum in-use count", sp->st_mutex_inuse_max); + + __os_ufree(env, sp); + + return (0); +} + +/* + * __mutex_print_all -- + * Display debugging mutex region statistics. + */ +static int +__mutex_print_all(env, flags) + ENV *env; + u_int32_t flags; +{ + static const FN fn[] = { + { DB_MUTEX_ALLOCATED, "alloc" }, + { DB_MUTEX_LOCKED, "locked" }, + { DB_MUTEX_LOGICAL_LOCK, "logical" }, + { DB_MUTEX_PROCESS_ONLY, "process-private" }, + { DB_MUTEX_SELF_BLOCK, "self-block" }, + { 0, NULL } + }; + DB_MSGBUF mb, *mbp; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + db_mutex_t i; + + DB_MSGBUF_INIT(&mb); + mbp = &mb; + + mtxmgr = env->mutex_handle; + mtxregion = mtxmgr->reginfo.primary; + + __db_print_reginfo(env, &mtxmgr->reginfo, "Mutex", flags); + __db_msg(env, "%s", DB_GLOBAL(db_line)); + + __db_msg(env, "DB_MUTEXREGION structure:"); + __mutex_print_debug_single(env, + "DB_MUTEXREGION region mutex", mtxregion->mtx_region, flags); + STAT_ULONG("Size of the aligned mutex", mtxregion->mutex_size); + STAT_ULONG("Next free mutex", mtxregion->mutex_next); + + /* + * The OOB mutex (MUTEX_INVALID) is 0, skip it. + * + * We're not holding the mutex region lock, so we're racing threads of + * control allocating mutexes. That's OK, it just means we display or + * clear statistics while mutexes are moving. + */ + __db_msg(env, "%s", DB_GLOBAL(db_line)); + __db_msg(env, "mutex\twait/nowait, pct wait, holder, flags"); + for (i = 1; i <= mtxregion->stat.st_mutex_cnt; ++i, ++mutexp) { + mutexp = MUTEXP_SET(mtxmgr, i); + + if (!F_ISSET(mutexp, DB_MUTEX_ALLOCATED)) + continue; + + __db_msgadd(env, mbp, "%5lu\t", (u_long)i); + + __mutex_print_debug_stats(env, mbp, i, flags); + + if (mutexp->alloc_id != 0) + __db_msgadd(env, + mbp, ", %s", __mutex_print_id(mutexp->alloc_id)); + + __db_prflags(env, mbp, mutexp->flags, fn, " (", ")"); + + DB_MSGBUF_FLUSH(env, mbp); + } + + return (0); +} + +/* + * __mutex_print_debug_single -- + * Print mutex internal debugging statistics for a single mutex on a + * single output line. + * + * PUBLIC: void __mutex_print_debug_single + * PUBLIC: __P((ENV *, const char *, db_mutex_t, u_int32_t)); + */ +void +__mutex_print_debug_single(env, tag, mutex, flags) + ENV *env; + const char *tag; + db_mutex_t mutex; + u_int32_t flags; +{ + DB_MSGBUF mb, *mbp; + + DB_MSGBUF_INIT(&mb); + mbp = &mb; + + if (LF_ISSET(DB_STAT_SUBSYSTEM)) + LF_CLR(DB_STAT_CLEAR); + __db_msgadd(env, mbp, "%lu\t%s ", (u_long)mutex, tag); + __mutex_print_debug_stats(env, mbp, mutex, flags); + DB_MSGBUF_FLUSH(env, mbp); +} + +/* + * __mutex_print_debug_stats -- + * Print mutex internal debugging statistics, that is, the statistics + * in the [] square brackets. + * + * PUBLIC: void __mutex_print_debug_stats + * PUBLIC: __P((ENV *, DB_MSGBUF *, db_mutex_t, u_int32_t)); + */ +void +__mutex_print_debug_stats(env, mbp, mutex, flags) + ENV *env; + DB_MSGBUF *mbp; + db_mutex_t mutex; + u_int32_t flags; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + u_long value; + char buf[DB_THREADID_STRLEN]; +#if defined(HAVE_SHARED_LATCHES) && defined(HAVE_MUTEX_HYBRID) + int sharecount; +#endif + + if (mutex == MUTEX_INVALID) { + __db_msgadd(env, mbp, "[!Set]"); + return; + } + + dbenv = env->dbenv; + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + __db_msgadd(env, mbp, "["); + if ((value = mutexp->mutex_set_wait) < 10000000) + __db_msgadd(env, mbp, "%lu", value); + else + __db_msgadd(env, mbp, "%luM", value / 1000000); + if ((value = mutexp->mutex_set_nowait) < 10000000) + __db_msgadd(env, mbp, "/%lu", value); + else + __db_msgadd(env, mbp, "/%luM", value / 1000000); + + __db_msgadd(env, mbp, " %d%% ", + DB_PCT(mutexp->mutex_set_wait, + mutexp->mutex_set_wait + mutexp->mutex_set_nowait)); + +#if defined(HAVE_SHARED_LATCHES) + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) { + __db_msgadd(env, mbp, " rd "); + if ((value = mutexp->mutex_set_rd_wait) < 10000000) + __db_msgadd(env, mbp, "%lu", value); + else + __db_msgadd(env, mbp, "%luM", value / 1000000); + if ((value = mutexp->mutex_set_rd_nowait) < 10000000) + __db_msgadd(env, mbp, "/%lu", value); + else + __db_msgadd(env, mbp, "/%luM", value / 1000000); + __db_msgadd(env, mbp, " %d%% ", + DB_PCT(mutexp->mutex_set_rd_wait, + mutexp->mutex_set_rd_wait + mutexp->mutex_set_rd_nowait)); + } +#endif + + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + __db_msgadd(env, mbp, "%s]", + dbenv->thread_id_string(dbenv, + mutexp->pid, mutexp->tid, buf)); + /* Only hybrid shared latches expose the share count. */ +#if defined(HAVE_SHARED_LATCHES) && defined(HAVE_MUTEX_HYBRID) + else if (F_ISSET(mutexp, DB_MUTEX_SHARED) && + (sharecount = atomic_read(&mutexp->sharecount)) != 0) { + if (sharecount == 1) + __db_msgadd(env, mbp, "1 reader"); + else + __db_msgadd(env, mbp, "%d readers", sharecount); + /* Show the thread which last acquired the latch. */ + __db_msgadd(env, mbp, "%s]", + dbenv->thread_id_string(dbenv, + mutexp->pid, mutexp->tid, buf)); + } +#endif + else + __db_msgadd(env, mbp, "!Own]"); + +#ifdef HAVE_MUTEX_HYBRID + if (mutexp->hybrid_wait != 0 || mutexp->hybrid_wakeup != 0) + __db_msgadd(env, mbp, " <wakeups %d/%d>", + mutexp->hybrid_wait, mutexp->hybrid_wakeup); +#endif + + if (LF_ISSET(DB_STAT_CLEAR)) + __mutex_clear(env, mutex); +} + +static const char * +__mutex_print_id(alloc_id) + int alloc_id; +{ + switch (alloc_id) { + case MTX_APPLICATION: return ("application allocated"); + case MTX_ATOMIC_EMULATION: return ("atomic emulation"); + case MTX_DB_HANDLE: return ("db handle"); + case MTX_ENV_DBLIST: return ("env dblist"); + case MTX_ENV_HANDLE: return ("env handle"); + case MTX_ENV_REGION: return ("env region"); + case MTX_LOCK_REGION: return ("lock region"); + case MTX_LOGICAL_LOCK: return ("logical lock"); + case MTX_LOG_FILENAME: return ("log filename"); + case MTX_LOG_FLUSH: return ("log flush"); + case MTX_LOG_HANDLE: return ("log handle"); + case MTX_LOG_REGION: return ("log region"); + case MTX_MPOOLFILE_HANDLE: return ("mpoolfile handle"); + case MTX_MPOOL_BH: return ("mpool buffer"); + case MTX_MPOOL_FH: return ("mpool filehandle"); + case MTX_MPOOL_FILE_BUCKET: return ("mpool file bucket"); + case MTX_MPOOL_HANDLE: return ("mpool handle"); + case MTX_MPOOL_HASH_BUCKET: return ("mpool hash bucket"); + case MTX_MPOOL_REGION: return ("mpool region"); + case MTX_MUTEX_REGION: return ("mutex region"); + case MTX_MUTEX_TEST: return ("mutex test"); + case MTX_REPMGR: return ("replication manager"); + case MTX_REP_CHKPT: return ("replication checkpoint"); + case MTX_REP_DATABASE: return ("replication database"); + case MTX_REP_EVENT: return ("replication event"); + case MTX_REP_REGION: return ("replication region"); + case MTX_SEQUENCE: return ("sequence"); + case MTX_TWISTER: return ("twister"); + case MTX_TXN_ACTIVE: return ("txn active list"); + case MTX_TXN_CHKPT: return ("transaction checkpoint"); + case MTX_TXN_COMMIT: return ("txn commit"); + case MTX_TXN_MVCC: return ("txn mvcc"); + case MTX_TXN_REGION: return ("txn region"); + default: return ("unknown mutex type"); + /* NOTREACHED */ + } +} + +/* + * __mutex_set_wait_info -- + * Return mutex statistics. + * + * PUBLIC: void __mutex_set_wait_info + * PUBLIC: __P((ENV *, db_mutex_t, uintmax_t *, uintmax_t *)); + */ +void +__mutex_set_wait_info(env, mutex, waitp, nowaitp) + ENV *env; + db_mutex_t mutex; + uintmax_t *waitp, *nowaitp; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + *waitp = mutexp->mutex_set_wait; + *nowaitp = mutexp->mutex_set_nowait; +} + +/* + * __mutex_clear -- + * Clear mutex statistics. + * + * PUBLIC: void __mutex_clear __P((ENV *, db_mutex_t)); + */ +void +__mutex_clear(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + mutexp->mutex_set_wait = mutexp->mutex_set_nowait = 0; +#ifdef HAVE_MUTEX_HYBRID + mutexp->hybrid_wait = mutexp->hybrid_wakeup = 0; +#endif +} + +#else /* !HAVE_STATISTICS */ + +int +__mutex_stat_pp(dbenv, statp, flags) + DB_ENV *dbenv; + DB_MUTEX_STAT **statp; + u_int32_t flags; +{ + COMPQUIET(statp, NULL); + COMPQUIET(flags, 0); + + return (__db_stat_not_built(dbenv->env)); +} + +int +__mutex_stat_print_pp(dbenv, flags) + DB_ENV *dbenv; + u_int32_t flags; +{ + COMPQUIET(flags, 0); + + return (__db_stat_not_built(dbenv->env)); +} +#endif diff --git a/db-4.8.30/mutex/mut_stub.c b/db-4.8.30/mutex/mut_stub.c new file mode 100644 index 0000000..d988e45 --- /dev/null +++ b/db-4.8.30/mutex/mut_stub.c @@ -0,0 +1,233 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#ifndef HAVE_MUTEX_SUPPORT +#include "db_config.h" + +#include "db_int.h" +#include "dbinc/db_page.h" +#include "dbinc/db_am.h" + +/* + * If the library wasn't compiled with mutex support, various routines + * aren't available. Stub them here, returning an appropriate error. + */ +static int __db_nomutex __P((ENV *)); + +/* + * __db_nomutex -- + * Error when a Berkeley DB build doesn't include mutexes. + */ +static int +__db_nomutex(env) + ENV *env; +{ + __db_errx(env, "library build did not include support for mutexes"); + return (DB_OPNOTSUP); +} + +int +__mutex_alloc_pp(dbenv, flags, indxp) + DB_ENV *dbenv; + u_int32_t flags; + db_mutex_t *indxp; +{ + COMPQUIET(flags, 0); + COMPQUIET(indxp, NULL); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_alloc(env, alloc_id, flags, indxp) + ENV *env; + int alloc_id; + u_int32_t flags; + db_mutex_t *indxp; +{ + COMPQUIET(env, NULL); + COMPQUIET(alloc_id, 0); + COMPQUIET(flags, 0); + *indxp = MUTEX_INVALID; + return (0); +} + +void +__mutex_clear(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + COMPQUIET(env, NULL); + COMPQUIET(mutex, MUTEX_INVALID); +} + +int +__mutex_free_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + COMPQUIET(indx, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_free(env, indxp) + ENV *env; + db_mutex_t *indxp; +{ + COMPQUIET(env, NULL); + *indxp = MUTEX_INVALID; + return (0); +} + +int +__mutex_get_align(dbenv, alignp) + DB_ENV *dbenv; + u_int32_t *alignp; +{ + COMPQUIET(alignp, NULL); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_get_increment(dbenv, incrementp) + DB_ENV *dbenv; + u_int32_t *incrementp; +{ + COMPQUIET(incrementp, NULL); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_get_max(dbenv, maxp) + DB_ENV *dbenv; + u_int32_t *maxp; +{ + COMPQUIET(maxp, NULL); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_get_tas_spins(dbenv, tas_spinsp) + DB_ENV *dbenv; + u_int32_t *tas_spinsp; +{ + COMPQUIET(tas_spinsp, NULL); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_lock_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + COMPQUIET(indx, 0); + return (__db_nomutex(dbenv->env)); +} + +void +__mutex_print_debug_single(env, tag, mutex, flags) + ENV *env; + const char *tag; + db_mutex_t mutex; + u_int32_t flags; +{ + COMPQUIET(env, NULL); + COMPQUIET(tag, NULL); + COMPQUIET(mutex, MUTEX_INVALID); + COMPQUIET(flags, 0); +} + +void +__mutex_print_debug_stats(env, mbp, mutex, flags) + ENV *env; + DB_MSGBUF *mbp; + db_mutex_t mutex; + u_int32_t flags; +{ + COMPQUIET(env, NULL); + COMPQUIET(mbp, NULL); + COMPQUIET(mutex, MUTEX_INVALID); + COMPQUIET(flags, 0); +} + +int +__mutex_set_align(dbenv, align) + DB_ENV *dbenv; + u_int32_t align; +{ + COMPQUIET(align, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_set_increment(dbenv, increment) + DB_ENV *dbenv; + u_int32_t increment; +{ + COMPQUIET(increment, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_set_max(dbenv, max) + DB_ENV *dbenv; + u_int32_t max; +{ + COMPQUIET(max, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_set_tas_spins(dbenv, tas_spins) + DB_ENV *dbenv; + u_int32_t tas_spins; +{ + COMPQUIET(tas_spins, 0); + return (__db_nomutex(dbenv->env)); +} + +void +__mutex_set_wait_info(env, mutex, waitp, nowaitp) + ENV *env; + db_mutex_t mutex; + uintmax_t *waitp, *nowaitp; +{ + COMPQUIET(env, NULL); + COMPQUIET(mutex, MUTEX_INVALID); + *waitp = *nowaitp = 0; +} + +int +__mutex_stat_pp(dbenv, statp, flags) + DB_ENV *dbenv; + DB_MUTEX_STAT **statp; + u_int32_t flags; +{ + COMPQUIET(statp, NULL); + COMPQUIET(flags, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_stat_print_pp(dbenv, flags) + DB_ENV *dbenv; + u_int32_t flags; +{ + COMPQUIET(flags, 0); + return (__db_nomutex(dbenv->env)); +} + +int +__mutex_unlock_pp(dbenv, indx) + DB_ENV *dbenv; + db_mutex_t indx; +{ + COMPQUIET(indx, 0); + return (__db_nomutex(dbenv->env)); +} +#endif /* !HAVE_MUTEX_SUPPORT */ diff --git a/db-4.8.30/mutex/mut_tas.c b/db-4.8.30/mutex/mut_tas.c new file mode 100644 index 0000000..f3922e0 --- /dev/null +++ b/db-4.8.30/mutex/mut_tas.c @@ -0,0 +1,560 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2009 Oracle. All rights reserved. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +static inline int __db_tas_mutex_lock_int __P((ENV *, db_mutex_t, int)); +static inline int __db_tas_mutex_readlock_int __P((ENV *, db_mutex_t, int)); + +/* + * __db_tas_mutex_init -- + * Initialize a test-and-set mutex. + * + * PUBLIC: int __db_tas_mutex_init __P((ENV *, db_mutex_t, u_int32_t)); + */ +int +__db_tas_mutex_init(env, mutex, flags) + ENV *env; + db_mutex_t mutex; + u_int32_t flags; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + int ret; + +#ifndef HAVE_MUTEX_HYBRID + COMPQUIET(flags, 0); +#endif + + dbenv = env->dbenv; + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + /* Check alignment. */ + if (((uintptr_t)mutexp & (dbenv->mutex_align - 1)) != 0) { + __db_errx(env, "TAS: mutex not appropriately aligned"); + return (EINVAL); + } + +#ifdef HAVE_SHARED_LATCHES + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) + atomic_init(&mutexp->sharecount, 0); + else +#endif + if (MUTEX_INIT(&mutexp->tas)) { + ret = __os_get_syserr(); + __db_syserr(env, ret, "TAS: mutex initialize"); + return (__os_posix_err(ret)); + } +#ifdef HAVE_MUTEX_HYBRID + if ((ret = __db_pthread_mutex_init(env, + mutex, flags | DB_MUTEX_SELF_BLOCK)) != 0) + return (ret); +#endif + return (0); +} + +/* + * __db_tas_mutex_lock_int + * Internal function to lock a mutex, or just try to lock it without waiting + */ +static inline int +__db_tas_mutex_lock_int(env, mutex, nowait) + ENV *env; + db_mutex_t mutex; + int nowait; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + DB_THREAD_INFO *ip; + u_int32_t nspins; + int ret; +#ifndef HAVE_MUTEX_HYBRID + u_long ms, max_ms; +#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(mtxmgr, mutex); + + CHECK_MTX_THREAD(env, mutexp); + +#ifdef HAVE_STATISTICS + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + ++mutexp->mutex_set_wait; + else + ++mutexp->mutex_set_nowait; +#endif + +#ifndef HAVE_MUTEX_HYBRID + /* + * Wait 1ms initially, up to 10ms for mutexes backing logical database + * locks, and up to 25 ms for mutual exclusion data structure mutexes. + * SR: #7675 + */ + ms = 1; + max_ms = F_ISSET(mutexp, DB_MUTEX_LOGICAL_LOCK) ? 10 : 25; +#endif + + /* + * 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 resource for N spins. */ + for (nspins = + mtxregion->stat.st_mutex_tas_spins; nspins > 0; --nspins) { +#ifdef HAVE_MUTEX_S390_CC_ASSEMBLY + tsl_t zero; + + zero = 0; +#endif + + dbenv = env->dbenv; + +#ifdef HAVE_MUTEX_HPPA_MSEM_INIT + relock: +#endif + /* + * Avoid interlocked instructions until they're likely to + * succeed by first checking whether it is held + */ + 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 (nowait) + return (DB_LOCK_NOTGRANTED); + /* + * Some systems (notably those with newer Intel CPUs) + * need a small pause here. [#6975] + */ + MUTEX_PAUSE + continue; + } + + MEMBAR_ENTER(); + +#ifdef HAVE_MUTEX_HPPA_MSEM_INIT + /* + * HP semaphores are unlocked automatically when a holding + * process exits. If the mutex appears to be locked + * (F_ISSET(DB_MUTEX_LOCKED)) but we got here, assume this + * has happened. Set the pid and tid into the mutex and + * lock again. (The default state of the mutexes used to + * block in __lock_get_internal is locked, so exiting with + * a locked mutex is reasonable behavior for a process that + * happened to initialize or use one of them.) + */ + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid); + goto relock; + } + /* + * If we make it here, the mutex isn't locked, the diagnostic + * won't fire, and we were really unlocked by someone calling + * the DB mutex unlock function. + */ +#endif +#ifdef DIAGNOSTIC + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + char buf[DB_THREADID_STRLEN]; + __db_errx(env, + "TAS lock failed: lock %d currently in use: ID: %s", + mutex, 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 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); + } + + /* Wait for the lock to become available. */ +#ifdef HAVE_MUTEX_HYBRID + /* + * By yielding here we can get the other thread to give up the + * mutex before calling the more expensive library mutex call. + * Tests have shown this to be a big win when there is contention. + * With shared latches check the locked bit only after checking + * that no one has the latch in shared mode. + */ + __os_yield(env, 0, 0); + if (!MUTEXP_IS_BUSY(mutexp)) + goto loop; + if ((ret = __db_pthread_mutex_lock(env, mutex)) != 0) + return (ret); +#else + __os_yield(env, 0, ms * US_PER_MS); + if ((ms <<= 1) > max_ms) + ms = max_ms; +#endif + + /* + * We're spinning. The environment might be hung, and somebody else + * has already recovered it. The first thing recovery does is panic + * the environment. Check to see if we're never going to get this + * mutex. + */ + PANIC_CHECK(env); + + goto loop; +} + +/* + * __db_tas_mutex_lock + * Lock on a mutex, blocking if necessary. + * + * PUBLIC: int __db_tas_mutex_lock __P((ENV *, db_mutex_t)); + */ +int +__db_tas_mutex_lock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_tas_mutex_lock_int(env, mutex, 0)); +} + +/* + * __db_tas_mutex_trylock + * Try to exclusively lock a mutex without ever blocking - ever! + * + * Returns 0 on success, + * DB_LOCK_NOTGRANTED on timeout + * Possibly DB_RUNRECOVERY if DB_ENV_FAILCHK or panic. + * + * This will work for DB_MUTEX_SHARED, though it always tries + * for exclusive access. + * + * PUBLIC: int __db_tas_mutex_trylock __P((ENV *, db_mutex_t)); + */ +int +__db_tas_mutex_trylock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_tas_mutex_lock_int(env, mutex, 1)); +} + +#if defined(HAVE_SHARED_LATCHES) +/* + * __db_tas_mutex_readlock_int + * Internal function to get a shared lock on a latch, blocking if necessary. + * + */ +static inline int +__db_tas_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; + DB_THREAD_INFO *ip; + int lock; + u_int32_t nspins; + int ret; +#ifndef HAVE_MUTEX_HYBRID + u_long ms, max_ms; +#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(mtxmgr, mutex); + + CHECK_MTX_THREAD(env, mutexp); + + DB_ASSERT(env, F_ISSET(mutexp, DB_MUTEX_SHARED)); +#ifdef HAVE_STATISTICS + if (F_ISSET(mutexp, DB_MUTEX_LOCKED)) + ++mutexp->mutex_set_rd_wait; + else + ++mutexp->mutex_set_rd_nowait; +#endif + +#ifndef HAVE_MUTEX_HYBRID + /* + * Wait 1ms initially, up to 10ms for mutexes backing logical database + * locks, and up to 25 ms for mutual exclusion data structure mutexes. + * SR: #7675 + */ + ms = 1; + max_ms = F_ISSET(mutexp, DB_MUTEX_LOGICAL_LOCK) ? 10 : 25; +#endif + /* + * 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 resource for N spins. */ + for (nspins = + mtxregion->stat.st_mutex_tas_spins; nspins > 0; --nspins) { + lock = atomic_read(&mutexp->sharecount); + if (lock == MUTEX_SHARE_ISEXCLUSIVE || + !atomic_compare_exchange(env, + &mutexp->sharecount, lock, lock + 1)) { + 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 (nowait) + return (DB_LOCK_NOTGRANTED); + /* + * Some systems (notably those with newer Intel CPUs) + * need a small pause here. [#6975] + */ + MUTEX_PAUSE + continue; + } + + MEMBAR_ENTER(); + /* For shared lactches the threadid is the last requestor's id. + */ + dbenv->thread_id(dbenv, &mutexp->pid, &mutexp->tid); + + return (0); + } + + /* Wait for the lock to become available. */ +#ifdef HAVE_MUTEX_HYBRID + /* + * By yielding here we can get the other thread to give up the + * mutex before calling the more expensive library mutex call. + * Tests have shown this to be a big win when there is contention. + */ + __os_yield(env, 0, 0); + if (atomic_read(&mutexp->sharecount) != MUTEX_SHARE_ISEXCLUSIVE) + goto loop; + if ((ret = __db_pthread_mutex_lock(env, mutex)) != 0) + return (ret); +#else + __os_yield(env, 0, ms * US_PER_MS); + if ((ms <<= 1) > max_ms) + ms = max_ms; +#endif + + /* + * We're spinning. The environment might be hung, and somebody else + * has already recovered it. The first thing recovery does is panic + * the environment. Check to see if we're never going to get this + * mutex. + */ + PANIC_CHECK(env); + + goto loop; +} + +/* + * __db_tas_mutex_readlock + * Get a shared lock on a latch, waiting if necessary. + * + * PUBLIC: #if defined(HAVE_SHARED_LATCHES) + * PUBLIC: int __db_tas_mutex_readlock __P((ENV *, db_mutex_t)); + * PUBLIC: #endif + */ +int +__db_tas_mutex_readlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_tas_mutex_readlock_int(env, mutex, 0)); +} + +/* + * __db_tas_mutex_tryreadlock + * Try to get a shared lock on a latch; don't wait when busy. + * + * PUBLIC: #if defined(HAVE_SHARED_LATCHES) + * PUBLIC: int __db_tas_mutex_tryreadlock __P((ENV *, db_mutex_t)); + * PUBLIC: #endif + */ +int +__db_tas_mutex_tryreadlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_tas_mutex_readlock_int(env, mutex, 1)); +} +#endif + +/* + * __db_tas_mutex_unlock -- + * Release a mutex. + * + * PUBLIC: int __db_tas_mutex_unlock __P((ENV *, db_mutex_t)); + * + * Hybrid shared latch wakeup + * When an exclusive requester waits for the last shared holder to + * release, it increments mutexp->wait and pthread_cond_wait()'s. The + * last shared unlock calls __db_pthread_mutex_unlock() to wake it. + */ +int +__db_tas_mutex_unlock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; +#ifdef HAVE_MUTEX_HYBRID + int ret; +#ifdef MUTEX_DIAG + int waiters; +#endif +#endif +#ifdef HAVE_SHARED_LATCHES + int sharecount; +#endif + dbenv = env->dbenv; + + if (!MUTEX_ON(env) || F_ISSET(dbenv, DB_ENV_NOLOCKING)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); +#if defined(HAVE_MUTEX_HYBRID) && defined(MUTEX_DIAG) + waiters = mutexp->wait; +#endif + +#if defined(DIAGNOSTIC) +#if defined(HAVE_SHARED_LATCHES) + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) { + if (atomic_read(&mutexp->sharecount) == 0) { + __db_errx(env, "shared unlock %d already unlocked", + mutex); + return (__env_panic(env, EACCES)); + } + } else +#endif + if (!F_ISSET(mutexp, DB_MUTEX_LOCKED)) { + __db_errx(env, "unlock %d already unlocked", mutex); + return (__env_panic(env, EACCES)); + } +#endif + +#ifdef HAVE_SHARED_LATCHES + if (F_ISSET(mutexp, DB_MUTEX_SHARED)) { + sharecount = atomic_read(&mutexp->sharecount); + /*MUTEX_MEMBAR(mutexp->sharecount);*/ /* XXX why? */ + if (sharecount == MUTEX_SHARE_ISEXCLUSIVE) { + F_CLR(mutexp, DB_MUTEX_LOCKED); + /* Flush flag update before zeroing count */ + MEMBAR_EXIT(); + atomic_init(&mutexp->sharecount, 0); + } else { + DB_ASSERT(env, sharecount > 0); + MEMBAR_EXIT(); + sharecount = atomic_dec(env, &mutexp->sharecount); + DB_ASSERT(env, sharecount >= 0); + if (sharecount > 0) + return (0); + } + } else +#endif + { + F_CLR(mutexp, DB_MUTEX_LOCKED); + MUTEX_UNSET(&mutexp->tas); + } + +#ifdef HAVE_MUTEX_HYBRID +#ifdef DIAGNOSTIC + if (F_ISSET(dbenv, DB_ENV_YIELDCPU)) + __os_yield(env, 0, 0); +#endif + + /* Prevent the load of wait from being hoisted before MUTEX_UNSET */ + MUTEX_MEMBAR(mutexp->flags); + if (mutexp->wait && + (ret = __db_pthread_mutex_unlock(env, mutex)) != 0) + return (ret); + +#ifdef MUTEX_DIAG + if (mutexp->wait) + printf("tas_unlock %d %x waiters! busy %x waiters %d/%d\n", + mutex, pthread_self(), + MUTEXP_BUSY_FIELD(mutexp), waiters, mutexp->wait); +#endif +#endif + + return (0); +} + +/* + * __db_tas_mutex_destroy -- + * Destroy a mutex. + * + * PUBLIC: int __db_tas_mutex_destroy __P((ENV *, db_mutex_t)); + */ +int +__db_tas_mutex_destroy(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; +#ifdef HAVE_MUTEX_HYBRID + int ret; +#endif + + if (!MUTEX_ON(env)) + return (0); + + mtxmgr = env->mutex_handle; + mutexp = MUTEXP_SET(mtxmgr, mutex); + + MUTEX_DESTROY(&mutexp->tas); + +#ifdef HAVE_MUTEX_HYBRID + if ((ret = __db_pthread_mutex_destroy(env, mutex)) != 0) + return (ret); +#endif + + COMPQUIET(mutexp, NULL); /* MUTEX_DESTROY may not be defined. */ + return (0); +} diff --git a/db-4.8.30/mutex/mut_win32.c b/db-4.8.30/mutex/mut_win32.c new file mode 100644 index 0000000..20987a1 --- /dev/null +++ b/db-4.8.30/mutex/mut_win32.c @@ -0,0 +1,540 @@ +/* + * See the file LICENSE for redistribution information. + * + * Copyright (c) 2002-2009 Oracle. 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 test-and-set mutex code. + */ +#include "dbinc/mutex_int.h" + +/* We don't want to run this code even in "ordinary" diagnostic mode. */ +#undef MUTEX_DIAG + +/* + * 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 SECURITY_DESCRIPTOR null_sd; +static SECURITY_ATTRIBUTES all_sa; +static int security_initialized = 0; + +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 (!security_initialized) { + InitializeSecurityDescriptor(&null_sd, + SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&null_sd, TRUE, 0, FALSE); + all_sa.nLength = sizeof(SECURITY_ATTRIBUTES); + all_sa.bInheritHandle = FALSE; + all_sa.lpSecurityDescriptor = &null_sd; + security_initialized = 1; + } +#endif + + if ((*eventp = CreateEvent(&all_sa, FALSE, FALSE, idbuf)) == NULL) { + ret = __os_get_syserr(); + __db_syserr(env, ret, "Win32 create event failed"); + } + + return (ret); +} + +/* + * __db_win32_mutex_lock_int + * Internal function to lock a win32 mutex + * + * If the wait paramter is 0, this function will return DB_LOCK_NOTGRANTED + * rather than wait. + * + */ +static __inline int +__db_win32_mutex_lock_int(env, mutex, wait) + ENV *env; + db_mutex_t mutex; + int wait; +{ + DB_ENV *dbenv; + DB_MUTEX *mutexp; + DB_MUTEXMGR *mtxmgr; + DB_MUTEXREGION *mtxregion; + DB_THREAD_INFO *ip; + HANDLE event; + u_int32_t nspins; + int ms, 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(mtxmgr, 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; + + /* + * 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, + "Win32 lock failed: mutex already locked by %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(&now); + printf("[%I64d]: Lost signal on mutex %p, " + "id %d, ms %d\n", + 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(&now); + printf("[%I64d]: Waiting on mutex %p, id %d\n", + 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, "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_handle, 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)); + */ +int +__db_win32_mutex_lock(env, mutex) + ENV *env; + db_mutex_t mutex; +{ + return (__db_win32_mutex_lock_int(env, mutex, 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 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(mtxmgr, 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(&now); + printf("[%I64d]: Lost signal on mutex %p, " + "id %d, ms %d\n", + 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(&now); + printf("[%I64d]: Waiting on mutex %p, id %d\n", + 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, "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; + DB_MUTEXMGR *mtxmgr; + HANDLE event; + 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; + mutexp = MUTEXP_SET(mtxmgr, mutex); + +#ifdef DIAGNOSTIC + if (!MUTEXP_IS_BUSY(mutexp) || !(F_ISSET(mutexp, DB_MUTEX_SHARED) || + F_ISSET(mutexp, DB_MUTEX_LOCKED))) { + __db_errx(env, + "Win32 unlock failed: lock already unlocked: mutex %d busy %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(&now); + printf("[%I64d]: Signalling mutex %p, id %d\n", + 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, "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); +} diff --git a/db-4.8.30/mutex/test_mutex.c b/db-4.8.30/mutex/test_mutex.c new file mode 100644 index 0000000..3804996 --- /dev/null +++ b/db-4.8.30/mutex/test_mutex.c @@ -0,0 +1,1051 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1999-2009 Oracle. All rights reserved. + * + * Standalone mutex tester for Berkeley DB mutexes. + * + * $Id$ + */ + +#include "db_config.h" + +#include "db_int.h" + +#ifdef DB_WIN32 +#define MUTEX_THREAD_TEST 1 + +extern int getopt(int, char * const *, const char *); + +typedef HANDLE os_pid_t; +typedef HANDLE os_thread_t; + +#define os_thread_create(thrp, attr, func, arg) \ + (((*(thrp) = CreateThread(NULL, 0, \ + (LPTHREAD_START_ROUTINE)(func), (arg), 0, NULL)) == NULL) ? -1 : 0) +#define os_thread_join(thr, statusp) \ + ((WaitForSingleObject((thr), INFINITE) == WAIT_OBJECT_0) && \ + GetExitCodeThread((thr), (LPDWORD)(statusp)) ? 0 : -1) +#define os_thread_self() GetCurrentThreadId() + +#else /* !DB_WIN32 */ + +#include <sys/wait.h> + +typedef pid_t os_pid_t; + +/* + * There's only one mutex implementation that can't support thread-level + * locking: UNIX/fcntl mutexes. + * + * The general Berkeley DB library configuration doesn't look for the POSIX + * pthread functions, with one exception -- pthread_yield. + * + * Use these two facts to decide if we're going to build with or without + * threads. + */ +#if !defined(HAVE_MUTEX_FCNTL) && defined(HAVE_PTHREAD_YIELD) +#define MUTEX_THREAD_TEST 1 + +#include <pthread.h> + +typedef pthread_t os_thread_t; + +#define os_thread_create(thrp, attr, func, arg) \ + pthread_create((thrp), (attr), (func), (arg)) +#define os_thread_join(thr, statusp) pthread_join((thr), (statusp)) +#define os_thread_self() pthread_self() +#endif /* HAVE_PTHREAD_YIELD */ +#endif /* !DB_WIN32 */ + +#define OS_BAD_PID ((os_pid_t)-1) + +#define TESTDIR "TESTDIR" /* Working area */ +#define MT_FILE "TESTDIR/mutex.file" +#define MT_FILE_QUIT "TESTDIR/mutex.file.quit" + +/* + * The backing data layout: + * TM[1] per-thread mutex array lock + * TM[nthreads] per-thread mutex array + * TM[maxlocks] per-lock mutex array + */ +typedef struct { + db_mutex_t mutex; /* Mutex. */ + u_long id; /* Holder's ID. */ + u_int wakeme; /* Request to awake. */ +} TM; + +DB_ENV *dbenv; /* Backing environment */ +ENV *env; +size_t len; /* Backing data chunk size. */ + +u_int8_t *gm_addr; /* Global mutex */ +u_int8_t *lm_addr; /* Locker mutexes */ +u_int8_t *tm_addr; /* Thread mutexes */ + +#ifdef MUTEX_THREAD_TEST +os_thread_t *kidsp; /* Locker threads */ +os_thread_t wakep; /* Wakeup thread */ +#endif + +#ifndef HAVE_MMAP +u_int nprocs = 1; /* -p: Processes. */ +u_int nthreads = 20; /* -t: Threads. */ +#elif MUTEX_THREAD_TEST +u_int nprocs = 5; /* -p: Processes. */ +u_int nthreads = 4; /* -t: Threads. */ +#else +u_int nprocs = 20; /* -p: Processes. */ +u_int nthreads = 1; /* -t: Threads. */ +#endif + +u_int maxlocks = 20; /* -l: Backing locks. */ +u_int nlocks = 10000; /* -n: Locks per process. */ +int verbose; /* -v: Verbosity. */ + +const char *progname; + +void data_off(u_int8_t *, DB_FH *); +void data_on(u_int8_t **, u_int8_t **, u_int8_t **, DB_FH **, int); +int locker_start(u_long); +int locker_wait(void); +os_pid_t os_spawn(const char *, char *const[]); +int os_wait(os_pid_t *, u_int); +void *run_lthread(void *); +void *run_wthread(void *); +os_pid_t spawn_proc(u_long, char *, char *); +void tm_env_close(void); +int tm_env_init(void); +void tm_mutex_destroy(void); +void tm_mutex_init(void); +void tm_mutex_stats(void); +int usage(void); +int wakeup_start(u_long); +int wakeup_wait(void); + +int +main(argc, argv) + int argc; + char *argv[]; +{ + enum {LOCKER, WAKEUP, PARENT} rtype; + extern int optind; + extern char *optarg; + os_pid_t wakeup_pid, *pids; + u_long id; + u_int i; + DB_FH *fhp, *map_fhp; + int ch, err; + char *p, *tmpath, cmd[1024]; + + if ((progname = __db_rpath(argv[0])) == NULL) + progname = argv[0]; + else + ++progname; + + rtype = PARENT; + id = 0; + tmpath = argv[0]; + while ((ch = getopt(argc, argv, "l:n:p:T:t:v")) != EOF) + switch (ch) { + case 'l': + maxlocks = (u_int)atoi(optarg); + break; + case 'n': + nlocks = (u_int)atoi(optarg); + break; + case 'p': + nprocs = (u_int)atoi(optarg); + break; + case 't': + if ((nthreads = (u_int)atoi(optarg)) == 0) + nthreads = 1; +#if !defined(MUTEX_THREAD_TEST) + if (nthreads != 1) { + fprintf(stderr, + "%s: thread support not available or not compiled for this platform.\n", + progname); + return (EXIT_FAILURE); + } +#endif + break; + case 'T': + if (!memcmp(optarg, "locker", sizeof("locker") - 1)) + rtype = LOCKER; + else if ( + !memcmp(optarg, "wakeup", sizeof("wakeup") - 1)) + rtype = WAKEUP; + else + return (usage()); + if ((p = strchr(optarg, '=')) == NULL) + return (usage()); + id = (u_long)atoi(p + 1); + break; + case 'v': + verbose = 1; + break; + case '?': + default: + return (usage()); + } + argc -= optind; + argv += optind; + + /* + * If we're not running a multi-process test, we should be running + * a multi-thread test. + */ + if (nprocs == 1 && nthreads == 1) { + fprintf(stderr, + "%s: running in a single process requires multiple threads\n", + progname); + return (EXIT_FAILURE); + } + + len = sizeof(TM) * (1 + nthreads * nprocs + maxlocks); + + /* + * In the multi-process test, the parent spawns processes that exec + * the original binary, ending up here. Each process joins the DB + * environment separately and then calls the supporting function. + */ + if (rtype == LOCKER || rtype == WAKEUP) { + __os_yield(env, 3, 0); /* Let everyone catch up. */ + /* Initialize random numbers. */ + srand((u_int)time(NULL) % (u_int)getpid()); + + if (tm_env_init() != 0) /* Join the environment. */ + exit(EXIT_FAILURE); + /* Join the backing data. */ + data_on(&gm_addr, &tm_addr, &lm_addr, &map_fhp, 0); + if (verbose) + printf( + "Backing file: global (%#lx), threads (%#lx), locks (%#lx)\n", + (u_long)gm_addr, (u_long)tm_addr, (u_long)lm_addr); + + if ((rtype == LOCKER ? + locker_start(id) : wakeup_start(id)) != 0) + exit(EXIT_FAILURE); + if ((rtype == LOCKER ? locker_wait() : wakeup_wait()) != 0) + exit(EXIT_FAILURE); + + data_off(gm_addr, map_fhp); /* Detach from backing data. */ + + tm_env_close(); /* Detach from environment. */ + + exit(EXIT_SUCCESS); + } + + /* + * The following code is only executed by the original parent process. + * + * Clean up from any previous runs. + */ + snprintf(cmd, sizeof(cmd), "rm -rf %s", TESTDIR); + (void)system(cmd); + snprintf(cmd, sizeof(cmd), "mkdir %s", TESTDIR); + (void)system(cmd); + + printf( + "%s: %u processes, %u threads/process, %u lock requests from %u locks\n", + progname, nprocs, nthreads, nlocks, maxlocks); + printf("%s: backing data %lu bytes\n", progname, (u_long)len); + + if (tm_env_init() != 0) /* Create the environment. */ + exit(EXIT_FAILURE); + /* Create the backing data. */ + data_on(&gm_addr, &tm_addr, &lm_addr, &map_fhp, 1); + if (verbose) + printf( + "backing data: global (%#lx), threads (%#lx), locks (%#lx)\n", + (u_long)gm_addr, (u_long)tm_addr, (u_long)lm_addr); + + tm_mutex_init(); /* Initialize mutexes. */ + + if (nprocs > 1) { /* Run the multi-process test. */ + /* Allocate array of locker process IDs. */ + if ((pids = calloc(nprocs, sizeof(os_pid_t))) == NULL) { + fprintf(stderr, "%s: %s\n", progname, strerror(errno)); + goto fail; + } + + /* Spawn locker processes and threads. */ + for (i = 0; i < nprocs; ++i) { + if ((pids[i] = + spawn_proc(id, tmpath, "locker")) == OS_BAD_PID) { + fprintf(stderr, + "%s: failed to spawn a locker\n", progname); + goto fail; + } + id += nthreads; + } + + /* Spawn wakeup process/thread. */ + if ((wakeup_pid = + spawn_proc(id, tmpath, "wakeup")) == OS_BAD_PID) { + fprintf(stderr, + "%s: failed to spawn waker\n", progname); + goto fail; + } + ++id; + + /* Wait for all lockers to exit. */ + if ((err = os_wait(pids, nprocs)) != 0) { + fprintf(stderr, "%s: locker wait failed with %d\n", + progname, err); + goto fail; + } + + /* Signal wakeup process to exit. */ + if ((err = __os_open( + env, MT_FILE_QUIT, 0, DB_OSO_CREATE, 0664, &fhp)) != 0) { + fprintf(stderr, + "%s: open %s\n", progname, db_strerror(err)); + goto fail; + } + (void)__os_closehandle(env, fhp); + + /* Wait for wakeup process/thread. */ + if ((err = os_wait(&wakeup_pid, 1)) != 0) { + fprintf(stderr, "%s: %lu: exited %d\n", + progname, (u_long)wakeup_pid, err); + goto fail; + } + } else { /* Run the single-process test. */ + /* Spawn locker threads. */ + if (locker_start(0) != 0) + goto fail; + + /* Spawn wakeup thread. */ + if (wakeup_start(nthreads) != 0) + goto fail; + + /* Wait for all lockers to exit. */ + if (locker_wait() != 0) + goto fail; + + /* Signal wakeup process to exit. */ + if ((err = __os_open( + env, MT_FILE_QUIT, 0, DB_OSO_CREATE, 0664, &fhp)) != 0) { + fprintf(stderr, + "%s: open %s\n", progname, db_strerror(err)); + goto fail; + } + (void)__os_closehandle(env, fhp); + + /* Wait for wakeup thread. */ + if (wakeup_wait() != 0) + goto fail; + } + + tm_mutex_stats(); /* Display run statistics. */ + tm_mutex_destroy(); /* Destroy mutexes. */ + + data_off(gm_addr, map_fhp); /* Detach from backing data. */ + + tm_env_close(); /* Detach from environment. */ + + printf("%s: test succeeded\n", progname); + return (EXIT_SUCCESS); + +fail: printf("%s: FAILED!\n", progname); + return (EXIT_FAILURE); +} + +int +locker_start(id) + u_long id; +{ +#if defined(MUTEX_THREAD_TEST) + u_int i; + int err; + + /* + * Spawn off threads. We have nthreads all locking and going to + * sleep, and one other thread cycling through and waking them up. + */ + if ((kidsp = + (os_thread_t *)calloc(sizeof(os_thread_t), nthreads)) == NULL) { + fprintf(stderr, "%s: %s\n", progname, strerror(errno)); + return (1); + } + for (i = 0; i < nthreads; i++) + if ((err = os_thread_create( + &kidsp[i], NULL, run_lthread, (void *)(id + i))) != 0) { + fprintf(stderr, "%s: failed spawning thread: %s\n", + progname, db_strerror(err)); + return (1); + } + return (0); +#else + return (run_lthread((void *)id) == NULL ? 0 : 1); +#endif +} + +int +locker_wait() +{ +#if defined(MUTEX_THREAD_TEST) + u_int i; + void *retp; + + /* Wait for the threads to exit. */ + for (i = 0; i < nthreads; i++) { + (void)os_thread_join(kidsp[i], &retp); + if (retp != NULL) { + fprintf(stderr, + "%s: thread exited with error\n", progname); + return (1); + } + } + free(kidsp); +#endif + return (0); +} + +void * +run_lthread(arg) + void *arg; +{ + TM *gp, *mp, *tp; + u_long id, tid; + u_int lock, nl; + int err, i; + + id = (u_long)arg; +#if defined(MUTEX_THREAD_TEST) + tid = (u_long)os_thread_self(); +#else + tid = 0; +#endif + printf("Locker: ID %03lu (PID: %lu; TID: %lx)\n", + id, (u_long)getpid(), tid); + + gp = (TM *)gm_addr; + tp = (TM *)(tm_addr + id * sizeof(TM)); + + for (nl = nlocks; nl > 0;) { + /* Select and acquire a data lock. */ + lock = (u_int)rand() % maxlocks; + mp = (TM *)(lm_addr + lock * sizeof(TM)); + if (verbose) + printf("%03lu: lock %d (mtx: %lu)\n", + id, lock, (u_long)mp->mutex); + + if ((err = dbenv->mutex_lock(dbenv, mp->mutex)) != 0) { + fprintf(stderr, "%s: %03lu: never got lock %d: %s\n", + progname, id, lock, db_strerror(err)); + return ((void *)1); + } + if (mp->id != 0) { + fprintf(stderr, + "%s: RACE! (%03lu granted lock %d held by %03lu)\n", + progname, id, lock, mp->id); + return ((void *)1); + } + mp->id = id; + + /* + * Pretend to do some work, periodically checking to see if + * we still hold the mutex. + */ + for (i = 0; i < 3; ++i) { + __os_yield(env, 0, (u_long)rand() % 3); + if (mp->id != id) { + fprintf(stderr, + "%s: RACE! (%03lu stole lock %d from %03lu)\n", + progname, mp->id, lock, id); + return ((void *)1); + } + } + + /* + * Test self-blocking and unlocking by other threads/processes: + * + * acquire the global lock + * set our wakeup flag + * release the global lock + * acquire our per-thread lock + * + * The wakeup thread will wake us up. + */ + if ((err = dbenv->mutex_lock(dbenv, gp->mutex)) != 0) { + fprintf(stderr, "%s: %03lu: global lock: %s\n", + progname, id, db_strerror(err)); + return ((void *)1); + } + if (tp->id != 0 && tp->id != id) { + fprintf(stderr, + "%s: %03lu: per-thread mutex isn't mine, owned by %03lu\n", + progname, id, tp->id); + return ((void *)1); + } + tp->id = id; + if (verbose) + printf("%03lu: self-blocking (mtx: %lu)\n", + id, (u_long)tp->mutex); + if (tp->wakeme) { + fprintf(stderr, + "%s: %03lu: wakeup flag incorrectly set\n", + progname, id); + return ((void *)1); + } + tp->wakeme = 1; + if ((err = dbenv->mutex_unlock(dbenv, gp->mutex)) != 0) { + fprintf(stderr, + "%s: %03lu: global unlock: %s\n", + progname, id, db_strerror(err)); + return ((void *)1); + } + if ((err = dbenv->mutex_lock(dbenv, tp->mutex)) != 0) { + fprintf(stderr, "%s: %03lu: per-thread lock: %s\n", + progname, id, db_strerror(err)); + return ((void *)1); + } + /* Time passes... */ + if (tp->wakeme) { + fprintf(stderr, "%s: %03lu: wakeup flag not cleared\n", + progname, id); + return ((void *)1); + } + + if (verbose) + printf("%03lu: release %d (mtx: %lu)\n", + id, lock, (u_long)mp->mutex); + + /* Release the data lock. */ + mp->id = 0; + if ((err = dbenv->mutex_unlock(dbenv, mp->mutex)) != 0) { + fprintf(stderr, + "%s: %03lu: lock release: %s\n", + progname, id, db_strerror(err)); + return ((void *)1); + } + + if (--nl % 1000 == 0) + printf("%03lu: %d\n", id, nl); + } + + return (NULL); +} + +int +wakeup_start(id) + u_long id; +{ +#if defined(MUTEX_THREAD_TEST) + int err; + + /* + * Spawn off wakeup thread. + */ + if ((err = os_thread_create( + &wakep, NULL, run_wthread, (void *)id)) != 0) { + fprintf(stderr, "%s: failed spawning wakeup thread: %s\n", + progname, db_strerror(err)); + return (1); + } + return (0); +#else + return (run_wthread((void *)id) == NULL ? 0 : 1); +#endif +} + +int +wakeup_wait() +{ +#if defined(MUTEX_THREAD_TEST) + void *retp; + + /* + * A file is created when the wakeup thread is no longer needed. + */ + (void)os_thread_join(wakep, &retp); + if (retp != NULL) { + fprintf(stderr, + "%s: wakeup thread exited with error\n", progname); + return (1); + } +#endif + return (0); +} + +/* + * run_wthread -- + * Thread to wake up other threads that are sleeping. + */ +void * +run_wthread(arg) + void *arg; +{ + TM *gp, *tp; + u_long id, tid; + u_int check_id; + int err, quitcheck; + + id = (u_long)arg; + quitcheck = 0; +#if defined(MUTEX_THREAD_TEST) + tid = (u_long)os_thread_self(); +#else + tid = 0; +#endif + printf("Wakeup: ID %03lu (PID: %lu; TID: %lx)\n", + id, (u_long)getpid(), tid); + + gp = (TM *)gm_addr; + + /* Loop, waking up sleepers and periodically sleeping ourselves. */ + for (check_id = 0;; ++check_id) { + /* Check to see if the locking threads have finished. */ + if (++quitcheck >= 100) { + quitcheck = 0; + if (__os_exists(env, MT_FILE_QUIT, NULL) == 0) + break; + } + + /* Check for ID wraparound. */ + if (check_id == nthreads * nprocs) + check_id = 0; + + /* Check for a thread that needs a wakeup. */ + tp = (TM *)(tm_addr + check_id * sizeof(TM)); + if (!tp->wakeme) + continue; + + if (verbose) { + printf("%03lu: wakeup thread %03lu (mtx: %lu)\n", + id, tp->id, (u_long)tp->mutex); + (void)fflush(stdout); + } + + /* Acquire the global lock. */ + if ((err = dbenv->mutex_lock(dbenv, gp->mutex)) != 0) { + fprintf(stderr, "%s: wakeup: global lock: %s\n", + progname, db_strerror(err)); + return ((void *)1); + } + + tp->wakeme = 0; + if ((err = dbenv->mutex_unlock(dbenv, tp->mutex)) != 0) { + fprintf(stderr, "%s: wakeup: unlock: %s\n", + progname, db_strerror(err)); + return ((void *)1); + } + + if ((err = dbenv->mutex_unlock(dbenv, gp->mutex)) != 0) { + fprintf(stderr, "%s: wakeup: global unlock: %s\n", + progname, db_strerror(err)); + return ((void *)1); + } + + __os_yield(env, 0, (u_long)rand() % 3); + } + return (NULL); +} + +/* + * tm_env_init -- + * Create the backing database environment. + */ +int +tm_env_init() +{ + u_int32_t flags; + int ret; + char *home; + + /* + * Create an environment object and initialize it for error + * reporting. + */ + if ((ret = db_env_create(&dbenv, 0)) != 0) { + fprintf(stderr, "%s: %s\n", progname, db_strerror(ret)); + return (1); + } + env = dbenv->env; + dbenv->set_errfile(dbenv, stderr); + dbenv->set_errpfx(dbenv, progname); + + /* Allocate enough mutexes. */ + if ((ret = dbenv->mutex_set_increment(dbenv, + 1 + nthreads * nprocs + maxlocks)) != 0) { + dbenv->err(dbenv, ret, "dbenv->mutex_set_increment"); + return (1); + } + + flags = DB_CREATE; + if (nprocs == 1) { + home = NULL; + flags |= DB_PRIVATE; + } else + home = TESTDIR; + if (nthreads != 1) + flags |= DB_THREAD; + if ((ret = dbenv->open(dbenv, home, flags, 0)) != 0) { + dbenv->err(dbenv, ret, "environment open: %s", home); + return (1); + } + + return (0); +} + +/* + * tm_env_close -- + * Close the backing database environment. + */ +void +tm_env_close() +{ + (void)dbenv->close(dbenv, 0); +} + +/* + * tm_mutex_init -- + * Initialize the mutexes. + */ +void +tm_mutex_init() +{ + TM *mp; + u_int i; + int err; + + if (verbose) + printf("Allocate the global mutex: "); + mp = (TM *)gm_addr; + if ((err = dbenv->mutex_alloc(dbenv, 0, &mp->mutex)) != 0) { + fprintf(stderr, "%s: DB_ENV->mutex_alloc (global): %s\n", + progname, db_strerror(err)); + exit(EXIT_FAILURE); + } + if (verbose) + printf("%lu\n", (u_long)mp->mutex); + + if (verbose) + printf( + "Allocate %d per-thread, self-blocking mutexes: ", + nthreads * nprocs); + for (i = 0; i < nthreads * nprocs; ++i) { + mp = (TM *)(tm_addr + i * sizeof(TM)); + if ((err = dbenv->mutex_alloc( + dbenv, DB_MUTEX_SELF_BLOCK, &mp->mutex)) != 0) { + fprintf(stderr, + "%s: DB_ENV->mutex_alloc (per-thread %d): %s\n", + progname, i, db_strerror(err)); + exit(EXIT_FAILURE); + } + if ((err = dbenv->mutex_lock(dbenv, mp->mutex)) != 0) { + fprintf(stderr, + "%s: DB_ENV->mutex_lock (per-thread %d): %s\n", + progname, i, db_strerror(err)); + exit(EXIT_FAILURE); + } + if (verbose) + printf("%lu ", (u_long)mp->mutex); + } + if (verbose) + printf("\n"); + + if (verbose) + printf("Allocate %d per-lock mutexes: ", maxlocks); + for (i = 0; i < maxlocks; ++i) { + mp = (TM *)(lm_addr + i * sizeof(TM)); + if ((err = dbenv->mutex_alloc(dbenv, 0, &mp->mutex)) != 0) { + fprintf(stderr, + "%s: DB_ENV->mutex_alloc (per-lock: %d): %s\n", + progname, i, db_strerror(err)); + exit(EXIT_FAILURE); + } + if (verbose) + printf("%lu ", (u_long)mp->mutex); + } + if (verbose) + printf("\n"); +} + +/* + * tm_mutex_destroy -- + * Destroy the mutexes. + */ +void +tm_mutex_destroy() +{ + TM *gp, *mp; + u_int i; + int err; + + if (verbose) + printf("Destroy the global mutex.\n"); + gp = (TM *)gm_addr; + if ((err = dbenv->mutex_free(dbenv, gp->mutex)) != 0) { + fprintf(stderr, "%s: DB_ENV->mutex_free (global): %s\n", + progname, db_strerror(err)); + exit(EXIT_FAILURE); + } + + if (verbose) + printf("Destroy the per-thread mutexes.\n"); + for (i = 0; i < nthreads * nprocs; ++i) { + mp = (TM *)(tm_addr + i * sizeof(TM)); + if ((err = dbenv->mutex_free(dbenv, mp->mutex)) != 0) { + fprintf(stderr, + "%s: DB_ENV->mutex_free (per-thread %d): %s\n", + progname, i, db_strerror(err)); + exit(EXIT_FAILURE); + } + } + + if (verbose) + printf("Destroy the per-lock mutexes.\n"); + for (i = 0; i < maxlocks; ++i) { + mp = (TM *)(lm_addr + i * sizeof(TM)); + if ((err = dbenv->mutex_free(dbenv, mp->mutex)) != 0) { + fprintf(stderr, + "%s: DB_ENV->mutex_free (per-lock: %d): %s\n", + progname, i, db_strerror(err)); + exit(EXIT_FAILURE); + } + } +} + +/* + * tm_mutex_stats -- + * Display mutex statistics. + */ +void +tm_mutex_stats() +{ +#ifdef HAVE_STATISTICS + TM *mp; + uintmax_t set_wait, set_nowait; + u_int i; + + printf("Per-lock mutex statistics.\n"); + for (i = 0; i < maxlocks; ++i) { + mp = (TM *)(lm_addr + i * sizeof(TM)); + __mutex_set_wait_info(env, mp->mutex, &set_wait, &set_nowait); + printf("mutex %2d: wait: %lu; no wait %lu\n", i, + (u_long)set_wait, (u_long)set_nowait); + } +#endif +} + +/* + * data_on -- + * Map in or allocate the backing data space. + */ +void +data_on(gm_addrp, tm_addrp, lm_addrp, fhpp, init) + u_int8_t **gm_addrp, **tm_addrp, **lm_addrp; + DB_FH **fhpp; + int init; +{ + DB_FH *fhp; + size_t nwrite; + int err; + void *addr; + + fhp = NULL; + + /* + * In a single process, use heap memory. + */ + if (nprocs == 1) { + if (init) { + if ((err = + __os_calloc(env, (size_t)len, 1, &addr)) != 0) + exit(EXIT_FAILURE); + } else { + fprintf(stderr, + "%s: init should be set for single process call\n", + progname); + exit(EXIT_FAILURE); + } + } else { + if (init) { + if (verbose) + printf("Create the backing file.\n"); + + if ((err = __os_open(env, MT_FILE, 0, + DB_OSO_CREATE | DB_OSO_TRUNC, 0666, &fhp)) == -1) { + fprintf(stderr, "%s: %s: open: %s\n", + progname, MT_FILE, db_strerror(err)); + exit(EXIT_FAILURE); + } + + if ((err = + __os_seek(env, fhp, 0, 0, (u_int32_t)len)) != 0 || + (err = + __os_write(env, fhp, &err, 1, &nwrite)) != 0 || + nwrite != 1) { + fprintf(stderr, "%s: %s: seek/write: %s\n", + progname, MT_FILE, db_strerror(err)); + exit(EXIT_FAILURE); + } + } else + if ((err = __os_open(env, MT_FILE, 0, 0, 0, &fhp)) != 0) + exit(EXIT_FAILURE); + + if ((err = + __os_mapfile(env, MT_FILE, fhp, len, 0, &addr)) != 0) + exit(EXIT_FAILURE); + } + + *gm_addrp = (u_int8_t *)addr; + addr = (u_int8_t *)addr + sizeof(TM); + *tm_addrp = (u_int8_t *)addr; + addr = (u_int8_t *)addr + sizeof(TM) * (nthreads * nprocs); + *lm_addrp = (u_int8_t *)addr; + + if (fhpp != NULL) + *fhpp = fhp; +} + +/* + * data_off -- + * Discard or de-allocate the backing data space. + */ +void +data_off(addr, fhp) + u_int8_t *addr; + DB_FH *fhp; +{ + if (nprocs == 1) + __os_free(env, addr); + else { + if (__os_unmapfile(env, addr, len) != 0) + exit(EXIT_FAILURE); + if (__os_closehandle(env, fhp) != 0) + exit(EXIT_FAILURE); + } +} + +/* + * usage -- + * + */ +int +usage() +{ + fprintf(stderr, "usage: %s %s\n\t%s\n", progname, + "[-v] [-l maxlocks]", + "[-n locks] [-p procs] [-T locker=ID|wakeup=ID] [-t threads]"); + return (EXIT_FAILURE); +} + +/* + * os_wait -- + * Wait for an array of N procs. + */ +int +os_wait(procs, n) + os_pid_t *procs; + u_int n; +{ + u_int i; + int status; +#if defined(DB_WIN32) + DWORD ret; +#endif + + status = 0; + +#if defined(DB_WIN32) + do { + ret = WaitForMultipleObjects(n, procs, FALSE, INFINITE); + i = ret - WAIT_OBJECT_0; + if (i < 0 || i >= n) + return (__os_posix_err(__os_get_syserr())); + + if ((GetExitCodeProcess(procs[i], &ret) == 0) || (ret != 0)) + return (ret); + + /* remove the process handle from the list */ + while (++i < n) + procs[i - 1] = procs[i]; + } while (--n); +#elif !defined(HAVE_VXWORKS) + do { + if (wait(&status) == -1) + return (__os_posix_err(__os_get_syserr())); + + if (WIFEXITED(status) == 0 || WEXITSTATUS(status) != 0) { + for (i = 0; i < n; i++) + (void)kill(procs[i], SIGKILL); + return (WEXITSTATUS(status)); + } + } while (--n); +#endif + + return (0); +} + +os_pid_t +spawn_proc(id, tmpath, typearg) + u_long id; + char *tmpath, *typearg; +{ + char *const vbuf = verbose ? "-v" : NULL; + char *args[13], lbuf[16], nbuf[16], pbuf[16], tbuf[16], Tbuf[256]; + + args[0] = tmpath; + args[1] = "-l"; + snprintf(lbuf, sizeof(lbuf), "%d", maxlocks); + args[2] = lbuf; + args[3] = "-n"; + snprintf(nbuf, sizeof(nbuf), "%d", nlocks); + args[4] = nbuf; + args[5] = "-p"; + snprintf(pbuf, sizeof(pbuf), "%d", nprocs); + args[6] = pbuf; + args[7] = "-t"; + snprintf(tbuf, sizeof(tbuf), "%d", nthreads); + args[8] = tbuf; + args[9] = "-T"; + snprintf(Tbuf, sizeof(Tbuf), "%s=%lu", typearg, id); + args[10] = Tbuf; + args[11] = vbuf; + args[12] = NULL; + + return (os_spawn(tmpath, args)); +} + +os_pid_t +os_spawn(path, argv) + const char *path; + char *const argv[]; +{ + os_pid_t pid; + int status; + + COMPQUIET(pid, 0); + COMPQUIET(status, 0); + +#ifdef HAVE_VXWORKS + fprintf(stderr, "%s: os_spawn not supported for VxWorks.\n", progname); + return (OS_BAD_PID); +#elif defined(HAVE_QNX) + /* + * For QNX, we cannot fork if we've ever used threads. So + * we'll use their spawn function. We use 'spawnl' which + * is NOT a POSIX function. + * + * The return value of spawnl is just what we want depending + * on the value of the 'wait' arg. + */ + return (spawnv(P_NOWAIT, path, argv)); +#elif defined(DB_WIN32) + return (os_pid_t)(_spawnv(P_NOWAIT, path, argv)); +#else + if ((pid = fork()) != 0) { + if (pid == -1) + return (OS_BAD_PID); + return (pid); + } else { + (void)execv(path, argv); + exit(EXIT_FAILURE); + } +#endif +} diff --git a/db-4.8.30/mutex/uts4_cc.s b/db-4.8.30/mutex/uts4_cc.s new file mode 100644 index 0000000..1c67c8b --- /dev/null +++ b/db-4.8.30/mutex/uts4_cc.s @@ -0,0 +1,26 @@ + / See the file LICENSE for redistribution information. + / + / Copyright (c) 1997-2009 Oracle. All rights reserved. + / + / $Id$ + / + / int uts_lock ( int *p, int i ); + / Update the lock word pointed to by p with the + / value i, using compare-and-swap. + / Returns 0 if update was successful. + / Returns 1 if update failed. + / + entry uts_lock + uts_lock: + using .,r15 + st r2,8(sp) / Save R2 + l r2,64+0(sp) / R2 -> word to update + slr r0, r0 / R0 = current lock value must be 0 + l r1,64+4(sp) / R1 = new lock value + cs r0,r1,0(r2) / Try the update ... + be x / ... Success. Return 0 + la r0,1 / ... Failure. Return 1 + x: / + l r2,8(sp) / Restore R2 + b 2(,r14) / Return to caller + drop r15 |