1043 lines
24 KiB
C
1043 lines
24 KiB
C
/*
|
|
* Standalone mutex tester for Berkeley DB mutexes.
|
|
*
|
|
* $Id: test_mutex.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
|
*/
|
|
|
|
#include "db_config.h"
|
|
|
|
#include "db_int.h"
|
|
|
|
#include <sys/wait.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 */
|
|
|
|
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 = (uintptr_t)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 % 100 == 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;
|
|
|
|
id = (uintptr_t)arg;
|
|
#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 (__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;
|
|
u_int32_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, 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
|
|
}
|